水稻:最近有个朋友生产环境出现MySQL死锁问题,一听是死锁,那必须去看看啊,于是饶(si)有(qu)兴(huo)致(lai)的研究了好几天

菜瓜:MySQL死锁,赶紧分享一下

水稻:能否先让我装完X,我从朋友那里拿到数据结构,复现,分析,查资料,总。。。

菜瓜:今天的菜真香

水稻:。。。好吧,进入正题(数据已加工处理)

  • 一开始朋友拿出了死锁日志中记录的两条SQL语句(暂且把第一条SQL叫SQL1,第二条SQL2)

      1. -- 两句SQL结构一致,只是参数不一样。
      1. explain
      2. update `deadlock` set `status` = 1,`expired` = CURRENT_TIMESTAMP
      3. where `id` <= 35610745 and `from` = '' and `to` = 'c' ;
      4.  
      5. explain
      6. update `deadlock` set `status` = 1,`expired` = CURRENT_TIMESTAMP
      7. where `id` <= 35611183 and `from` = '' and `to` = '' ;
  • 然后是表结构和数据 (这里只录入模拟数据,数据量并不是这次分析的关键原因)
      1. CREATE TABLE `deadlock` (
      2. `id` int(11) NOT NULL AUTO_INCREMENT,
      3. `from` varchar(64) CHARACTER SET utf8mb4 DEFAULT '',
      4. `to` varchar(64) CHARACTER SET utf8mb4 DEFAULT '',
      5. `status` int(11) DEFAULT NULL,
      6. `expired` datetime DEFAULT NULL,
      7. PRIMARY KEY (`id`),
      8. KEY `idx_from` (`from`),
      9. KEY `idx_to` (`to`)
      10. ) ENGINE=InnoDB AUTO_INCREMENT=35611184 DEFAULT CHARSET=utf8;
      11.  
      12. INSERT INTO `deadlock` (`id`, `from`, `to`, `status`, `expired`)
      13. VALUES
      14. (5681309, '', 'b', NULL, NULL),
      15. (5681310, 'b', '', NULL, NULL),
      16. (5681311, '', 'b', NULL, NULL),
      17. (5681312, '', 'f', NULL, NULL),
      18. (5681313, '', 'f', NULL, NULL),
      19. (5681314, '', 'f', NULL, NULL),
      20. (5681315, 'f', '', NULL, NULL),
      21. (35610742, '', 'c', 1, '2020-07-07 02:03:58'),
      22. (35610744, '', 'c', 1, '2020-07-07 02:03:58'),
      23. (35610745, '', 'c', 1, '2020-07-07 02:03:58'),
      24. (35611180, '', '', 1, '2020-07-07 02:03:58'),
      25. (35611182, '', '', 1, '2020-07-07 02:03:58'),
      26. (35611183, '', '', 1, '2020-07-07 02:03:58');
  • 两条SQL命中的记录各三条。一看是死锁,第一反应是发生记录资源互斥等待。猜想会不会是这6行记录在执行update的时候SQL1和SQL2修改的记录发生了互斥

菜瓜:所以你最开始想的是更新时两条SQL获取记录的顺序反了,譬如说SQL1先拿35610742,再拿35610744前,SQL2先把35610744拿到了且它要拿35610742在阻塞

水稻:是的,但是很快发现这个思路很离谱,我们说锁的时候应该是和索引联系起来,分析记录就有点偏了。而且按记录来看,两个SQL命中的记录都不一样。于是回到索引上,就能说通他们都命中了from='1'这个条件,在idx_from上发生了互斥。

菜瓜:soga,真实情况呢

