一次死锁排查过程

一次死锁排查过程

最近有个脚本报警,事务产生死锁。

查询InnoDB的死锁日志:

SHOW ENGINE INNODB STATUS;
返回的有关死锁的日志(已脱敏)
------------------------
LATEST DETECTED DEADLOCK
------------------------
2022-01-17 11:59:23 0x7f034c90c700
*** (1) TRANSACTION:
TRANSACTION 307242180, ACTIVE 0 sec fetching rows
mysql tables in use 3, locked 3
LOCK WAIT 47 lock struct(s), heap size 8400, 4 row lock(s)
MySQL thread id 22014272, OS thread handle 139653892638464, query id 1086559135 10.26.15.194 root updating
UPDATE dead_lock SET `field1`='A' WHERE `index_field1` = '26021775' AND `index_field2` = '2022-01-02'
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 29244 page no 140 n bits 240 index PRIMARY of table `ke_onse_test`.`user_kaoqi_dead_lock` trx id 307242180 lock_mode X locks rec but not gap waiting
Record lock, heap no 36 PHYSICAL RECORD: n_fields 17; compact format; info bits 0
 0: len 4; hex 80001d32; asc    2;;
 1: len 6; hex 00001250074f; asc    P O;;
 2: len 7; hex e2000000340110; asc     4  ;;
 3: len 8; hex 3233303935303930; asc 23095090;;
 4: len 9; hex e9bb84e9a296e6b481; asc          ;;
 5: len 3; hex 8fcc22; asc   ";;
 6: len 4; hex 80000002; asc     ;;
 7: len 9; hex e585ace4bc91e697a5; asc          ;;
 8: len 2; hex 5b5d; asc [];;
 9: len 2; hex 5b5d; asc [];;
 10: len 2; hex 5b5d; asc [];;
 11: len 4; hex 80000000; asc     ;;
 12: len 4; hex 80000001; asc     ;;
 13: len 0; hex ; asc ;;
 14: len 0; hex ; asc ;;
 15: len 5; hex 99abe2be47; asc     G;;
 16: len 5; hex 99abe2be47; asc     G;;

*** (2) TRANSACTION:
TRANSACTION 307242183, ACTIVE 0 sec fetching rows, thread declared inside InnoDB 4754
mysql tables in use 3, locked 3
47 lock struct(s), heap size 8400, 3 row lock(s)
MySQL thread id 22014295, OS thread handle 139652146185984, query id 1086559137 10.26.15.194 root updating
UPDATE dead_lock SET `field1`='B' WHERE `index_field1` = '23095090' AND `index_field2` = '2022-01-02'
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 29244 page no 140 n bits 240 index PRIMARY of table `ke_onse_test`.`user_kaoqi_dead_lock` trx id 307242183 lock_mode X locks rec but not gap
Record lock, heap no 36 PHYSICAL RECORD: n_fields 17; compact format; info bits 0
 0: len 4; hex 80001d32; asc    2;;
 1: len 6; hex 00001250074f; asc    P O;;
 2: len 7; hex e2000000340110; asc     4  ;;
 3: len 8; hex 3233303935303930; asc 23095090;;
 4: len 9; hex e9bb84e9a296e6b481; asc          ;;
 5: len 3; hex 8fcc22; asc   ";;
 6: len 4; hex 80000002; asc     ;;
 7: len 9; hex e585ace4bc91e697a5; asc          ;;
 8: len 2; hex 5b5d; asc [];;
 9: len 2; hex 5b5d; asc [];;
 10: len 2; hex 5b5d; asc [];;
 11: len 4; hex 80000000; asc     ;;
 12: len 4; hex 80000001; asc     ;;
 13: len 0; hex ; asc ;;
 14: len 0; hex ; asc ;;
 15: len 5; hex 99abe2be47; asc     G;;
 16: len 5; hex 99abe2be47; asc     G;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 29244 page no 29 n bits 1368 index idx_index_field2 of table `baii_test`.`dead_lock` trx id 307242183 lock_mode X locks rec but not gap waiting
