Bloqueo de MySQL explicación detallada y análisis de casos

Bloqueo de MySQL explicación detallada y análisis de casos

1. Una declaración de actualización

Insertar descripción de la imagen aquí

2. Introducción a los bloqueos de MySQL

  • En términos de granularidad de bloqueo, MySQL contiene principalmente tres tipos (niveles) de mecanismos de bloqueo:

    全局锁: Toda la base de datos está bloqueada. Implementado por la capa SQL de MySQL
    表级锁: una determinada tabla está bloqueada. Implementado por la capa SQL de MySQL
    行级锁: bloquea una determinada fila de datos y también puede bloquear espacios entre filas. Implementado por algunos motores de almacenamiento, como InnoDB.

  • Según la función del candado, se divide en: candado compartido y candado exclusivo .

    Bloqueos compartidos (bloqueos S):
    1. Compatibilidad: los registros con bloqueos S pueden agregar bloqueos S a otras transacciones, y no se permite agregar bloqueos X a otras transacciones
    2. Método de bloqueo: seleccionar... bloquear en compartir modo

    Bloqueos exclusivos (bloqueos X):
    1. Compatibilidad: se agregan registros con bloqueos X y otras transacciones no pueden agregar bloqueos S o bloqueos X
    2. Método de bloqueo: seleccione... para actualizar

3. Bloqueo global

El bloqueo global bloquea toda la instancia de la base de datos. Una vez bloqueado el bloqueo, toda la instancia queda en un estado de solo lectura. Las declaraciones de escritura MDL posteriores, las declaraciones DDL y las declaraciones de confirmación de transacciones que se hayan actualizado se bloquearán. Su escenario de uso típico es realizar una copia de seguridad lógica de toda la base de datos y bloquear todas las tablas para obtener una vista coherente y garantizar la integridad de los datos.

El comando para agregar un bloqueo global es:

flush tables with read lock

El comando para liberar el bloqueo global es:

unlock tables

Método de desbloqueo dos

Ver procesos bloqueados

Si la base de datos MySQL está bloqueada, puede usar el siguiente comando para ver el proceso bloqueado:
MOSTRAR LISTA DE PROCESOS COMPLETA;
este comando mostrará todas las conexiones de la base de datos actuales y su estado, incluida la instrucción SQL que se está ejecutando, el tiempo de ejecución y otra información. Al ver esta información, puede encontrar procesos bloqueados y su información relacionada.

Matar proceso bloqueado

Después de encontrar el proceso bloqueado, puede utilizar el siguiente comando para finalizar el proceso:
KILL <ID de proceso>;
donde el ID del proceso es el ID del proceso que se muestra en el comando SHOW FULL PROCESSLIST. Al finalizar el proceso bloqueado, se puede desbloquear la base de datos MySQL.

Demostración de bloqueo global

1.Preparación del entorno

CREATE TABLE mylock ( 
id int(11) NOT NULL AUTO_INCREMENT, 
NAME varchar(20) DEFAULT NULL, 
PRIMARY KEY (id) 
); 
INSERT INTO mylock (id,NAME) VALUES (1, 'a');
 INSERT INTO mylock (id,NAME) VALUES (2, 'b'); 
 INSERT INTO mylock (id,NAME) VALUES (3, 'c'); 
 INSERT INTO mylock (id,NAME) VALUES (4, 'd'); 

2. Demostración de bloqueo global

Responder 1 Responder 2
1. Bloquear la base de datosInsertar descripción de la imagen aquí 2. Cree una nueva sesión para realizar operaciones de consulta.Insertar descripción de la imagen aquí
ninguno 3. Ejecute la declaración de eliminación.realizar eliminación
4. Libera el bloqueoInsertar descripción de la imagen aquí 5. Ejecute la declaración de eliminación nuevamente.Insertar descripción de la imagen aquí

4. Bloqueos a nivel de tabla de MySQL (todos implementados por la capa del servidor)

1. Introducción a los bloqueos a nivel de tabla

Hay cuatro tipos de bloqueos a nivel de tabla en MySQL:

1. Bloqueos de lectura y escritura de tablas.
2. Bloqueo de metadatos (bloqueo de metadatos, MDL).
3. Bloqueos de intención (InnoDB)
4. Bloqueos AUTO-INC

2. Lectura de tabla S, bloqueo de escritura X

1) Comandos relacionados con el bloqueo de tabla

  • Variables de estado de contención para bloqueo a nivel de tabla implementadas en MySQL:
show status like 'table%'; 

Insertar descripción de la imagen aquí

  • table_locks_immediate: el número de bloqueos a nivel de tabla generados;
  • table_locks_waited: el número de esperas debidas a la contención de bloqueos a nivel de tabla
  • Los candados de mesa tienen dos formas.

    Bloqueo de lectura compartido de tabla (Bloqueo de lectura de tabla)
    Bloqueo de escritura exclusivo de tabla (Bloqueo de escritura de tabla)

  • Agregar manualmente el bloqueo de la tabla

    bloquear la tabla nombre de la tabla leer (escribir), nombre de la tabla 2 leer (escribir), otros;
    1. Bloqueo compartido a nivel de tabla bloquear
    la tabla nombre de la tabla leer;
    las operaciones de lectura se pueden compartir, pero no se pueden realizar operaciones. 2. Escritura del nombre de la tabla de la tabla de bloqueo
    exclusivo a nivel de tabla ;

  • Verificar el estado de bloqueo de la mesa

    mostrar mesas abiertas;

  • Quitar bloqueo de mesa:

    desbloquear mesas;

2) Demostración de bloqueo de mesa

1. Bloqueo compartido a nivel de tabla (bloqueo de lectura)

Se puede compartir para lectura, pero no se pueden realizar operaciones de escritura.
Una vez que se agrega un bloqueo compartido a esta tabla, otras sesiones también pueden agregar bloqueos compartidos a esta tabla; los bloqueos compartidos pueden coexistir, usted puede agregar un bloqueo y yo también puedo agregar un bloqueo. Debido a que es un bloqueo de lectura compartido, una vez bloqueado el bloqueo, la tabla se puede leer. La transacción actual no puede realizar operaciones de escritura y otras transacciones no pueden realizar operaciones de escritura. Todos los bloqueos de lectura deben liberarse.