水稻:事情如果这么简单我就不会研究它好几天了。分析完了之后开始准备复现死锁。思路很简单,因为SQL只有一句,要模拟出来就用多线程并发执行

  • Java Code

      1. @Resource
      2. private DeadlockMapper deadlockMapper;
      3.  
      4. @Resource
      5. ThreadPoolTaskExecutor threadPoolTaskExecutor;
      6.  
      7. @Test
      8. public void deadlock() {
      9. for (; ; ) {
      10. threadPoolTaskExecutor.execute(() -> {
      11. try {
      12. Long id = 35611183L;
      13. String from = "1";
      14. String to = "2";
      15. deadlockMapper.updateDeadlock(id, from, to);
      16. } catch (Exception e) {
      17. System.out.println("transaction tututu");
      18. e.printStackTrace();
      19. System.exit(0);
      20. }
      21. });
      22. threadPoolTaskExecutor.execute(() -> {
      23. try {
      24. Long id = 35610745L;
      25. String from = "1";
      26. String to = "c";
      27. deadlockMapper.updateDeadlock(id, from, to);
      28. } catch (Exception e) {
      29. System.out.println("transaction kakaka");
      30. e.printStackTrace();
      31. System.exit(0);
      32. }
      33. });
      34. System.out.println("once loop finish ...");
      35. }
      36. }
  • MyBatis Code
      1. public interface DeadlockMapper extends Mapper<Deadlock> {
      2.  
      3. int updateDeadlock(@Param("id") Long id,
      4. @Param("from") String from,
      5. @Param("to") String to);
      6. }
      7.  
      8. <update id="updateDeadlock">
      9. update `deadlock`
      10. set `status` = 1,`expired` = CURRENT_TIMESTAMP
      11. where `id` <![CDATA[<=]]> #{id}
      12. and `from` = #{from}
      13. and `to` = #{to}
      14. </update>
  • 执行结果
      1. once loop finish ...
      2. once loop finish ...
      3. transaction tututu
      4. transaction tututu
      5. org.springframework.dao.DeadlockLoserDataAccessException:
      6. ### Error updating database. Cause: com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction
      7. ### The error may involve defaultParameterMap
      8. ### The error occurred while setting parameters
      9. ### SQL: update `deadlock` set `status` = 1,`expired` = CURRENT_TIMESTAMP where `id` <= ? and `from` = ? and `to` = ?
      10. ### Cause: com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction
      11. 。。。
  • 死锁日志 - show engine innodb status;
      1. LATEST DETECTED DEADLOCK
      2. ------------------------
      3. 2020-07-07 08:00:54 0x7f2c38b6a700
      4. *** (1) TRANSACTION:
      5. TRANSACTION 61382, ACTIVE 0 sec starting index read
      6. mysql tables in use 3, locked 3
      7. LOCK WAIT 4 lock struct(s), heap size 1136, 3 row lock(s)
      8. MySQL thread id 147, OS thread handle 139827907593984, query id 22005 101.68.68.234 root updating
      9. update `deadlock`
      10. set `status` = 1,`expired` = CURRENT_TIMESTAMP
      11. where `id` <= 35611183
      12. and `from` = ''
      13. and `to` = ''
      14. *** (1) WAITING FOR THIS LOCK TO BE GRANTED:
      15. RECORD LOCKS space id 66 page no 4 n bits 80 index idx_from of table `qc`.`deadlock` trx id 61382 lock_mode X waiting
      16. Record lock, heap no 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
      17. 0: len 1; hex 31; asc 1;;
      18. 1: len 4; hex 821f6076; asc `v;;
      19.  
      20. *** (2) TRANSACTION:
      21. TRANSACTION 61381, ACTIVE 0 sec fetching rows
      22. mysql tables in use 3, locked 3
      23. 5 lock struct(s), heap size 1136, 11 row lock(s)
      24. MySQL thread id 150, OS thread handle 139827906782976, query id 22004 101.68.68.234 root updating
      25. update `deadlock`
      26. set `status` = 1,`expired` = CURRENT_TIMESTAMP
      27. where `id` <= 35610745
      28. and `from` = ''
      29. and `to` = 'c'
      30. *** (2) HOLDS THE LOCK(S):
      31. RECORD LOCKS space id 66 page no 4 n bits 80 index idx_from of table `qc`.`deadlock` trx id 61381 lock_mode X
      32. Record lock, heap no 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
      33. 0: len 1; hex 31; asc 1;;
      34. 1: len 4; hex 821f6076; asc `v;;
      35.  
      36. Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
      37. 0: len 1; hex 31; asc 1;;
      38. 1: len 4; hex 821f6078; asc `x;;
      39.  
      40. Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
      41. 0: len 1; hex 31; asc 1;;
      42. 1: len 4; hex 821f6079; asc `y;;
      43.  
      44. Record lock, heap no 5 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
      45. 0: len 1; hex 31; asc 1;;
      46. 1: len 4; hex 821f622c; asc b,;;
      47.  
      48. *** (2) WAITING FOR THIS LOCK TO BE GRANTED:
      49. RECORD LOCKS space id 66 page no 3 n bits 80 index PRIMARY of table `qc`.`deadlock` trx id 61381 lock_mode X locks rec but not gap waiting
      50. Record lock, heap no 12 PHYSICAL RECORD: n_fields 7; compact format; info bits 0
      51. 0: len 4; hex 821f622c; asc b,;;
      52. 1: len 6; hex 00000000e962; asc b;;
      53. 2: len 7; hex 27000001d901fd; asc ' ;;
      54. 3: len 1; hex 31; asc 1;;
      55. 4: len 1; hex 32; asc 2;;
      56. 5: len 4; hex 80000001; asc ;;
      57. 6: len 5; hex 99a6ce7d03; asc } ;;
      58.  
      59. *** WE ROLL BACK TRANSACTION (1)

