6. Bloqueo

Cuando se desarrollan aplicaciones multiusuario basadas en bases de datos, la mayor dificultad es: por un lado, maximizar el uso del acceso simultáneo a la base de datos, por un lado, para garantizar que cada usuario pueda leer y modificar datos de manera coherente Mecanismo de bloqueo.

6.1 ¿Qué es un candado?

El mecanismo de bloqueo se usa para administrar el acceso concurrente a los recursos compartidos. InnoDB no solo bloquea los datos de la tabla a nivel de fila, sino que también se bloquea en varios otros lugares dentro de la base de datos, lo que permite el acceso concurrente a muchos recursos diferentes. Por ejemplo, operar la lista LRU en el grupo de búferes, eliminar y agregar elementos en la lista LRU móvil, etc.

La implementación de bloqueos de InnoDB es muy similar a la base de datos de Oracle, proporcionando lecturas constantes sin bloqueo, soporte de bloqueo de nivel de fila, los bloqueos de nivel de fila no tienen sobrecarga adicional asociada y pueden obtener concurrencia y consistencia.

6.2 Cerradura y pestillo

Los pestillos generalmente se denominan pestillos (ligeros), porque requieren un tiempo de bloqueo muy corto y, si la duración es larga, el rendimiento será bajo. En InnoDB, el enclavamiento se puede dividir en mutex y rwlock (bloqueo de lectura-escritura). El propósito es asegurar la precisión de los hilos concurrentes que operan recursos críticos, y generalmente no hay verificación de punto muerto.

lock generalmente bloquea objetos en la base de datos, como tablas, páginas y filas. Y el objeto de bloqueo solo se libera después de la transacción de confirmación o reversión.

  bloquear pestillo
Objeto Negocios Hilo
Proteger Memoria de base de datos Estructura de datos de memoria
Duración Todo el proceso de transacción Recurso crítico
Patrón Bloqueo de fila, bloqueo de mesa, bloqueo de intención Bloqueo de lectura y escritura, bloqueo de mutex
Punto muerto Detección y procesamiento de punto muerto a través de mecanismos de espera de gráficos y tiempo de espera Sin mecanismo de detección y procesamiento de punto muerto, solo a través del orden de bloqueo de la aplicación para garantizar que no se produzca un punto muerto
Existe en tabla hash del administrador de bloqueos Objeto de cada estructura de datos.

6.3 Bloqueos en el motor de almacenamiento InnoDB

6.3.1 Tipos de cerraduras

InnoDB implementa los siguientes dos bloqueos estándar de nivel de fila:

  1. El bloqueo compartido, S Lock, permite que las transacciones lean una fila de datos
  2. El bloqueo exclusivo, X Lock, permite que las transacciones eliminen o actualicen una fila de datos

Los bloqueos S y X son bloqueos de fila, y la compatibilidad se refiere a la compatibilidad de los registros en la misma fila.

InnoDB admite el bloqueo de granularidad múltiple, que admite bloqueos de nivel de fila y bloqueos de nivel de tabla al mismo tiempo, es decir, bloqueos de intención, bloqueos de intención dividen el objeto bloqueado en múltiples niveles, y los bloqueos de intención significan que las transacciones quieren una mayor precisión Bloquearlo

Si necesita agregar un bloqueo X al registro r en la página, debe intentar bloquear IX en la base de datos A, tabla y página, y finalmente bloquear X en r. Si alguna de estas partes causa una espera, entonces la operación debe esperar a que se complete el bloqueo de grano grueso. InnoDB admite bloqueos de intención que son relativamente simples: los bloqueos de intención son bloqueos a nivel de tabla, principalmente para revelar el tipo de bloqueos que se solicitarán en la siguiente fila de una transacción.

  1. Intención de bloqueo compartido (IS Lock), la transacción desea obtener un bloqueo compartido de algunas filas en la tabla
  2. Intención de bloqueo exclusivo (IX Lock), la transacción desea obtener un bloqueo exclusivo de algunas filas en la tabla

InnoDB admite bloqueos de nivel de fila, por lo que los bloqueos de intención no bloquearán ninguna solicitud que no sea un análisis completo de la tabla.

  ES IX S X
ES Compatible Arrow Rong Compatible No compatible
IX Compatible Compatible No compatible No compatible
S Compatible No compatible Compatible No compatible
X No compatible No compatible No compatible No compatible

6.3.2 Lectura constante sin bloqueo