lock table 表名 read;
Responder 1 Responder 2
1. Agregar bloqueo compartidoInsertar descripción de la imagen aquí ninguno
2. Consultar datosInsertar descripción de la imagen aquí ninguno
3. Nuevos datos: indica que hay un bloqueo de lectura y no se pueden actualizarInsertar descripción de la imagen aquí 4. Crea una nueva conversación y agrega un candado.Insertar descripción de la imagen aquí
ninguno 5. Consultar datosInsertar descripción de la imagen aquí
ninguno 6. Nuevos datos: indica que hay un bloqueo de lectura y no se pueden actualizarInsertar descripción de la imagen aquí
ninguno 7. Libere el bloqueo de lectura.Insertar descripción de la imagen aquí
ninguno 8. Ejecute los nuevos datos nuevamente y siempre estará en estado de espera porque hay un bloqueo de lectura en la sesión uno.Insertar descripción de la imagen aquí
9. Libera el bloqueo tan pronto como respondas.Insertar descripción de la imagen aquí 10. Nuevos datos agregados en la Respuesta 2: ÉxitoInsertar descripción de la imagen aquí
2. Bloqueo exclusivo a nivel de mesa

Se agrega un bloqueo exclusivo a la tabla en una sesión/transacción, y la sesión actual puede leer y escribir operaciones en la tabla; otras sesiones se bloquearán al leer y realizar operaciones en la tabla, y deberán esperar a que se active el bloqueo. liberado en la transacción bloqueada.

lock table 表名 write;

La mesa se encuentra en estado exclusivo.

Demostración de bloqueo de escritura

Responder 1 Responder 2
1. Agregue un bloqueo de escritura a la tabla mylock en la Respuesta 1Insertar descripción de la imagen aquí ninguno
2. La sesión actual puede realizar consultas + actualizaciones + nuevas operaciones en la tabla bloqueada.Insertar descripción de la imagen aquí
3. La segunda sesión se bloquea al realizar consultas (modificar / agregar) y debe esperar a que se libere el bloqueo.Insertar descripción de la imagen aquí
4. Libera el bloqueoInsertar descripción de la imagen aquí 5. La respuesta 2 obtuvo la consulta de bloqueo con éxito.Insertar descripción de la imagen aquí

3. Bloqueo de metadatos

1) Introducción al bloqueo de metadatos

Cuando se consulta una tabla en una transacción y otras sesiones no pueden modificar la estructura de la tabla, se agrega un bloqueo de metadatos a la tabla.
No es necesario utilizar MDL explícitamente, se agregará automáticamente al acceder a una tabla. La función de MDL es garantizar la corrección de la lectura y la escritura. Puede imaginar que si una consulta atraviesa los datos de una tabla y durante la ejecución otro hilo realiza cambios en la estructura de la tabla y elimina una columna, entonces los resultados obtenidos por el hilo de consulta no coinciden con la estructura de la tabla, lo cual definitivamente no es así. posible. .
Por lo tanto, MDL se introdujo en MySQL versión 5.5. Al agregar, eliminar, modificar y consultar una tabla, agregue bloqueos de lectura MDL; cuando realice cambios estructurales en la tabla, agregue bloqueos de escritura MDL .
Los bloqueos de lectura no son mutuamente excluyentes, por lo que puede tener varios subprocesos agregando, eliminando, modificando y consultando una tabla al mismo tiempo.
Los bloqueos de lectura y escritura y los bloqueos de escritura se excluyen mutuamente para garantizar la seguridad de los cambios en la estructura de la tabla.. Por lo tanto, si dos subprocesos quieren agregar campos a una tabla al mismo tiempo, uno de ellos tendrá que esperar a que el otro finalice antes de poder iniciar la ejecución .

2) Demostración de bloqueo de metadatos

Insertar descripción de la imagen aquí

1、session1: begin;--开启事务 
select * from mylock;--加MDL读锁 
2、session2: alter table mylock add f int; -- 修改阻塞 
3、session1:commit; --提交事务    或者    rollback 释放读锁 
4、session2:Query OK, 0 rows affected (38.67 sec) --修改完成 
Records: 0  Duplicates: 0  Warnings: 0
Responder 1 Responder 2
1. sesión1: comenzar; – iniciar transacciónInsertar descripción de la imagen aquí ninguno
1.1 seleccione * de mylock; – agregue bloqueo de lectura MDLInsertar descripción de la imagen aquí 2. sesión2: alterar tabla mylock agregar edad int;-Modificar bloqueoInsertar descripción de la imagen aquí
3. sesión1: confirmar; – confirmar la transacción o revertir para liberar el bloqueo de lecturaInsertar descripción de la imagen aquí 4. Ejecución exitosaInsertar descripción de la imagen aquí

4. Cerraduras AUTO-INC

El bloqueo AUTO-INC es un bloqueo especial a nivel de tabla que se produce cuando se produce una operación de inserción transaccional que involucra la columna AUTO_INCREMENT.

5. Bloqueos de nivel de fila de MySQL (granularidad fina)

Es necesario utilizar el motor InnoDB.

1. Introducción a los bloqueos a nivel de fila

Los bloqueos de nivel de fila de MySQL son implementados por el motor de almacenamiento . Aquí explicamos principalmente los bloqueos de nivel de fila de InnoDB . Los bloqueos de filas de InnoDB se implementan
bloqueando las entradas del índice en el índice . Por lo tanto, las características de la implementación del bloqueo de filas de InnoDB significan:InnoDB solo usa bloqueos a nivel de fila para los datos recuperados a través de condiciones de índice; de ​​lo contrario, InnoDB usará bloqueos de tabla.

  • Los bloqueos a nivel de fila de InnoDB se dividen en cuatro tipos según el alcance del bloqueo:

    记录锁(Record Locks):Bloquear un registro en el índice.
    间隙锁(Gap Locks):Bloquee el valor en el medio del registro de índice o bloquee el valor antes del primer registro de índice o el valor después del último registro de índice.
    临键锁(Next-Key Locks):Es una combinación de un bloqueo de registro en el registro de índice y un bloqueo de espacio antes del registro de índice (bloqueo de espacio + bloqueo de registro).
    插入意向锁(Insert Intention Locks):El bloqueo en la identificación del registro agregado durante la operación de inserción.

  • Los bloqueos a nivel de fila de InnoDB se dividen en dos tipos según sus funciones:

    共享锁(S):Permite que una transacción lea una fila, evitando que otras transacciones obtengan un bloqueo exclusivo en el mismo conjunto de datos.
    排他锁(X):Permita que las transacciones que obtienen bloqueos exclusivos actualicen los datos y evite que otras transacciones obtengan bloqueos de lectura compartidos y bloqueos de escritura exclusivos en el mismo conjunto de datos.

