DROP TABLE IF EXISTS `t`; CREATE TABLE `t` ( `id` int NOT NULL, `b` int DEFAULT NULL, `c` int DEFAULT NULL, `d` int DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_b` (`b`) USING BTREE, KEY `c` (`c`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; // 堆代码 duidaima.com // 插入数据 insert into t values(0,0,0,0),(5,5,5,5),(10,10,10,10),(15,15,15,15),(20,20,20,20),(25,25,25,25);
下图中 LOCK_MODE:IX 就代表着意向写锁,LOCK_TYPE:TABLE 代表着表级锁。
行级锁
MySQL 的行级锁是在引擎层由各个引擎实现。但并不是所有的引擎都支持行级锁,比如 MyISAM 引擎就不支持行级锁,InnoDB 支持行级锁,这也是 MyISAM 被 InnoDB 替代的重要原因之一。
演示
RR 隔离级别下开启事物,执行 select id from t where c = 10 for update 语句后,查看当前加锁情况。如下图所示:
'IX' 代表表级意向写锁,'X' 代表临键写锁,'X,REC_NOT_GAP' 代表记录写锁,'X,GAP' 代表间隙写锁。
加锁规则
RR 隔离级别下:.若其他线程并发插入同一索引位置时,若当前位置是普通索引,在该记录上加一把X锁;若当前位置是唯一索引,则会给冲突的索引记录添加S锁。
.和 RR 类似,没有间隙锁和临键锁,查询过程中访问到的对象才会加锁,加锁的基本单位为记录锁,语句执行完就释放“不满足条件的行”的记录锁,“满足条件的行”的记录锁才在事务结束时才释放。
MySQL 中不同的锁之间存在兼容互斥关系,如果线程 1 中需要的锁资源 C 和线程 2 中拥有的锁资源 B 互斥,线程 1 就会阻塞等待线程 2 释放锁 B ;线程 2 需要的锁资源 D 又和线程 1 拥有的锁资源 A 互斥,线程 2 会阻塞等待线程 1 释放锁 A ,导致互相等待对方锁资源释放,这个现象就是死锁。
通过 deadlock 关键词条搜索线上日志,相关日志如上图所示,其中会打印出相关的sql语句,可以很容易定位到程序中的代码位置。
偶尔的死锁并不用担心,可以使用 SELECT `count` FROM INFORMATION_SCHEMA.INNODB_METRICS WHERE NAME="lock_deadlocks" 命令查看数据库死锁发生的次数,如果出现次数特别多,就需要排查下是否是程序代码的问题;
在 MySQL 管理台上执行 SHOW ENGINE INNODB STATUS 命令可以查看最后一次发生死锁时的日志,Status 字段中就是日志。
查看死锁日志命令只能看到最近一次死锁日志,你想看的死锁日志可能被其他业务死锁覆盖,你可以打开innodb_print_all_deadlocks 配置,会记录所有死锁日志,排查好后再关闭该配置。
------------------------ LATEST DETECTED DEADLOCK ------------------------ 2023-11-08 21:08:08 0x16c56f000 *** (1) TRANSACTION: TRANSACTION 3466, ACTIVE 341 sec starting index read mysql tables in use 1, locked 1 LOCK WAIT 3 lock struct(s), heap size 1128, 2 row lock(s), undo log entries 1 MySQL thread id 59, OS thread handle 6131134464, query id 5744 localhost 127.0.0.1 root updating UPDATE t set status=5 where id = 5 *** (1) HOLDS THE LOCK(S): RECORD LOCKS space id 18 page no 4 n bits 88 index PRIMARY of table `test`.`t` trx id 3466 lock_mode X locks rec but not gap Record lock, heap no 10 PHYSICAL RECORD: n_fields 13; compact format; info bits 64 0: len 4; hex 80000000; asc ;; 1: len 6; hex 000000000d8a; asc ;; 2: len 7; hex 02000001160511; asc ;; 3: len 4; hex 80000000; asc ;; 4: len 4; hex 80000000; asc ;; 5: len 4; hex 80000000; asc ;; 6: len 30; hex 80000005000d400058002880000005000000000d80010000011908ba8000; asc @ X ( ; (total 4294967291 bytes); 7: len 30; hex 80000005000d400058002880000005000000000d80010000011908ba8000; asc @ X ( ; (total 4294967291 bytes); 8: len 30; hex 80000005000d400058002880000005000000000d80010000011908ba8000; asc @ X ( ; (total 4294967291 bytes); 9: len 30; hex 80000005000d400058002880000005000000000d80010000011908ba8000; asc @ X ( ; (total 4294967291 bytes); 10: len 30; hex 80000005000d400058002880000005000000000d80010000011908ba8000; asc @ X ( ; (total 4294967291 bytes); 11: len 30; hex 80000005000d400058002880000005000000000d80010000011908ba8000; asc @ X ( ; (total 4294967291 bytes); 12: len 4; hex 80000005; asc ;; *** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 18 page no 4 n bits 88 index PRIMARY of table `test`.`t` trx id 3466 lock_mode X locks rec but not gap waiting Record lock, heap no 11 PHYSICAL RECORD: n_fields 13; compact format; info bits 64 0: len 4; hex 80000005; asc ;; 1: len 6; hex 000000000d80; asc ;; 2: len 7; hex 010000011908ba; asc ;; 3: len 4; hex 80000005; asc ;; 4: len 4; hex 80000005; asc ;; 5: len 4; hex 80000005; asc ;; 6: len 30; hex 80000005000d40006000288000000a000000000d82020000017201518000; asc @ ` ( r Q ; (total 4294967291 bytes); 7: len 30; hex 80000005000d40006000288000000a000000000d82020000017201518000; asc @ ` ( r Q ; (total 4294967291 bytes); 8: len 30; hex 80000005000d40006000288000000a000000000d82020000017201518000; asc @ ` ( r Q ; (total 4294967291 bytes); 9: len 30; hex 80000005000d40006000288000000a000000000d82020000017201518000; asc @ ` ( r Q ; (total 4294967291 bytes); 10: len 30; hex 80000005000d40006000288000000a000000000d82020000017201518000; asc @ ` ( r Q ; (total 4294967291 bytes); 11: len 30; hex 80000005000d40006000288000000a000000000d82020000017201518000; asc @ ` ( r Q ; (total 4294967291 bytes); 12: len 4; hex 80000005; asc ;; *** (2) TRANSACTION: TRANSACTION 3467, ACTIVE 28 sec starting index read mysql tables in use 1, locked 1 LOCK WAIT 3 lock struct(s), heap size 1128, 2 row lock(s) MySQL thread id 60, OS thread handle 6127792128, query id 5748 localhost 127.0.0.1 root updating UPDATE t set status=5 where id = 0 *** (2) HOLDS THE LOCK(S): RECORD LOCKS space id 18 page no 4 n bits 88 index PRIMARY of table `test`.`t` trx id 3467 lock_mode X locks rec but not gap Record lock, heap no 11 PHYSICAL RECORD: n_fields 13; compact format; info bits 64 0: len 4; hex 80000005; asc ;; 1: len 6; hex 000000000d80; asc ;; 2: len 7; hex 010000011908ba; asc ;; 3: len 4; hex 80000005; asc ;; 4: len 4; hex 80000005; asc ;; 5: len 4; hex 80000005; asc ;; 6: len 30; hex 80000005000d40006000288000000a000000000d82020000017201518000; asc @ ` ( r Q ; (total 4294967291 bytes); 7: len 30; hex 80000005000d40006000288000000a000000000d82020000017201518000; asc @ ` ( r Q ; (total 4294967291 bytes); 8: len 30; hex 80000005000d40006000288000000a000000000d82020000017201518000; asc @ ` ( r Q ; (total 4294967291 bytes); 9: len 30; hex 80000005000d40006000288000000a000000000d82020000017201518000; asc @ ` ( r Q ; (total 4294967291 bytes); 10: len 30; hex 80000005000d40006000288000000a000000000d82020000017201518000; asc @ ` ( r Q ; (total 4294967291 bytes); 11: len 30; hex 80000005000d40006000288000000a000000000d82020000017201518000; asc @ ` ( r Q ; (total 4294967291 bytes); 12: len 4; hex 80000005; asc ;; *** (2) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 18 page no 4 n bits 88 index PRIMARY of table `test`.`t` trx id 3467 lock_mode X locks rec but not gap waiting Record lock, heap no 10 PHYSICAL RECORD: n_fields 13; compact format; info bits 64 0: len 4; hex 80000000; asc ;; 1: len 6; hex 000000000d8a; asc ;; 2: len 7; hex 02000001160511; asc ;; 3: len 4; hex 80000000; asc ;; 4: len 4; hex 80000000; asc ;; 5: len 4; hex 80000000; asc ;; 6: len 30; hex 80000005000d400058002880000005000000000d80010000011908ba8000; asc @ X ( ; (total 4294967291 bytes); 7: len 30; hex 80000005000d400058002880000005000000000d80010000011908ba8000; asc @ X ( ; (total 4294967291 bytes); 8: len 30; hex 80000005000d400058002880000005000000000d80010000011908ba8000; asc @ X ( ; (total 4294967291 bytes); 9: len 30; hex 80000005000d400058002880000005000000000d80010000011908ba8000; asc @ X ( ; (total 4294967291 bytes); 10: len 30; hex 80000005000d400058002880000005000000000d80010000011908ba8000; asc @ X ( ; (total 4294967291 bytes); 11: len 30; hex 80000005000d400058002880000005000000000d80010000011908ba8000; asc @ X ( ; (total 4294967291 bytes); 12: len 4; hex 80000005; asc ;; *** WE ROLL BACK TRANSACTION (2)分析下主要内容:
WE ROLL BACK TRANSACTION (2),是最终的处理结果,表示回滚了第一个事务。
第一个事务信息就显示了持有着主键 ID = 0 的记录写锁,想在主键 ID = 5 上加记录写锁时被阻塞等待;