La lectura constante sin bloqueo (lectura constante sin bloqueo) se refiere a InnoDB a través del control de concurrencia de versiones múltiples (control de concurrencia de versiones múltiples) para leer los datos de la fila de tiempo de ejecución actual en la base de datos. Si en este momento, la fila de lectura está realizando una eliminación o actualización, entonces la operación de lectura no esperará a que se libere el bloqueo en la fila. Por el contrario, InnoDB leerá una instantánea de la fila (esta implementación se realiza mediante un segmento de deshacer ), Y el segmento de deshacer se usa para revertir los datos en una transacción, de modo que no haya una sobrecarga adicional. Además, la lectura de datos de instantáneas no necesita ser bloqueada, porque ninguna transacción necesita modificar datos históricos .

La lectura constante sin bloqueo es el modo de lectura predeterminado de InnoDB (nivel de aislamiento repetible) , es decir, la lectura no ocupará y esperará bloqueos en la tabla.

Una fila registra más de un dato de instantánea. Esta tecnología generalmente se llama tecnología de control de concurrencia de versiones múltiples de fila. En el nivel de aislamiento de transacción de READ COMMITED y REPEATABLE READ, InnoDB usa lecturas consistentes sin bloqueo.

Bajo el nivel de aislamiento de transacción LEÍDO COMPROMETIDO, para los datos de la instantánea, siempre se leen los últimos datos de la fila bloqueada. Desde la perspectiva de la teoría de la base de datos, viola el aislamiento I en ACID. REPEATABLE READ lee la versión de datos de fila al comienzo de la transacción.

Tiempo Sesión A Sesión B
1 EMPEZAR  
2 seleccione * de t donde id = 1  
3   EMPEZAR
4 4  

actualizar t establecer id = 3 donde id = 1

En este momento, la transacción no se envía, se agrega el bloqueo X, la consulta de la sesión A5, usa los resultados de la consulta MVCC

5 5 seleccione * de t donde id = 1  
6 6   COMETER;
7 7 seleccione * de t donde id = 1  
8 COMETER;  

En el nivel de aislamiento READ COMMITED, 1, 5 devuelve datos con id = 1, 7 devuelve nulo, porque después de que se envía la sesión B, los datos con id = 1 se actualizan a 3, y los datos en este momento son los últimos. En REPEATABLE READ, 1, 5, 7 siempre devuelven los datos con id = 1, porque los datos antes del inicio de la transacción siempre se leen.

6.3.3 Lectura de bloqueo constante

En algunos casos, los usuarios necesitan bloquear explícitamente la operación de lectura de la base de datos para garantizar la consistencia de los datos. Esto requiere que la base de datos admita sentencias de bloqueo, incluso para consultas seleccionadas. InnoDB admite dos operaciones de lectura de bloqueo consistentes para sentencias select:

  1. seleccione ...... para actualizar
  2. seleccione ...... bloquear en modo compartido

seleccione ...... para la actualización agrega bloqueo X a la fila de lectura, ninguna otra transacción puede agregar ningún bloqueo a la fila. seleccione ...... compartir en modo Agregar bloqueo S al registro de fila de lectura, otras transacciones pueden agregar bloqueo S a la fila bloqueada, pero no pueden agregar bloqueo X, el bloqueo X solo puede bloquear.

 seleccione ...... para actualizar y seleccione ...... compartir en modo debe estar en la transacción, confirmación de transacción, liberación de bloqueo.

6.3.4 Crecimiento propio y bloqueo

En la estructura de memoria de InnoDB, hay un contador de autoincremento para cada tabla que contiene valores de autoincremento. Ejecute la siguiente instrucción para obtener el valor del contador:

seleccione MAX (auto_inc_col) de t para la actualización;

  La operación de inserción agregará 1 a la columna de auto-incremento de acuerdo con este valor de contador de auto-incremento. Esta implementación se denomina Bloqueo AUTO-INC. Este tipo de bloqueo en realidad utiliza un mecanismo especial de bloqueo de tabla. Para mejorar el rendimiento de inserción, el bloqueo no se libera después de que se complete la transacción, sino después de completar la instrucción SQL para la inserción del valor de auto-incremento Liberar de inmediato.

  Aunque el bloqueo de AUTO-INC mejora la concurrencia hasta cierto punto, el rendimiento de las inserciones concurrentes para columnas con valores de aumento automático es deficiente, y la transacción debe esperar la finalización de la inserción anterior (aunque no es necesario esperar la finalización de la transacción), seguido de Insertar ... La inserción de una gran cantidad de datos seleccionados afectará el rendimiento de la inserción, porque se bloqueará otra transacción.