Para UPDATElas declaraciones DELETEy , se agregará automáticamente un bloqueo exclusivo (X) al conjunto de datos involucrado; para la declaración ordinaria, no se agregará ningún bloqueo. La transacción puede agregar bloqueos compartidos o bloqueos exclusivos al conjunto de registros a través de las siguientes declaraciones .INSERTInnoDBSELECTInnoDB

Agregue manualmente bloqueos compartidos:

SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE 

Agregar bloqueo exclusivo (x) manualmente:

SELECT * FROM table_name WHERE ... FOR UPDATE 

2. Bloqueos de registros

Utilice bloqueos de registros al actualizar según los valores de la clave principal.

(1) Bloqueo de registro, solo bloquea una fila del registro de índice y bloquea un único registro de índice.
(2) El bloqueo de registros siempre bloquea el índice, no el registro en sí. Incluso si no hay ningún índice en la tabla, innodb creará un índice de clave primaria agrupada oculta en segundo plano, y es este índice de clave primaria agrupada oculta el que está bloqueado. .

Por lo tanto, cuando una declaración SQL no utiliza ningún índice, se agregará un bloqueo X después de cada índice agregado. Esto es similar a un bloqueo de tabla, pero en principio debería ser completamente diferente de un bloqueo de tabla.

-- 共享记录锁
select * from table where id = 1 lock in share mode; 
-- 排他记录锁
select * from table  where id = 1 for udpate;
Demostración de bloqueo de registros compartidos

Si agrega un bloqueo de registro compartido a un determinado registro, otras sesiones también pueden agregar un bloqueo de registro compartido a este registro y usted también puede leer este registro; pero no puede escribir y no puede agregar un bloqueo exclusivo a este registro ( estar en un estado bloqueado).
Durante la demostración, activamos la transacción para que podamos ver fácilmente el efecto. Si la transacción no está habilitada, el bloqueo se liberará automáticamente después de que se ejecute esta declaración y el efecto será invisible.

Responder 1


-- 1.开启事务
BEGIN;

-- 2.查询操作
SELECT * FROM `mylock`;
-- 3.给id=2的记录添加共享记录锁
select * from mylock where id = 2 lock in share mode; 
-- 4.本地回话可以进行写操作
UPDATE mylock SET name='李四' WHERE id=2
-- 4.1.查询结果,看见更新成功
SELECT * FROM `mylock`;

-- 11.再次查询结果,看见查询的数据和步骤5一致,
--而不是回话2中修改后的,这是正确的保证一个事务中数据不可重复读
SELECT * FROM `mylock`;
-- 12提交事务
COMMIT;
-- 13 再次查询,数据最新的
SELECT * FROM `mylock`;

Responder 2


-- 回话2
-- 5.查询id=2数据成功
select * from mylock where id = 2 
 -- 6.加记录共享锁成功
select * from mylock where id = 2 lock in share mode;
-- 7.加记录排他锁阻塞
select * from mylock where id = 2 for update; 
-- 8.更新id=2的数据阻塞
UPDATE mylock set name='李武' WHERE id =2; 
-- 9.更新其他记录成功
UPDATE mylock set name='李白' WHERE id =3; 
-- 10.查询记录,由于回话一中并没有提交id=2的修改记录,查询到的仍是旧值
SELECT * FROM mylock; 
Responder 1 Responder 2
COMENZAR: – 1. Iniciar transacción
SELECCIONAR * DESDE mylock– 2. Operación de consultaInsertar descripción de la imagen aquí
seleccione * de mylock donde id = 2 bloquear en modo compartido: – 3. Agregue un bloqueo de registro compartido al registro con id = 2Insertar descripción de la imagen aquí
ACTUALIZAR mylock SET name='李思' WHERE id=2 – 4. Las llamadas locales pueden realizar operaciones de escrituraInsertar descripción de la imagen aquí
SELECT * FROM mylock;-- 4.1 Consulta los resultados y comprueba que la actualización es exitosaInsertar descripción de la imagen aquí seleccione * de mylock donde id = 2 – 5. Consulta de datos de id = 2 correctamenteInsertar descripción de la imagen aquí
ninguno seleccione * de mylock donde id = 2 bloqueo en modo compartido: – 6. Agregue el registro de bloqueo compartido con éxitoInsertar descripción de la imagen aquí
ninguno seleccione * de mylock donde id = 2 para actualizar – 7. No se pudo agregar el bloqueo exclusivo de registroInsertar descripción de la imagen aquí
ninguno ACTUALIZAR mylock set name = 'Li Wu' DONDE id = 2; – 8. No se pudieron actualizar los datos con id = 2Insertar descripción de la imagen aquí
ninguno UPDATE mylock set name=‘李白’ WHERE id =3; – 9.更新其他记录成功Insertar descripción de la imagen aquí
SELECT * FROM mylock; – 10.查询记录,由于回话一中并没有提交id=2的修改记录,查询到的仍是旧值Insertar descripción de la imagen aquí
SELECT * FROM mylock; – 11.再次查询结果,看见查询的数据和步骤4.1一致,而不是回话2中修改后的,这是正确的保证一个事务中数据不可重复读Insertar descripción de la imagen aquí
COMMIT;-- 12提交事务 SELECT * FROM mylock; – 13 再次查询发现结果已是最新Insertar descripción de la imagen aquí
排他记录锁演示

对某一条记录添加了排它锁,同一回话中可以写操作。在其他回话中可以读这个条记录,但是不能进行写,不能对这条记录加排它锁或者是共享锁(会处于阻塞状态)

回话一

-- 1.开启事务
BEGIN;
-- 2.查询操作
SELECT * FROM `mylock`;
-- 3.给id=2的记录添加排它锁
select * from mylock where id = 2 for UPDATE; 
-- 4.id=2的记录更新操作成功
UPDATE mylock SET name='张丹' WHERE id=2;

