---TRANSACTION 929632, ACTIVE 27 sec 2 lock struct(s), heap size 1136, 1 row lock(s), undo log entries 1 MySQL thread id 1309, OS thread handle 123145430310912, query id 9179 localhost root TABLE LOCK table `test`.`id_pk_rc` trx id 929632 lock mode IX RECORD LOCKS space id 1813 page no 3 n bits 72 index PRIMARY of table `test`.`id_pk_rc` trx id 929632 lock_mode X locks rec but not gap Record lock, heap no 4 PHYSICAL RECORD: n_fields 4; compact format; info bits 32 0: len 4; hex 80000005; asc ;; 1: len 6; hex 0000000e2f60; asc /`;; 2: len 7; hex 4c000002222e83; asc L ". ;; 3: len 1; hex 63; asc c;;
“page no 3 n bits 72” 代表在第 3 页的记录上,lock bitmap 共 72 位
这个记录里没法直接看出锁住了哪些记录。一种方法是通过 select * from information_schema.innodb_locks \G; 查看抢锁没抢到的信息,为了查看记录,在测试时可以另开一个会话,用诸如 SELECT * FROM ... WHERE ... FOR UPDATE 来抢锁,这样就可以看出锁在哪个记录上了。样例输出:
-- 设置为 RC 隔离级别 SET TRANSACTION ISOLATION LEVEL READ COMMITTED; BEGIN; -- 开启事务 DELETEFROM id_pk_rc WHERE id =5; -- 先不结束事务,验证 Monitor Output 再用 ROLLBACK; 回滚
Monitor 输出日志:
---TRANSACTION 929632, ACTIVE 27 sec 2 lock struct(s), heap size 1136, 1 row lock(s), undo log entries 1 MySQL thread id 1309, OS thread handle 123145430310912, query id 9179 localhost root TABLE LOCK table `test`.`id_pk_rc` trx id 929632 lock mode IX RECORD LOCKS space id 1813 page no 3 n bits 72 index PRIMARY of table `test`.`id_pk_rc` trx id 929632 lock_mode X locks rec but not gap Record lock, heap no 4 PHYSICAL RECORD: n_fields 4; compact format; info bits 32 0: len 4; hex 80000005; asc ;; 1: len 6; hex 0000000e2f60; asc /`;; 2: len 7; hex 4c000002222e83; asc L ". ;; 3: len 1; hex 63; asc c;;
看到输出里有 lock_mode X locks rec but not gap,可以确定持有的是记录锁。
唯一索引 + RC
结论:索引和聚簇索引/主键中都对 ID = 5 加 Record Lock
首先建表准备数据:
-- 建表 CREATETABLE id_ui_rc(pk intprimary key, id int, name varchar(32)); CREATEUNIQUE INDEX id_ui ON id_ui_rc(id);
-- 设置为 RC 隔离级别 SET TRANSACTION ISOLATION LEVEL READ COMMITTED; BEGIN; -- 开启事务 DELETEFROM id_ui_rc WHERE id =5; -- 先不结束事务,验证 Monitor Output 再用 ROLLBACK; 回滚
Monitor 输出日志:
---TRANSACTION 929694, ACTIVE 6 sec 3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1 MySQL thread id 1309, OS thread handle 123145430310912, query id 9241 localhost root TABLE LOCK table `test`.`id_ui_rc` trx id 929694 lock mode IX RECORD LOCKS space id 1815 page no 4 n bits 72 index id_ui of table `test`.`id_ui_rc` trx id 929694 lock_mode X locks rec but not gap Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 32 0: len 4; hex 80000005; asc ;; 1: len 4; hex 80000003; asc ;;
RECORD LOCKS space id 1815 page no 3 n bits 72 index PRIMARY of table `test`.`id_ui_rc` trx id 929694 lock_mode X locks rec but not gap Record lock, heap no 4 PHYSICAL RECORD: n_fields 5; compact format; info bits 32 0: len 4; hex 80000003; asc ;; 1: len 6; hex 0000000e2f9e; asc / ;; 2: len 7; hex 7a0000059525c9; asc z % ;; 3: len 4; hex 80000005; asc ;; 4: len 1; hex 63; asc c;;
可以看到分别对 index id_ui 和 index PRIMARY 加了 Record Lock。
非唯一索引 + RC
结论:会对所有 ID = 5 的索引记录加 Record Lock,同时对主键加 Record Lock。
首先建表准备数据:
-- 建表 CREATETABLE id_si_rc(pk intprimary key, id int, name varchar(32)); CREATE INDEX id_si ON id_si_rc(id);
-- 设置为 RC 隔离级别 SET TRANSACTION ISOLATION LEVEL READ COMMITTED; BEGIN; -- 开启事务 DELETEFROM id_si_rc WHERE id =5; -- 先不结束事务,验证 Monitor Output 再用 ROLLBACK; 回滚
Monitor 输出日志(省略了 PHYSICAL RECORD 的内容):
---TRANSACTION 929779, ACTIVE 3 sec 3 lock struct(s), heap size 1136, 4 row lock(s), undo log entries 2 MySQL thread id 1309, OS thread handle 123145430310912, query id 9325 localhost root TABLE LOCK table `test`.`id_si_rc` trx id 929779 lock mode IX RECORD LOCKS space id 1817 page no 4 n bits 72 index id_si of table `test`.`id_si_rc` trx id 929779 lock_mode X locks rec but not gap Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 32 ... Record lock, heap no 6 PHYSICAL RECORD: n_fields 2; compact format; info bits 32 ...
RECORD LOCKS space id 1817 page no 3 n bits 72 index PRIMARY of table `test`.`id_si_rc` trx id 929779 lock_mode X locks rec but not gap Record lock, heap no 4 PHYSICAL RECORD: n_fields 5; compact format; info bits 32 ... Record lock, heap no 6 PHYSICAL RECORD: n_fields 5; compact format; info bits 32 ...
可以看到一共有 4 条记录,首先可以看到索引 id_si 和 PRIMARY 分别锁住了两条记录,加的锁都是 X Record Lock No Gap,也就是记录锁。我们通过 select * from information_schema.innodb_locks \G; 查看是锁住了 3, 5 这两条记录。
-- 设置为 RC 隔离级别 SET TRANSACTION ISOLATION LEVEL READ COMMITTED; BEGIN; -- 开启事务 DELETEFROM id_ni_rc WHERE id =5; -- 先不结束事务,验证 Monitor Output 再用 ROLLBACK; 回滚
Monitor 输出日志(省略了 PHYSICAL RECORD 的内容):
---TRANSACTION 1446, ACTIVE 17 sec 2 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 2 MySQL thread id 7, OS thread handle 123145446559744, query id 267 localhost root TABLE LOCK table `test`.`id_ni_rc` trx id 1446 lock mode IX RECORD LOCKS space id 27 page no 3 n bits 72 index PRIMARY of table `test`.`id_ni_rc` trx id 1446 lock_mode X locks rec but not gap Record lock, heap no 4 PHYSICAL RECORD: n_fields 5; compact format; info bits 32 ... Record lock, heap no 6 PHYSICAL RECORD: n_fields 5; compact format; info bits 32 ...
-- 设置为 RC 隔离级别 SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; BEGIN; -- 开启事务 DELETEFROM id_si_rr WHERE id =5; -- 先不结束事务,验证 Monitor Output 再用 ROLLBACK; 回滚
Monitor 输出日志(省略 PHYSICAL RECORD 的内容):
---TRANSACTION 929891, ACTIVE 6 sec 4 lock struct(s), heap size 1136, 5 row lock(s), undo log entries 2 MySQL thread id 1309, OS thread handle 123145430310912, query id 9442 localhost root TABLE LOCK table `test`.`id_si_rr` trx id 929891 lock mode IX RECORD LOCKS space id 1820 page no 4 n bits 72 index id_si of table `test`.`id_si_rr` trx id 929891 lock_mode X Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 32 ... Record lock, heap no 6 PHYSICAL RECORD: n_fields 2; compact format; info bits 32 ...
RECORD LOCKS space id 1820 page no 3 n bits 72 index PRIMARY of table `test`.`id_si_rr` trx id 929891 lock_mode X locks rec but not gap Record lock, heap no 4 PHYSICAL RECORD: n_fields 5; compact format; info bits 32 ... Record lock, heap no 6 PHYSICAL RECORD: n_fields 5; compact format; info bits 32 ...
RECORD LOCKS space id 1820 page no 4 n bits 72 index id_si of table `test`.`id_si_rr` trx id 929891 lock_mode X locks gap before rec Record lock, heap no 5 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 ...
-- 设置为 RC 隔离级别 SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; BEGIN; -- 开启事务 DELETEFROM id_ni_rr WHERE id =5; -- 先不结束事务,验证 Monitor Output 再用 ROLLBACK; 回滚
Monitor 输出日志(省略了部分信息):
---TRANSACTION 929980, ACTIVE 5 sec 2 lock struct(s), heap size 1136, 6 row lock(s), undo log entries 2 MySQL thread id 1309, OS thread handle 123145430310912, query id 9529 localhost root TABLE LOCK table `test`.`id_ni_rr` trx id 929980 lock mode IX RECORD LOCKS space id 1822 page no 3 n bits 72 index PRIMARY of table `test`.`id_ni_rr` trx id 929980 lock_mode X Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0 0: len 8; hex 73757072656d756d; asc supremum;;
Record lock, heap no 2 PHYSICAL RECORD: n_fields 5; compact format; info bits 0 ... Record lock, heap no 3 PHYSICAL RECORD: n_fields 5; compact format; info bits 0 ... Record lock, heap no 4 PHYSICAL RECORD: n_fields 5; compact format; info bits 32 ... Record lock, heap no 5 PHYSICAL RECORD: n_fields 5; compact format; info bits 0 ... Record lock, heap no 6 PHYSICAL RECORD: n_fields 5; compact format; info bits 32 ...
首先看到 TABLE LOCK 的状态是 IX 说明没有加表锁。同时看到锁住了 heap no 2~6的记录,对应数据库中的 5 条记录。另外这里的锁是 Next Key Lock,加上 heap no 为 1
的 “supremum” 记录的 gap lock,锁住了所有已经存在和不存在的行。因此如果执行
SELECT * FROM id_ni_rc WHERE id = 0 FOR UPDATE 也会阻塞,尽管 0 记录不在数据库中。
死锁验证
死锁与获取锁的顺序有关,一条语句(如 INSERT、DELETE)中对不同行、不同索引的加锁存在先后,因此不同事务内的语句执行时,有可能产生死锁。常见死锁原因(摘自
MySQL InnoDB锁和死锁):
Primary key 和 Secondary index,通过 primary key 找到记录,更新 Secondary
index 字段与通过 Secondary index 更新记录
样例情形:
首先建表准备数据:
CREATETABLE deadlock(id intprimary key, name varchar(32), reg int); CREATE INDEX deadlock_name ON deadlock(name); CREATE INDEX deadlock_reg ON deadlock(reg);
------------------------ LATEST DETECTED DEADLOCK ------------------------ 2020-12-13 15:59:40 0x700007a56000 *** (1) TRANSACTION: TRANSACTION 930064, ACTIVE 0 sec starting index read mysql tables in use 1, locked 1 LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s) MySQL thread id 1309, OS thread handle 123145430310912, query id 9616 localhost root updating DELETE FROM deadlock WHERE name = 'x' *** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 1825 page no 3 n bits 72 index PRIMARY of table `test`.`deadlock` trx id 930064 lock_mode X locks rec but not gap waiting Record lock, heap no 2 PHYSICAL RECORD: n_fields 5; compact format; info bits 32 ...
*** (2) TRANSACTION: TRANSACTION 930063, ACTIVE 0 sec updating or deleting mysql tables in use 1, locked 1 3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1 MySQL thread id 1308, OS thread handle 123145430589440, query id 9615 localhost root updating DELETE FROM deadlock WHERE reg >= 2 *** (2) HOLDS THE LOCK(S): RECORD LOCKS space id 1825 page no 3 n bits 72 index PRIMARY of table `test`.`deadlock` trx id 930063 lock_mode X Record lock, heap no 2 PHYSICAL RECORD: n_fields 5; compact format; info bits 32 ...
*** (2) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 1825 page no 4 n bits 72 index deadlock_name of table `test`.`deadlock` trx id 930063 lock_mode X locks rec but not gap waiting Record lock, heap no 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 ...
*** WE ROLL BACK TRANSACTION (1)
我们看到:
第一个事务在等待 PRIMARY 索引上 heap_no = 2 的记录的 Record Lock
第二个事务已经取得 PRIMARY 索引上 heap_no = 2 的 Next Key Lock
同时第二个事务在等待 deadlock_name 索引上 heap_no = 2 的 Record Lock