Comenzando con  MySQL 5.1.22, InnoDB proporciona un mecanismo de implementación de crecimiento propio para mutexes ligeros . A partir de esta versión, InnoDB proporciona un parámetro innodb_autoinc_lock_mode para controlar el modo de autoincremento, que por defecto es 1.

innodb_autoinc_lock_mode Explicación
0 0 La implementación anterior a MySQL5.1.22, es decir, a través del modo de bloqueo AUTO-INC, debido al nuevo modo de crecimiento propio, este valor no debería ser la preferencia del usuario
1

El valor predeterminado, para "inserciones simples", este valor usará el mutex para acumular los contadores en la memoria. Para "insertos a granel", el método tradicional de bloqueo AUTO-INC todavía se utiliza. En esta configuración, si no se considera la operación de reversión, el crecimiento del valor agregado es continuo. Y pensando de esta manera, la forma de replicación basada en declaraciones todavía puede funcionar muy bien.

Nota: Si ha utilizado el bloqueo AUTO-INC, y necesita realizar nuevamente "inserciones simples", deberá esperar a que se libere el "bloqueo AUTO-INC".

2 En este modo, todos los valores de aumento automático de "insertar Me gusta" se generan mediante exclusión mutua, no bloqueo automático-INC. Obviamente, este es el método de mayor rendimiento, sin embargo, traerá algunos problemas porque Con la existencia de concurrencia, el valor del auto-aumento puede no ser continuo en cada inserción. Además, es importante que haya problemas basados ​​en la replicación basada en declaraciones. Por lo tanto, al usar este modo, la replicación basada en filas debe usarse en todo momento, para garantizar la máxima concurrencia y consistencia de los datos de replicación maestro-esclavo.

 

Insertar tipo Explicación
insert-like 所有的插入语句,包括 insert、replace、insert……select、replace……select、load data等
simple inserts 能在插入前就确定插入行数的语句,包括insert,replace等,不包含 insert …… on duplicate key update这类SQL
bulk inserts 在插入前不能确定插入行数的语,如 insert……select,replace……select、load data等
mixed-mode inserts 插入中有一部分的值是自增长的,一部分是可以确定的,如insert into t (e1,e2) values (1,'a'),(NULL,'b'),(3,'c'),也可以是 insert …… on duplicate key update这类SQL 

另外,在 InnoDB中,自增长的列必须是索引,同时是索引的第一个列,否则MySQL会抛出异常。

6.3.5 外键和锁

外键主要用于引用完整性的约束检查,在InnoDB中,对于一个外键列,如果没有显示的对这个列添加索引,InnoDB会自动对其加索引,避免表锁。

对于外键的插入或更新,需要先查询父表,但是对于父表的查询不是使用一致性非锁定读(MVCC),因为这样会发生数据不一致的问题,使用的是 select ……lock in share mode,主动为付表加S锁,如果这时父表上已有X锁,则阻塞。

时间 会话A 会话B
1 BEGIN  
2 delete from parent where id=5  
3   BEGIN
4  

insert into child select 2,5

#第二列是外键,执行时被阻塞waiting

6.4 锁的算法

6.4.1 行锁的三种算法

  InnoDB 有三种行锁算法,分别是

  1. Record Lock:单个记录上的锁
  2. Gap Lock:间隙锁,锁定一个范围,但不包含记录本身
  3. Next-Key Lock:Gap Lock + Record Lock,锁定一个范围,并锁定记录本身

Record Lock 总是锁住索引记录,如果 InnoDB在建立时没有设置任何一个索引,那么 InnoDB会使用隐士的主键来锁定。

InnoDB对于行的查询都是采用 Next-Key Lock,其目的是为了解决幻读 Phantom Problem,是谓词锁的一种改进。

当查询的索引含有唯一属性时,InnoDB 会对Next-Key Lock优化,降级为 Record Key,以此提高应用的并发性。

如前面介绍的。next-key lock 降级为record lock是在查询的列是唯一索引的情况下,若是辅助索引,则情况不同:

create table z (a int, b int , Primary key(a), key(b))
insert into z select 1,1;
insert into z select 3,1;
insert into z select 5,3;
insert into z select 7,6;
insert into z select 10,8;

  其中b是辅助索引,此时执行 select * from z where b=3 for update;

  此时,sql 语句通过索引列b进行查询,因此其使用传统的 next-key lock 进行加锁,并且由于有两个索引,其需要分别进行锁定。对于聚集索引,仅对列a=5的索引加record lock。而辅助索引,加的是next-key lock,锁定的是(1,3)范围,特别注意的是,InnoDB还会对辅助索引下一个键值加上 gap lock,即还有一个(3,6)范围的锁。因此,一下SQL都会被阻塞:

select * from z where a=5 lock in share mode;
insert into z select 4,2;
insert into z select 6,5;

  从上面可以看出,gap lock的作用是为了阻止多个事务将记录插入到同一个范围内,而这会导致幻读问题的产生。用户可以通过以下两种方式来关闭 Gap Lock:

  1. 将事务隔离级别改为 READ COMMITED 
  2. 将参数 innodb_locks_unsafe_for_binlog设为1

在上述的配置下,除了外键约束和唯一性检查依然需要的Gap Lock,其余情况仅使用 Record Lock进行锁定,需要牢记的是,上述配置破坏了事务的隔离性,并且对 replication可能会导致不一致。且从性能上看,READ COMMITED也不会优于默认的 READ REPEATABLE;

在 InnoDB中,对Insert的操作,其会检查插入记录的下一条记录是否被锁定,若已锁定,则不允许查询。对于上面的例子,会话A已经锁定了表中b=3的记录,即已锁定了(1,3)的范围,这时如果在其他会话中进行如下的插入同样会导致阻塞

insert into z select 2,2;

  因为检测到辅助索引列b上插入2的记录时,发现3已经被索引,而将插入修改为如下值,可以立即执行:

insert into z select 2,0;

  最后,对于唯一键值的锁定,next-key lock降级为record ke仅存在于查询所有的唯一索引列。若唯一索引由多个列组成,而查询仅是查找多个唯一索引列中的一个,那么查询其实是range类型,而不是point查询,故InnoDB依然采用 next-key lock进行锁定

6.4.2 解决 Phantom Problem

幻读指的是在同一个事务下,连续执行两次相同的SQL语句可能返回不一样的结果,第二次的SQL语句可能会返回之前不存在的行。

InnoDB采用 next-key lock 的算法解决了 Phantom Problem,对 select * from t where id > 2 for update,锁住的不单是5这个值,而是对(2,+∞)这个范围加了X锁。因此,对这个范围的插入是不允许的,从而避免幻读。

时间 会话A 会话B
1

set session

tx_isolation = 'READ-COMMITED'

 
2 BEGIN  
3

select * from t where a>2 for update;

***********1 row *************

a:4

 
4   BEGIN
5   insert into t select 4
6   COMMIT;
7

select * from t where a>2 for update;

***********1 row *************

a:4

***********2 row *************

a:5

 

REPEATABLE  READ 采用的是 next-key locking加锁。而 READCOMMITED 采用的是 record lock .

此外,用户可以通过 InnoDB的 next-key lock在应用层面实现唯一性的检查:

select * from table where col=xxx lock in share mode;
if not found any row :
#unique for insert value
insert into table values(……);

  如果用户通过一个索引查询一个值,并对该行加上了S lock,那么即使查询的值不存在,其锁定的也是一个范围,因此若没有返回任何行,那么新插入的值一定是唯一的。

那,如果在第一步select lock in share mode时,有多个事务并发操作,那么这种唯一性检查是否会有问题,其实不会,因为会发生死锁。只有一个事务会成功,其他的事务会抛出死锁错误

6.5 锁问题

6.5.1 脏读

脏数据是指未提交的数据,如果读到了脏数据,即一个事务可以读到另一个事务未提交的数据,则显然违反了数据库的隔离性。

脏读指的是在不同事务下,当前事务可以读到另外事务未提交的数据,即脏数据。

时间 会话A 会话B
1

set 

@@tx_isolation = 'read-ncommited'

 
2  

set 

@@tx_isolation = 'read-ncommited'

3  

BEGIN

4  

select * from t ;

**********1 row *************

a:1

5 insert into t select 2;  
6  

select * from t ;

**********1 row *************

a:1

**********2 row *************

a:2

脏读发生条件是需要事务的隔离级别为 read uncommited;目前大部分数据库至少设置为 read COMMITED;