-- 9 提交事务,当前排它锁释放
COMMIT;

回话二

-- 5.加共享锁处于阻塞
select * from mylock where id = 2  lock in share mode;
-- 6.加排它锁锁处于阻塞
select * from mylock where id = 2 for UPDATE; 
-- 7.更新id=2记录处于阻塞
UPDATE mylock SET name='白冰' WHERE id=2;
-- 8.查询id=2的记录成功
SELECT * FROM `mylock` WHERE id=2;
回话1 回话2
– 1.开启事务 BEGIN;-- 2.查询操作 SELECT * FROM mylock;Insertar descripción de la imagen aquí
– 3.给id=2的记录添加排它锁 select * from mylock where id = 2 for UPDATE; Insertar descripción de la imagen aquí
– 4.id=2的记录更新操作成功 UPDATE mylock SET name=‘张丹’ WHERE id=2;Insertar descripción de la imagen aquí
– 5.加共享锁处于阻塞select * from mylock where id = 2 lock in share mode;Insertar descripción de la imagen aquí
– 6.加排它锁锁处于阻塞select * from mylock where id = 2 for UPDATE; Insertar descripción de la imagen aquí
– 7.更新id=2记录处于阻塞UPDATE mylock SET name=‘白冰’ WHERE id=2;Insertar descripción de la imagen aquí
– 8.查询id=2的记录成功SELECT * FROM mylock WHERE id=2;Insertar descripción de la imagen aquí
– 9 提交事务,当前排它锁释放 COMMIT;

3、间隙锁(Gap Locks)

在记录和记录之间的范围区间就是间隙,间隙锁就是加区间之上的,防止插入数据。目的就是防止幻读。

(1)区间锁, 仅仅锁住一个索引区间(开区间,不包括双端端点)。
(2)在索引记录之间的间隙中加锁,或者是在某一条索引记录之前或者之后加锁,并不包括该索引记录 本身。
(3)间隙锁可用于防止幻读,保证索引间的不会被插入数据

Insertar descripción de la imagen aquí

session1: 
begin; 
select * from t1_simple where id > 4 for update; 
--------------------------------------------------------- session2: 
insert into t1_simple values (7,100); --阻塞 
insert into t1_simple values (3,100); --成功

4、临键锁(Next-Key Locks)

就是记录锁+间隙锁
是一个左开右闭的区间。
(1,3],(3,5]…
在InnoDB中默认使用临键锁,当满足某些条件时,临键锁会退化。
(1)record lock + gap lock, 左开右闭区间,例如(5,8]。
(2)默认情况下,innodb使用next-key locks来锁定记录。select … for update
(3)但当查询的索引含有唯一属性的时候,Next-Key Lock 会进行优化,将其降级为Record Lock,即仅锁住索引本身,不是范围。
(4)Next-Key Lock在不同的场景中会退化:
Insertar descripción de la imagen aquí

案例演示
CREATE TABLE `t1_simple`  ( 
`id` int(11) NOT NULL, 
`pubtime` int(11) NULL DEFAULT NULL,
 PRIMARY KEY (`id`) USING BTREE, 
INDEX `idx_pu`(`pubtime`) USING BTREE ) ENGINE = InnoDB; 
INSERT INTO `t1_simple`(`id`, `pubtime`) VALUES (1, 10);
INSERT INTO `t1_simple`(`id`, `pubtime`) VALUES (3, 3);
INSERT INTO `t1_simple`(`id`, `pubtime`) VALUES (5, 100);
INSERT INTO `t1_simple`(`id`, `pubtime`) VALUES (10, 1);
INSERT INTO `t1_simple`(`id`, `pubtime`) VALUES (20, 5);
INSERT INTO `t1_simple`(`id`, `pubtime`) VALUES (100, 20);

Insertar descripción de la imagen aquí

(1) 排他记录锁:使用主键索引精确匹配(=),且记录存在。
session1执行:

begin; 
-- 临键锁区间(10,20],(20,100]
select * from t1_simple where id = 20 for update; 

session2执行:

-- 16和25在临键锁区间(10,20],(20,100]内,因为有临建锁会阻塞
insert into t1_simple values (16, 19); --阻塞 
insert into t1_simple values (25, 50); --阻塞 

-- 退化成排它锁,在其他回话中不能对这条记录加排它锁
select * from t1_simple where id = 20 for update; --阻塞
insert into t1_simple values (4, 101); --成功 

(2)间隙锁: 使用主键索引精确匹配(=),且记录不存在。
Insertar descripción de la imagen aquí
session1执行:

BEGIN;
-- 6在(5,10)之间,锁为间隙锁
SELECT * FROM t1_simple WHERE id =6 FOR UPDATE;

session2执行:


--因为回话一给(5,10)添加了间隙锁。回话二中插入id=6数据时会阻塞
INSERT INTO `t1_simple` VALUES (6, 33);

--成功
INSERT INTO `t1_simple` VALUES (28, 32);

(3)临键锁: 使用主键索引精确匹配(< 和>)
加了临建锁,在范围值内,插入数据会阻塞。
session1执行:

BEGIN;
-- 添加临键锁
SELECT * FROM `t1_simple` WHERE id<=5 FOR UPDATE;

Insertar descripción de la imagen aquí
session2执行:

INSERT INTO `t1_simple` VALUES (2, 33); -- 阻塞

INSERT INTO `t1_simple` VALUES (5, 92); -- 阻塞

INSERT INTO `t1_simple` VALUES (18, 72); -- 成功

5、意向锁 (Intention Locks)(表级锁)

意向锁IX、IS,当数据库中某条记录上有行锁时,需要在表上增加一个标志位,代表当前表中某行记录上有行锁。
通俗的来说:

假如某行记录上有共享锁(S)那么表上标志位会变成意向共享锁(IS)
假如某行记录上有排它锁(X)那么表上标志位会变成意向排它锁(IX)
假如表中不同行记录上有共享锁(S)和排它锁(X)那么表上标志位会变成意向共享锁(IS)和意向排它锁(IX)

总结:

标记当前表中是否有行锁的存在。
行锁:S 意向锁:IS
行锁:X 意向锁:IX

1)意向锁介绍

