index_merge导致死锁案例分析
发布日期:2021-05-18 09:16:21 浏览次数:18 分类:精选文章

本文共 2462 字,大约阅读时间需要 8 分钟。

一、死锁现象描述

1.1 基本环境信息

数据库版本:5.7.26-log

隔离级别:READ-COMMITTED

2.0 死锁表结构信息

表名:deadlock_test

表结构:

CREATE TABLE `deadlock_test` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`info_uuid` varchar(50) DEFAULT NULL,
`view_date` date DEFAULT NULL,
`view_count` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_view_date` (`view_date`),
KEY `idx_info_uuid` (`info_uuid`)
) ENGINE=InnoDB AUTO_INCREMENT=908189 DEFAULT CHARSET=utf8

3.0 死锁日志

死锁发生时间:2020-05-14 07:25:04

死锁原因:

  • 事务1(线程2557156)执行更新操作,等待deadlock_test表的PRIMARY索引上的锁。
  • 事务2(线程2557158)持有deadlock_test表的PRIMARY索引上的锁,并等待idx_view_date索引上的锁。

二、原因分析

2.1 死锁日志分析

2.1.1 事务1
  • 业务SQL
    UPDATE deadlock_test SET VIEW_COUNT = VIEW_COUNT + 1 WHERE INFO_UUID = '1a973b987d1d11eaa8c07cd30ad3aa18' AND VIEW_DATE = '2020-05-14'
  • 锁等待
    • 等待deadlock_test表的PRIMARY索引上的锁,涉及记录id=900004
  • MySQL处理方案
    • 回滚事务。
2.1.2 事务2
  • 业务SQL
    UPDATE deadlock_test SET VIEW_COUNT = VIEW_COUNT + 1 WHERE INFO_UUID = '27c3cf1e63fa11ea8b9a7cd30ae0074a' AND VIEW_DATE = '2020-05-14'
  • 锁持有
    • 持有deadlock_test表的PRIMARY索引上的锁,涉及记录id=900004
  • 锁等待
    • 等待deadlock_test表的idx_view_date索引上的锁,涉及记录view_date='2020-05-14', id=900004
  • MySQL处理方案
    • 不回滚,执行成功。

2.2 发生死锁SQL以及数据分布分析

2.2.1 业务SQL执行计划
  • 查询使用index_merge策略,分别使用idx_info_uuididx_view_date索引,取交集进行查询。
2.2.2 业务SQL数据分布
  • INFO_UUID字段的过滤性较好,但仍需使用单列索引idx_info_uuididx_view_date进行index_merge
  • 此外,通过数据分布可知,INFO_UUIDVIEW_DATE字段能够有效约束数据范围,定位到特定记录。

2.3 初步的分析判断

  • 事务1和事务2未被放置在事务中执行
  • **事务1执行的UPDATE语句试图修改的记录为id=900006,但死锁日志中却发现其试图等待id=900004的锁。异常
  • **事务2持有id=900004的锁,但仍需等待view_date='2020-05-14'id=900004的锁。异常
  • **因使用index_merge,MySQL在执行过程中可能对idx_info_uuididx_view_date进行扫描加锁,扩大了锁范围,导致并发请求时发生死锁。可能原因
  • 三、测试验证

    3.1 数据准备

    • deadlock_test已准备好测试数据,确保当INFO_UUIDVIEW_DATE组合唯一时,可以正确定位到目标记录。

    3.2 模拟index_merge下的加锁情况

    • 会话A和会话B同时执行查询时,可能导致index_merge下的锁等待,进一步触发死锁。
      | 模拟SQL | 会话 | 结果 |
      |--------|-------|------|
      | UPDATE ... FOR UPDATE | 会话A | 成功 |
      | UPDATE ... FOR UPDATE | 会话B | 死锁 |

    3.3 死锁日志查看

    • information_schema中获取锁等待信息,确认两个会话相互等待对方的锁资源,形成死锁闭环。

    3.4 锁等待分析

    • 会话A和会话B之间的锁等待关系:
      • 会话A等待会话B持有的锁,
      • 会话B等待会话A持有的锁。
    • MySQL的死锁检测在启用时会自动回滚资源较少的事务,防止死锁。关闭innodb_deadlock_detect可能会导致锁等待信息未被记录。

    3.5 整体分析处理

    • 案例分析
      • 会话A和会话B在执行index_merge下的并发读取时,由于锁资源扩大,导致彼此等待。
    • 处理方法
      • 优化索引:将idx_info_uuididx_view_date合并为一个复合索引,以减少锁等待。
      • 优化查询:修改查询方式,避免使用index_merge,而是利用-coverging index(覆盖索引)或其他高效索引策略。
      • 锁管理:使用合适的锁模式(如MySQL的隐式锁或显式锁),减少锁的竞争程度。
      • 事务设计:将多个数据操作合并到一个事务中,减少并发锁请求的次数。

    四、总结

    此死锁事件的成因在于数据库查询策略中对索引的使用不当,导致锁资源扩大。index_merge策略和单字段索引的结合,引发了多个锁的等待,最终导致死锁。我建议优化索引结构,减少锁等待,采用合适的查询策略,确保在高并发环境下系统的稳定性和性能。

    上一篇:MySQL的几种表关联算法
    下一篇:MySQL如何对order by优化?

    发表评论

    最新留言

    能坚持,总会有不一样的收获!
    [***.219.124.196]2025年04月19日 21时44分46秒