6.5.2 不可重复读

不可重复读和脏读的区别是:脏读是读到未提交的数据,而不可重复读读到的是已提交的数据,但是违反了事务一致性的要求。

时间 会话A 会话B
1

et 

@@tx_isolation = 'read-commited'

 
2  

et 

@@tx_isolation = 'read-commited'

3 BEGIN BEGIN
4

select * from t ;

**********1 row *************

a:1

 
5   insert into t select 2;
6   COMMITED
7

select * from t ;

**********1 row *************

a:1

**********2 row *************

a:2

 

 

一般来说,不可重复读是可接受的,因为读到的是已提交的数据,本身没有带来很大问题。在 InnoDB中使用 next-key lock避免不可重复读问题,即 幻读(Phantom Problem)。在 Next-Key lock算法下,对索引的扫描,不仅是锁住扫描到的索引,还有这些索引覆盖的范围,因此在这个范围内插入是不允许的。这样则避免了另外的事务在这个范围内的插入导致不可重读的问题。

6.5.3 丢失更新

丢失更新就是一个事务的更新操作被另一个事务的更新操作覆盖,从而导致数据不一致。

  1. 事务1将行记录r更新为v1,但是事务未提交
  2. 事务2将行记录r更新为v2,事务未提交
  3. 事务1提交
  4. 事务2提交

当前数据库的任何隔离级别下,以上情况都不会导致数据库理论意义上的丢失更新问题,因为,对于行的DML操作,需要对行货其他粗粒度级别的对象加锁,步骤2,事务2并不能对记录进行更新,被阻塞,直到事务1提交。

但在生产应用中,还有一个逻辑意义的丢失更新问题,而导致该问题的不是因为数据库本身的问题,简单来说,下面情况会发生丢失更新:

  1. 事务T1查询一行数据,放入本地内存,返回给User1
  2. 事务T2查询一行数据,放入本地内存,返回给User2
  3. User1修改后,更新数据库提交
  4. User2修改后,更新数据库提交

显然,这个过程中,User1的修改操作”丢失“了。在银行操作中,尤为恐怖。要避免丢失,需要让事务串行化。

时间 会话A 会话B
1 BEGIN  
2

select cash into @cash

from account

where user=pUser for update

#加X锁

 
3  

select cash into @cash

from account

where user=pUser for update

#等待,直到m提交后,锁释放

  …… ……
m

update account set cash = @cash - 9000

where user = pUser

 
m+1 commit  
m+2  

update account set cash = @cash -1

where user = pUser

m+3   commit

6.6 阻塞

因为不同锁之间的兼容性关系,在有些时刻一个事务中的锁需要等待另一个事务中的锁释放它所占用的资源,这就是阻塞。

阻塞并不是一件坏事,其是为了确保事务可以并发且正常地运行。

需要牢记的是,默认情况下,InnoDB存储引擎不会回滚超时引发的错误异常,其实,InnoDB在大部分情况下都不会对异常进行回滚

时间 会话A 会话B
1

select * from t;

**********3 row *************

a:1

a:2

a:4

 
 2  BEGIN  
 3

 select * from t where a < 4 for update;

**********2 row *************

a:1

a:2

#对(2,4)上X锁

 
 4    BEGIN
 5    insert into t select 5;
 

 insert into t select 3;

#等待超时,需要确定超时后,5的插入是否需要回滚或提交,否则这是十分危险的状态

6.7 死锁

死锁是指两个或两个以上的事务在执行过程中,因争夺资源而造成的一种相互等待的现象。解决死锁的办法:

  1. 超时回滚
  2. wait-for graph 等待图的方式进行死锁检测,采用深度有限的算法,选择回滚undo量最小的事务。

死锁的概率与以下因素有关:

  1. 事务的数量n,数量越多死锁概率越大
  2. 事务操作的数量r,数量越多,死锁概率越大
  3. 操作数据的集合R,越小,死锁概率越大

6.8 锁升级

锁升级就是将当前锁的粒度降低,例如把行锁升级为页锁,把页锁升级为表锁。

InnoDB不存在锁升级,因为其不是根据每个记录来产生行锁的。相反,其根据每个事务访问的每个页对锁进行管理,采用的是位图的方式。因此不管一个事务锁住页中的一个记录还是多个记录,开销都是一样的。

 

Supongo que te gusta

Origin www.cnblogs.com/jjfan0327/p/12720850.html
Recomendado
Clasificación