InnoDB也实现了表级锁,也就是意向锁,意向锁是mysql内部使用的,不需要用户干预。意向锁和行锁可以共存,意向锁的主要作用是为了【全表更新数据】时的性能提升否则在全表更新数据时,需要先检索该范是否某些记录上面有行锁

  1. 表明“某个事务正在某些行持有了锁、或该事务准备去持有锁”
  2. 意向锁的存在是为了协调行锁和表锁的关系,支持多粒度(表锁与行锁)的锁并存。
  3. 例子:事务A修改user表的记录r,会给记录r上一把行级的排他锁(X),同时会给user表上一把意向排他锁(IX),这时事务B要给user表上一个表级的排他锁就会被阻塞。意向锁通过这种方式实现了行锁和表锁共存且满足事务隔离性的要求。
  4. 1)意向共享锁(IS锁):事务在请求S锁前,要先获得IS锁
    2)意向排他锁(IX锁):事务在请求X锁前,要先获得IX锁

2)意向锁的作用

当我们需要给表加一个排他锁时,需要根据意向锁去判断表中有没有数据行被锁定(行锁);
(1)如果意向锁是行锁,则需要遍历每一行数据去确认;
(2)如果意向锁是表锁,则只需要判断一次即可知道有没数据行被锁定,提升性能。

3)意向锁和共享锁、排他锁的兼容关系

下图表示意向锁和表共享锁、表排他锁的兼容关系

Insertar descripción de la imagen aquí
意向锁相互兼容,因为IX、IS只是表明申请更低层次级别元素(比如 page、记录)的X、S操作。
因为上了表级S锁后,不允许其他事务再加X锁,所以表级S锁和X、IX锁不兼容。
上了表级X锁后,会修改数据,所以表级X锁和 IS、IX、S、X(即使是行排他锁,因为表级锁定的行肯定包括行级锁定的行,所以表级X和IX、行级X)不兼容。

注意: 上了行级X锁后,行级X锁不会因为有别的事务上了IX而堵塞,一个mysql是允许多个行级X锁同时存在的,只要他们不是针对相同的数据行。

6、行锁加锁规则

1)主键索引

  1. 等值查询
    (1)命中记录,加记录锁。
    (2)未命中记录,加间隙锁。
  2. 范围查询
    (1)没有命中任何一条记录时,加间隙锁。
    (2)命中1条或者多条,包含where条件的临键区间,加临键锁

2)辅助索引

  1. 等值查询
    (1)命中记录,命中记录的辅助索引项+主键索引项加记录锁,辅助索引项两侧加间隙锁。
    (2)未命中记录,加间隙锁
  2. 范围查询
    (1)没有命中任何一条记录时,加间隙锁。
    (2)命中1条或者多条,包含where条件的临键区间加临键锁。命中记录的id索引项加记录锁。

7、插入意向锁(Insert Intention Locks)

在开启事务之后插入数据之前需要开启插入意向锁(把id锁上),防止其他的回话也插入相同的记录。
(1)插入意向锁是一种Gap锁,不是意向锁,在insert操作时产生。
(2)在多事务同时写入不同数据至同一索引间隙的时候,并不需要等待其他事务完成,不会发生锁等待。
(3)假设有一个记录索引包含键值4和7,不同的事务分别插入5和6,每个事务都会产生一个加在4-7之
间的插入意向锁,获取在插入行上的排它锁,但是不会被互相锁住,因为数据行并不冲突。
(4)插入意向锁不会阻止任何锁,对于插入的记录会持有一个记录锁。

8、锁相关参数

Innodb所使用的行级锁定争用状态查看:

show status like 'innodb_row_lock%';

Insertar descripción de la imagen aquí

  • Innodb_row_lock_current_waits:当前正在等待锁定的数量;
  • Innodb_row_lock_time:从系统启动到现在锁定总时间长度;
  • Innodb_row_lock_time_avg:每次等待所花平均时间;
  • Innodb_row_lock_time_max:从系统启动到现在等待最常的一次所花的时间;
  • Innodb_row_lock_waits:系统启动后到现在总共等待的次数;

对于这5个状态变量,比较重要的主要是:

  • Innodb_row_lock_time_avg(等待平均时长)
  • Innodb_row_lock_waits(等待总次数)
  • Innodb_row_lock_time(等待总时长)这三项。

尤其是当等待次数很高,而且每次等待时长也不小的时候,我们就需要分析系统中为什么会有如此多的等待,然后根据分析结果着手指定优化计划。

查看事务、锁的sql:

select * from information_schema.innodb_locks;
select * from information_schema.innodb_lock_waits;
select * from information_schema.innodb_trx;

六、行锁分析实战

在介绍完一些背景知识之后,接下来将选择几个有代表性的例子,来详细分析MySQL的加锁处理。当然,还是从最简单的例子说起。经常有朋友发给我一个SQL,然后问我,这个SQL加什么锁?就如同下面两条简单的SQL,他们加什么锁?

  • SQL1:
select * from t1 where id = 10;
  • SQL2:
delete from t1 where id = 10;

针对这个问题,该怎么回答?能想象到的一个答案是:

  • SQL1:不加锁。因为MySQL是使用多版本并发控制的,读不加锁。
  • SQL2:对id = 10的记录加写锁 (走主键索引)。

这个答案对吗?说不上来。即可能是正确的,也有可能是错误的,已知条件不足,这个问题没有答案。
必须还要知道以下的一些前提,前提不同,能给出的答案也就不同。要回答这个问题,还缺少哪些前提条件?

前提一: id列是不是主键?
前提二:当前系统的隔离级别是什么?
前提三:id列如果不是主键,那么id列上有索引吗?
前提四:id列上如果有二级索引,那么这个索引是唯一索引吗?
前提五:两个SQL的执行计划是什么?索引扫描?全表扫描?

没有这些前提,直接就给定一条SQL,然后问这个SQL会加什么锁,都是很业余的表现。而当这些问题有了明确的答案之后,给定的SQL会加什么锁,也就一目了然。下面,我们将这些问题的答案进行组合,然后按照从易到难的顺序,逐个分析每种组合下,对应的SQL会加哪些锁?

注:下面的这些组合,需要做一个前提假设,也就是有索引时,执行计划一定会选择使用索引进行过滤 (索引扫描)。但实际情况会复杂很多,真正的执行计划,还是需要根据MySQL输出的为准。