Record lock, heap no 656 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 0: len 3; hex 8fcc22; asc   ";;
 1: len 4; hex 80001d32; asc    2;;

*** WE ROLL BACK TRANSACTION (2)

表结构大概如下:

CREATE TABLE `dead_lock` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `index_field1` int(20) NOT NULL,
  `index_field2` int(20) NOT NULL,
  `field1` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
  PRIMARY KEY (`id`),
  KEY `idx_index_field1` (`index_field1`),
  KEY `idx_index_field2` (`index_field2`),
  KEY `idx_day_desc` (`field1`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

提取有关死锁的两个事务

  • TRANSACTION(307242180) 下面简称事务1
UPDATE dead_lock SET `field1`='A' WHERE `index_field1` = '26021775' AND `index_field2` = '2022-01-02'
  • TRANSACTION(307242183) 下面简称事务2
UPDATE dead_lock SET `field1`='B' WHERE `index_field1` = '23095090' AND `index_field2` = '2022-01-02'
  • 查看两个sql的 EXPLAIN, 发现均使用了 merge index 优化技术, 正常的sql 只能使用一个索引,但使用 merge index 技术时会使用多个索引交替查询, 也就是说在查询时需要对两个索引进行加锁,而加锁过程不是原子性的,导致在并发事务过多的情况下,极容易发生死锁。
解析事务日志得到事务时序如下:
TRANSACTION(307242180) 事务1 TRANSACTION(307242183) 事务2
sql START TRANSACTION;
对索引:index_field1=‘26021775’ 加X锁(next-key锁) sql START TRANSACTION;
回表: 主键索引 index_field1=‘26021775’ 对应的主键 加X锁(行锁) 对索引:index_field1=‘23095090’ 加X锁
回表: 主键索引 index_field1=‘23095090’ 对应的主键 加X锁(行锁)
对索引:index_field2=‘2022-01-02’ 加X锁(next-key锁)
回表: 主键索引 index_field2=‘2022-01-02’ 对应的主键 加X锁(行锁), 发现 id=6790164 的行已被事务2持有 开始等待
对索引:index_field2=‘2022-01-02’ 加X锁(next-key锁), 发现 index_field2='2022-01-02' 的行已被 事务1持有, 此时死锁发生
采用就近原则, 回滚此事务, id=6790164 的行锁解除
sql COMMIT;
  • 在隔离级别为RR的情况下,普通索引加锁为 next-key锁
  • 主键是唯一索引,所以给主键加的锁退化为 行锁
分析完日志后一些问题的解答
  1. merge index 技术什么情况下才会使用?
  • 不可以转化成 range scan 的条件
  • where 条件有多列都建有索引
  • merge index 优化 已启用
  • Intersect和Union要符合 ROR,即 Rowid-Ordered-Retrival
  1. merge index 技术是啥东西
  • 官方的 http://dev.mysql.com/doc/refman/5.6/en/index-merge-optimization.html
  • 民间的 https://www.cnblogs.com/digdeep/p/4975977.html
  1. 如何避免这种死锁问题
  • 先查出主键 使用 主键 进行更新
  • 创建联合索引(科学的建立索引) 有联合索引后就不走 merge index 了
  • 调整mysql 配置关闭 merge index 优化
  • 更新事使用单个索引列进行更新,既能防止全表锁又能防止这种死锁问题
  1. next-key锁 的范围
  • 左开右闭区间

5 MySQL 优化术语必知必会让你愉快的和其他程序员交(zhuang)流(bei)

  • https://www.modb.pro/db/182912
参考文献
  1. MySQL 优化之 index merge(索引合并)
  2. 记录一次 mysql 死锁 (患者 为 index merge)
  3. mysql insert锁机制(insert死锁)
  4. mysql使用了index merge(索引合并)造成死锁分析总结
  5. multi-column-indexes-vs-index-merge

你可能感兴趣的:(sql,mysql)