菜瓜:这不是很顺利吗

水稻:我会告诉你在我本地环境中一直没有复现出来吗?我会告诉你我找了一天才找到原因是因为MySQL配置问题(需要开启bin log,开启成功后执行 select version() 会看到 5.7.30-log)?我会告诉你这过程中我放弃了好几次又决定再看看吗?当然不会

菜瓜:好厉害哦!那你很棒棒哦!

水稻:。。。来,进入分析环节

  • 两个SQL语句的explain执行计划

      1. explain
      2. update `deadlock` set `status` = 1,`expired` = CURRENT_TIMESTAMP
      3. where `id` <= 35610745 and `from` = '' and `to` = 'c' ;
      4. -- Using intersect(idx_from,idx_to); Using where
      5.  
      6. explain
      7. update `deadlock` set `status` = 1,`expired` = CURRENT_TIMESTAMP
      8. where `id` <= 35611183 and `from` = '' and `to` = '' ;
      9. -- Using intersect(idx_to,idx_from); Using where
    • 可以看到执行这两个SQL的时候命中的索引竟然是NB的Merge Index(鬼知道这是啥),而且它两顺序还不一致(有兴趣的朋友可以上搜索一下这个玩意)
    • Merge Index 索引合并技术是MySQL的一个优化,会将两个独立的索引扫描的结果进行取交集或者并集来加快数据的检索。
    • 这里的关键点是它会导致MySQL在拿锁的顺序上不一致

菜瓜:你这个铺垫太多了,我已经记不清楚前面那么多东西了,请直接上结论吧

水稻:好的,前面的code是方便您自己下去复现的,对于动手党有用。您可以直接忽略

  • 这里原因的解析只需要注意死锁日志和最后的explain执行计划结果
  • 事务Transaction1
  • 事务Transaction2

  • 结论就是:id为35610742记录上的索引顺序不一致出现了死锁

    • SQL1(事务2)持有idx_from,在等待主键primary key。执行计划中它命中索引 intersect(idx_from,idx_to) ,非聚簇索引除了拿到自己的索引之外还要获取主键索引,所以它拿锁的顺序是先获取idx_from,再获取primary key,最后获取idx_to,再primary key

    • SQL2(事务1)持有primary key,在等待idx_from。同理它命中索引intersect(idx_to,idx_from),它拿锁的顺序是先获取idx_to,再获取primary key,最后获取idx_from,再primary key
      (日志里面看不出来SQL2持有主键索引,这里是根据出现死锁的互斥条件得出它持有主键索引)

菜瓜:这是何等的wocao!!!

水稻:解决这个问题只要把两个单个索引改成联合索引即可

总结:

  • 单行update语句引发的死锁问题
  • 单行记录拿锁顺序不一致出现死锁
  • 索引合并Merge Index 导致获取锁顺序不一致
  • 场景复现需要把log_bin日志打开,注意查看select version()
  • 多线程模拟并发场景
  • 死锁日志分析