组合一:id列是主键,RC隔离级别
组合二:id列是二级唯一索引,RC隔离级别
组合三:id列是二级非唯一索引,RC隔离级别
组合四:id列上没有索引,RC隔离级别
组合五:id列是主键,RR隔离级别
组合六:id列是二级唯一索引,RR隔离级别
组合七:id列是二级非唯一索引,RR隔离级别
组合八:id列上没有索引,RR隔离级别
组合九:Serializable隔离级别

排列组合还没有列举完全,但是看起来,已经很多了。真的有必要这么复杂吗?事实上,要分析加锁,就是需要这么复杂。但是从另一个角度来说,只要你选定了一种组合,SQL需要加哪些锁,其实也就确定了。接下来,就让我们来逐个分析这9种组合下的SQL加锁策略。

注:在前面八种组合下,也就是RC,RR隔离级别下SQL1:select操作均不加锁,采用的是快照读,因此在下面的讨论中就忽略了,主要讨论SQL2:delete操作的加锁。

1)组合一:id主键+RC

分析

这个组合,是最简单,最容易分析的组合。id是主键Read Committed隔离级别,给定SQL:delete from t1 where id = 10; 只需要将主键上id = 10的记录加上X锁即可。如下图所示:

Insertar descripción de la imagen aquí

结论

id是主键时,此SQL只需要在id=10这条记录上加X锁即可。

2)组合二:id唯一索引+RC

分析

这个组合,id不是主键,而是一个Unique的二级索引键值。那么在RC隔离级别下,delete from t1 where id = 10; 需要加什么锁呢?见下图:
Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí

此组合中,id是unique索引,而主键是name列。此时,加锁的情况由于组合一有所不同。由于id是 unique索引,因此delete语句会选择走id列的索引进行where条件的过滤,在找到id=10的记录后:
首先 会将unique索引上的id=10索引记录加上X锁,同时,会根据读取到的name列,回主键索引(聚簇索引);
然后将聚簇索引上的name = ‘d’ 对应的主键索引项加X锁

为什么聚簇索引上的记录也要加锁?
试想一下,如果并发的一个SQL,是通过主键索引来更新:update t1 set id = 100 where name = ‘d’; 此时,如果delete语句没有将主键索引上的记录加锁,那么并发的update就会感知不到delete语句的存在,违背了同一记录上的更新/删除需要串行执行的约束。

结论

若id列是unique列,其上有unique索引。那么SQL需要加两个X锁,一个对应于id unique索引上的id = 10的记录,另一把锁对应于聚簇索引上的【name=’d’,id=10】的记录。

3)组合三:id非唯一索引+RC

分析

相对于组合一、二,组合三又发生了变化,隔离级别仍旧是RC不变,但是id列上的约束又降低了,id列不再唯一,只有一个普通的索引。假设delete from t1 where id = 10; 语句,仍旧选择id列上的索引进行
过滤where条件
,那么此时会持有哪些锁?同样见下图:
Insertar descripción de la imagen aquí

根据此图,可以看到,首先,id列索引上,满足id = 10查询条件的记录,均已加锁。同时,这些记录对
应的主键索引上的记录也都加上了锁。与组合二唯一的区别在于,组合二最多只有一个满足等值查询的
记录,而组合三会将所有满足查询条件的记录都加锁。

结论:

若id列上有非唯一索引,那么对应的所有满足SQL查询条件的记录,都会被加锁。同时,这些记录在主键
索引上的记录,也会被加锁。

4)组合四:id无索引+RC

分析

相对于前面三个组合,这是一个比较特殊的情况。id列上没有索引where id = 10; 这个过滤条件,没法通过索引进行过滤,那么只能走全表扫描做过滤。
对应于这个组合,SQL会加什么锁?或者是换句话说,全表扫描时,会加什么锁?
这个答案也有很多: 有人说会在表上加X锁;有人说会将聚簇索引上,选择出来的id = 10;的记录加上X锁。
那么实际情况呢? 请看下图:
Insertar descripción de la imagen aquí
由于id列上没有索引,因此只能走聚簇索引,进行全部扫描。从图中可以看到,满足删除条件的记录有 两条,但是,聚簇索引上所有的记录,都被加上了X锁。无论记录是否满足条件,全部被加上X锁。既不 是加表锁,也不是在满足条件的记录上加行锁。

有人可能会问?为什么不是只在满足条件的记录上加锁呢?
这是由于MySQL的实现决定的。如果一个条 件无法通过索引快速过滤,那么存储引擎层面就会将所有记录加锁后返回,然后由MySQL Server层进行 过滤。因此也就把所有的记录,都锁上了

注:在实际的实现中,MySQL有一些改进,在MySQL Server过滤条件,发现不满足后,会调用 unlock_row方法,把不满足条件的记录放锁 (违背了2PL的约束)。这样做,保证了最后只会持有满 足条件记录上的锁,但是每条记录的加锁操作还是不能省略的。

结论:

id列上没有索引,SQL会走聚簇索引的全扫描进行过滤,由于过滤是由MySQL Server层面进行的。因此每条记录,无论是否满足条件,都会被加上X锁。但是,为了效率考量,MySQL做了优化,对于不满足条件的记录,会在判断后放锁,最终持有的,是满足条件的记录上的锁,但是不满足条件的记录上的加锁/放 锁动作不会省略。同时,优化也违背了2PL的约束。

5)组合五:id主键+RR

上面的四个组合,都是在Read Committed隔离级别下的加锁行为,接下来的四个组合,是在 Repeatable Read隔离级别下的加锁行为。
组合五,id列是主键列Repeatable Read隔离级别,针对delete from t1 where id = 10; 这条SQL,加锁与组合一:[id主键,Read Committed] 一致。

6)组合六:id唯一索引+RR

与组合五类似,组合六的加锁,与组合二:[id唯一索引,Read Committed] 一致。两个X锁,id唯一索引满足条件的记录上一个,对应的聚簇索引上的记录一个。

7)组合七:id非唯一索引+RR

分析

还记得前面提到的MySQL的四种隔离级别的区别吗?RC隔离级别允许幻读,而RR隔离级别,不允许存在幻读。但是在组合五、组合六中,加锁行为又是与RC下的加锁行为完全一致。那么RR隔离级别下,如 何防止幻读呢?问题的答案,就在组合七中揭晓。
组合七,Repeatable Read隔离级别id上有一个非唯一索引,执行delete from t1 where id = 10; 假设选择id列上的索引进行条件过滤,最后的加锁行为,是怎么样的呢?同样看下面这幅图:
Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí
此图,相对于组合三:[id列上非唯一锁,Read Committed] 看似相同,其实却有很大的区别。最大的区别在于,这幅图中多了一个GAP锁,而且GAP锁看起来也不是加在记录上的,倒像是加载两条记录之间 的位置,GAP锁有何用?
其实这个多出来的GAP锁,就是RR隔离级别,相对于RC隔离级别,不会出现幻读的关键。确实,GAP锁锁住的位置,也不是记录本身,而是两条记录之间的GAP。所谓幻读,就是同一个事务,连续做两次当 前读 (例如:select * from t1 where id = 10 for update;),那么这两次当前读返回的是完全相同的记录 (记录数量一致,记录本身也一致),第二次的当前读,不会比第一次返回更多的记录 (幻象)。

如何保证两次当前读返回一致的记录,那就需要在第一次当前读与第二次当前读之间,其他的事务不会 插入新的满足条件的记录并提交。为了实现这个功能,GAP锁应运而生。

如图中所示,有哪些位置可以插入新的满足条件的项 (id = 10),考虑到B+树索引的有序性,满足条件的项一定是连续存放的。记录[6,c]之前,不会插入id=10的记录;[6,c]与[10,b]间可以插入[10, aa];[10,b] 与[10,d]间,可以插入新的[10,bb],[10,c]等;[10,d]与[11,f]间可以插入满足条件的[10,e],[10,z]等;而 [11,f]之后也不会插入满足条件的记录。因此,为了保证[6,c]与[10,b]间,[10,b]与[10,d]间,[10,d]与 [11,f]不会插入新的满足条件的记录,MySQL选择了用GAP锁,将这三个GAP给锁起来。

Insert操作,如insert [10,aa],首先会定位到[6,c]与[10,b]间,然后在插入前,会检查这个GAP是否已经 被锁上,如果被锁上,则Insert不能插入记录。因此,通过第一遍的当前读,不仅将满足条件的记录锁 上 (X锁),与组合三类似。同时还是增加3把GAP锁,将可能插入满足条件记录的3个GAP给锁上,保证后 续的Insert不能插入新的id=10的记录,也就杜绝了同一事务的第二次当前读,出现幻象的情况。

有心的朋友看到这儿,可以会问:既然防止幻读,需要靠GAP锁的保护,为什么组合五、组合六,也是 RR隔离级别,却不需要加GAP锁呢?
首先,这是一个好问题。其次,回答这个问题,也很简单。GAP锁的目的,是为了防止同一事务的两次 当前读,出现幻读的情况。而组合五,id是主键;组合六,id是unique键,都能够保证唯一性。一个等值查询,最多只能返回一条记录,而且新的相同取值的记录,一定不会在新插入进来,因此也就避免了 GAP锁的使用。其实,针对此问题,还有一个更深入的问题:如果组合五、组合六下,针对SQL:select * from t1 where id = 10 for update; 第一次查询,没有找到满足查询条件的记录,那么GAP锁是否还能 够省略?此问题留给大家思考。

结论:

Repeatable Read隔离级别下,id列上有一个非唯一索引,对应SQL:delete from t1 where id = 10; 首先,通过id索引定位到第一条满足查询条件的记录,加记录上的X锁,加GAP上的GAP锁,然后加主键聚簇索引上的记录X锁,然后返回;然后读取下一条,重复进行。直至进行到第一条不满足条件的记录[11,f],此时,不需要加记录X锁,但是仍旧需要加GAP锁,最后返回结束。

8)组合八:id无索引+RR

分析

组合八,Repeatable Read隔离级别下的最后一种情况,id列上没有索引。此时SQL:delete from t1 where id = 10; 没有其他的路径可以选择,只能进行全表扫描。最终的加锁情况,如下图所示:
Insertar descripción de la imagen aquí
如图,这是一个很恐怖的现象。首先,聚簇索引上的所有记录,都被加上了X锁。其次,聚簇索引每条记录间的间隙(GAP),也同时被加上了GAP锁。这个示例表,只有6条记录,一共需要6个记录锁,7个GAP 锁。试想,如果表上有1000万条记录呢?

在这种情况下,这个表上,除了不加锁的快照度,其他任何加锁的并发SQL,均不能执行,不能更新, 不能删除,不能插入,全表被锁死。
当然,跟组合四:[id无索引, Read Committed]类似,这个情况下,MySQL也做了一些优化,就是所谓的semi-consistent readsemi-consistent read开启的情况下,对于不满足查询条件的记录,MySQL 会提前放锁。针对上面的这个用例,就是除了记录[d,10],[g,10]之外,所有的记录锁都会被释放,同时 不加GAP锁。semi-consistent read如何触发:要么是read committed隔离级别;要么是Repeatable Read隔离级别,同时设置了 innodb_locks_unsafe_for_binlog 参数。

结论:

Repeatable Read隔离级别下,如果进行全表扫描的当前读,那么会锁上表中的所有记录,同时会锁上聚簇索引内的所有GAP,杜绝所有的并发 更新/删除/插入 操作。当然,也可以通过触发semi- consistent read,来缓解加锁开销与并发影响,但是semi-consistent read本身也会带来其他问 题,不建议使用。

9)组合九:Serializable

分析

针对前面提到的简单的SQL,最后一个情况:Serializable隔离级别。对于SQL2来说,Serializable隔离 级别与Repeatable Read隔离级别完全一致,因此不做介绍。

delete from t1 where id = 10

Serializable隔离级别,影响的是SQL1这条SQL

select * from t1 where id = 10 

RC,RR隔离级别下,都是快照读,不加锁。但是在Serializable隔离级别,SQL1会加读锁,也就是说 快照读不复存在,MVCC并发控制降级为Lock-Based CC。

结论:

MySQL/InnoDB中,所谓的读不加锁,并不适用于所有的情况,而是隔离级别相关的。Serializable 隔离级别,读不加锁就不再成立,所有的读操作,都是当前读。

七、一条复杂SQL的加锁分析

分析