【MySQL】Merge Index导致死锁的更多相关文章

  1. [经验分享] MySQL Innodb表导致死锁日志情况分析与归纳【转,纯学习】

    在定时脚本运行过程中,发现当备份表格的sql语句与删除该表部分数据的sql语句同时运行时,mysql会检测出死锁,并打印出日志. 两个sql语句如下: (1)insert into backup_ta ...

  2. MySQL Innodb表导致死锁日志情况分析与归纳

    发现当备份表格的sql语句与删除该表部分数据的sql语句同时运行时,mysql会检测出死锁,并打印出日志   案例描述在定时脚本运行过程中,发现当备份表格的sql语句与删除该表部分数据的sql语句同时 ...

  3. mysql force index() 强制索引的使用

    mysql force index() 强制索引的使用 之前跑了一个SQL,由于其中一个表的数据量比较大,而在条件中有破坏索引或使用了很多其他索引,就会使得sql跑的非常慢... 那我们怎么解决呢? ...

  4. 解决Android Studio 3.0导入module依赖后unable to merge index

    解决Android Studio 3.0导入module依赖后unable to merge index 项目需要使用im, 在项目里导入了腾讯im的几个module依赖, 项目无法编译, 报错una ...

  5. MySQL 性能优化-数据库死锁监控

    MySQL性能优化-数据库死锁监控 by:授客 QQ:1033553122 1)表锁定 通过检查 table_locks_waited 和 table_locks_immediate 状态变量来分析表 ...

  6. Mysql索引引起的死锁

    提到索引,首先想到的是效率提高,查询速度提升,不知不觉都会有一种心理趋向,管它三七二十一,先上个索引提高一下效率..但是索引其实也是暗藏杀机的... 今天压测带优化项目,开着Jmeter高并发访问项目 ...

  7. MySQL MERGE存储引擎 简介及用法

    MERGE存储引擎把一组MyISAM数据表当做一个逻辑单元来对待,让我们可以同时对他们进行查询.构成一个MERGE数据表结构的各成员MyISAM数据表必须具有完全一样的结构.每一个成员数据表的数据列必 ...

  8. MySQL Online DDL导致全局锁表案例分析

    MySQL Online DDL导致全局锁表案例分析 我这边遇到了什么问题? 线上给某个表执行新增索引SQL, 然后整个数据CPU打到100%, 连接数暴增到极限, 最后导致所有访问数据库的应用都奔溃 ...

  9. Mysql force index和ignore index 使用实例

    前几天统计一个sql,是一个人提交了多少工单,顺便做了相关sql优化.数据大概2000多w. select CustName,count(1) c from WorkOrder where Creat ...

随机推荐

  1. 如何监控 Linux 服务器状态?

    Linux 服务器我们天天打交道,特别是 Linux 工程师更是如此.为了保证服务器的安全与性能,我们经常需要监控服务器的一些状态,以保证工作能顺利开展. 本文介绍的几个命令,不仅仅适用于服务器监控, ...

  2. 掌握SpringBoot-2.3的容器探针:实战篇

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:原创文章分类汇总,及配套源码,涉及Java.Docker.K8S.DevOPS等 经过多篇知识 ...

  3. 有关指针 -> 和* 的重载

    1, #include<iostream> #include<string> using namespace std; class test{ int i; public: t ...

  4. 【华为云技术分享】数据库开发:MySQL Seconds_Behind_Master简要分析

    [摘要]对于mysql主备实例,seconds_behind_master是衡量master与slave之间延时的一个重要参数.通过在slave上执行"show slave status;& ...

  5. 02、MyBatis XML配置

    MyBatis-全局配置文件 在MyBatis中全局配置文件有着重要的地位,里面有9类行为信息;如果我们要想将MyBatis运用的熟练,配置全局配置文件是必不可少的步骤,所以我们一定要啃下这一块硬骨头 ...

  6. EIGRP-13-弥散更新算法-停滞在活动状态

    如果一台路由器参与到了针对某个目的地的弥散计算中(即将相应路由置为活动状态,并发送查询包),它必须首先等待所有邻居都返回响应包,之后它才能执行自已的弥散计算,接着选出新的最优路径,最后开始发送自已的响 ...

  7. Win10下Tensorflow的安装

    Win10下Tensorflow的安装 1. Tensorflow简介 TensorFlow是谷歌基于DistBelief进行研发的第二代人工智能学习系统,其命名来源于本身的运行原理.Tensor(张 ...

  8. Second Large Rectangle【单调栈】

    Second Large Rectangle 题目链接(点击) 题目描述 Given a N×MN \times MN×M binary matrix. Please output the size ...

  9. jmeter录制app测试脚本

    1.jmeter 下载地址 https://jmeter.apache.org 2.选择下载包 3.下载完成后解压即可使用(也可以配置环境变量,但我一般不配置,可以使用) 4.打开jmeter 创建线 ...

  10. Mac 安装fiddler

    1, 安装mono 2,下载fiddler for mac https://www.telerik.com/download/fiddler 3. 解压fiddler-mac.zip 4, cd fi ...