写到这里,其实MySQL的加锁实现也已经介绍的八八九九。只要将本文上面的分析思路,大部分的 SQL,都能分析出其会加哪些锁。而这里,再来看一个稍微复杂点的SQL,用于说明MySQL加锁的另外 一个逻辑。SQL用例如下:
Insertar descripción de la imagen aquí
如图中的SQL,会加什么锁?假定在Repeatable Read隔离级别下 (Read Committed隔离级别下的加锁 情况,留给学生们分析),同时,假设SQL走的是idx_t1_pu索引。
在详细分析这条SQL的加锁情况前,还需要有一个知识储备,那就是一个SQL中的where条件如何拆分? 在这里,我直接给出分析后的结果:

  • Index key: pubtime > 1 and puptime < 20。此条件,用于确定SQL在idx_t1_pu索引上的查询范围。
  • Index Filter: userid = ‘hdc’ 。此条件,可以在idx_t1_pu索引上进行过滤,但不属于Index Key
  • Table Filter:comment is not NULL。此条件,在idx_t1_pu索引上无法过滤,只能在聚簇索引上过滤。

在分析出SQL where条件的构成之后,再来看看这条SQL的加锁情况 (RR隔离级别),如下图所示:
Insertar descripción de la imagen aquí
从图中可以看出,在Repeatable Read隔离级别下,由Index Key所确定的范围,被加上了GAP锁; Index Filter锁给定的条件 **(userid = ‘hdc’)**何时过滤,视MySQL的版本而定,在MySQL 5.6版本之前,不 支持Index Condition Pushdown(ICP),因此Index Filter在MySQL Server层过滤,在5.6后支持了Index Condition Pushdown,则在index上过滤。若不支持ICP,不满足Index Filter的记录,也需要加上记录X 锁;若支持ICP,则不满足Index Filter的记录,无需加记录X锁 (图中,用红色箭头标出的X锁,是否要加,视是否支持ICP而定);而Table Filter对应的过滤条件,则在聚簇索引中读取后,在MySQL Server层 面过滤,因此聚簇索引上也需要X锁。最后,选取出了一条满足条件的记录[8,hdc,d,5,good],但是加锁的数量,要远远大于满足条件的记录数量。

结论:

在Repeatable Read隔离级别下,针对一个复杂的SQL,首先需要提取其where条件。

  • Index Key确定的范围,需要加上GAP锁;
  • Index Filter过滤条件,视MySQL版本是否支持ICP,若支持ICP,则不满足Index Filter的记 录,不加X锁,否则需要X锁;
  • Table Filter过滤条件,无论是否满足,都需要加X锁。

八、死锁原理与分析

Insertar descripción de la imagen aquí

本文前面的部分,基本上已经涵盖了MySQL/InnoDB所有的加锁规则。深入理解MySQL如何加锁,有两个比较重要的作用:

  • 可以根据MySQL的加锁规则,写出不会发生死锁的SQL;
  • 可以根据MySQL的加锁规则,定位出线上产生死锁的原因;

下面,来看看两个死锁的例子 (一个是两个Session的两条SQL产生死锁;另一个是两个Session的一条 SQL,产生死锁):
Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí
上面的两个死锁用例。第一个非常好理解,也是最常见的死锁,每个事务执行两条SQL,分别持有了一 把锁,然后加另一把锁,产生死锁。

第二个用例,虽然每个Session都只有一条语句,仍旧会产生死锁。要分析这个死锁,首先必须用到本文 前面提到的MySQL加锁的规则。针对Session 1,从name索引出发,读到的[hdc, 1],[hdc, 6]均满足条 件,不仅会加name索引上的记录X锁,而且会加聚簇索引上的记录X锁,加锁顺序为先[1,hdc,100],后 [6,hdc,10]。而Session 2,从pubtime索引出发,[10,6],[100,1]均满足过滤条件,同样也会加聚簇索引 上的记录X锁,加锁顺序为[6,hdc,10],后[1,hdc,100]。发现没有,跟Session 1的加锁顺序正好相反, 如果两个Session恰好都持有了第一把锁,请求加第二把锁,死锁就发生了。

结论:

Si se produce un punto muerto o no, no depende de cuántas sentencias SQL haya en la transacción. [La clave del punto muerto es]: el [orden de bloqueo] de dos (o más) sesiones es inconsistente. Usando el método mencionado anteriormente en este artículo, analice las reglas de bloqueo de cada declaración SQL en MySQL, analice el orden de bloqueo de cada declaración y luego verifique si hay bloqueos en el orden opuesto entre múltiples SQL concurrentes, puede analizar Varios puntos muertos potenciales Se pueden identificar situaciones y también se pueden analizar las causas de los bloqueos en línea.

¿Cómo evitar un punto muerto?

De forma predeterminada, MySQL detectará activamente interbloqueos y revertirá una transacción con un impacto mínimo. Espere a que se complete la ejecución de otra transacción y luego vuelva a ejecutar la transacción.

Cómo evitar un punto muerto

1. Presta atención a la lógica del programa.

La razón fundamental es el orden de la lógica del programa, la más común es la actualización cruzada
Transacción 1: Actualizar tabla A -> Actualizar tabla B
Transacción 2: Actualizar tabla B -> Actualizar tabla A
La transacción obtiene dos recursos

2. Mantenga las transacciones ligeras

Cuanto más ligera es la transacción, menos recursos de bloqueo ocupa, por lo que la posibilidad de que se produzca un punto muerto es menor.

3. Mejorar la velocidad de carrera

Evite el uso de subconsultas, intente utilizar claves primarias, etc.

4. Envíe las transacciones lo más rápido posible para reducir el tiempo de retención de los bloqueos.

Cuanto antes se confirme la transacción, antes se liberará el bloqueo.

Nueve enlaces excelentes

1. Cómo obtener bloqueos MySQL (bloqueos globales, bloqueos a nivel de tabla, bloqueos a nivel de fila)
2. Base de datos MySQL: mecanismo de bloqueo
3. Resumen de preguntas comunes de entrevistas en la base de datos MySQL

Artículo anterior: Índice, motor de almacenamiento (tipo de índice, estructura de datos de índice, motor de almacenamiento MyISAM e InnoDB, índice de clave principal, índice auxiliar, índice combinado, índice de cobertura, inserción de índice bajo el motor de almacenamiento innodb

Supongo que te gusta

Origin blog.csdn.net/weixin_43811057/article/details/130328730
Recomendado
Clasificación