Mysql数据库数据软隔离的并发死锁情况

逻辑隔离表死锁问题分析报告,记录遇到的一个死锁问题。

在多服同表的场景下,采用server_id字段进行数据软隔离,各服访问的数据不存在交叉,但是实际如果索引利用不当,依然可能会引发死锁:

  1. 数据库表和索引,隔离级别为可重复读

    sql 复制代码
    CREATE TABLE `zone` (
      `server_id` int NOT NULL COMMENT '服务器',
      `guild_id` int NOT NULL COMMENT '公会,隶属于server_id',
      `assigned_server_id` int NOT NULL DEFAULT '0' COMMENT '分配服务器',
      `mode` int NOT NULL DEFAULT '0' COMMENT '模式',
      `zone_id` int NOT NULL DEFAULT '0' COMMENT '战区',
      ...,
      PRIMARY KEY (`server_id`,`guild_id`,`mode`),
      UNIQUE KEY `assigned_zone` (`assigned_server_id`,`mode`,`zone_id`)
    ) ENGINE=InnoDB DEFAULT;
  2. 死锁原因(...表示字段展开省略)

    sql 复制代码
    -- 1. 各关键节点服务器负责各自清除一组旧的相关数据(数据隔离互不干扰)
    DELETE FROM `zone` WHERE `server_id` IN (...) AND `mode` = ...;
    sql 复制代码
    -- 2. 随后同节点服务器重新维护一组新的初始数据(数据隔离互不干扰)
    INSERT INTO `zone`(...) VALUES (...) ON DUPLICATE KEY UPDATE ...=...;
  3. 偶发死锁,查询INNODB STATUS日志

    INSERT持有锁:

    RECORD LOCKS space id 2 page no 18 n bits 504 index assigned_zone of table zone trx id 1 lock_mode X

    Record lock, heap no 23 PHYSICAL RECORD: n_fields 5; compact format; info bits 0

    Record lock, heap no 225 PHYSICAL RECORD: n_fields 5; compact format; info bits 0
    INSERT等待锁:

    RECORD LOCKS space id 2 page no 26 n bits 352 index PRIMARY of table zone trx id 1 lock_mode X locks gap before rec insert intention waiting

    Record lock, heap no 149
    DELETE持有锁:

    RECORD LOCKS space id 2 page no 26 n bits 352 index PRIMARY of table zone trx id 2 lock_mode X

    Record lock, heap no 149
    DELETE等待锁:

    RECORD LOCKS space id 2 page no 18 n bits 504 index assigned_zone of table zone trx id 2 lock_mode X locks rec but not gap waiting

    Record lock, heap no 23 PHYSICAL RECORD: n_fields 5; compact format; info bits 0

    3.1 加锁时序分析

    阶段 INSERT DELETE
    第一步 assigned_zone索引加X锁 PRIMARY索引加X锁
    第二步 等待 PRIMARY 索引的间隙锁 等待 assigned_zone 索引的 X 锁
    结果 死锁 死锁

    3.2 根本原因

    两个事务的加锁顺序相反:

    • INSERT 操作:

      • 先加锁 assigned_zone 索引 ← 由于有 UNIQUE 约束需要检查唯一性

      • 再加锁 PRIMARY 索引

    • DELETE 操作:

      • 先加锁 PRIMARY 索引 ← WHERE 条件基于 server_id (PRIMARY 的一部分)

      • 再加锁 assigned_zone 索引 ← 需要检查对应的 UNIQUE 索引记录

    3.3 死锁发生的完整过程

    复制代码
    Transaction 1 (INSERT):
    	① 获得 assigned_zone 唯一索引上的锁(两条记录)
    	② 需要等待 PRIMARY 索引上的间隙锁
    
    Transaction 2 (DELETE):
    	① 获得 PRIMARY 索引上的锁(一条记录)
    	② 需要等待 assigned_zone 索引上的锁
    
    => 循环等待,死锁!
    即便两批业务数据"理论不交叉",只要它们在同一棵 B+Tree 的邻接范围、同一页、同一键前缀范围内,就可能形成等待链
  4. 快速解决方案

    根据业务逻辑,修改 DELETE 语句的 WHERE 条件,改用 assigned_server_id 而非 server_id:先触发 UNIQUE 索引加锁 → 再加 PRIMARY 索引锁。保证锁顺序一致,消除了死锁隐患。

  5. 总结

    5.1 InnoDB加锁原理

    对于有多个UNIQUE索引的INSERT操作,InnoDB的加锁顺序为:

    1. 先加 UNIQUE 索引锁 - 保证唯一性约束

    2. 再加 PRIMARY 索引锁 - 保证主键约束

    对于DELETE操作,加锁顺序取决于WHERE条件使用的索引:

    • 使用 PRIMARY 索引的条件 → 先加 PRIMARY 锁

    • 使用 UNIQUE 索引的条件 → 先加 UNIQUE 锁

    5.2 死锁的本质

    死锁四要素:

    1. 互斥条件
    2. 不可剥夺
    3. 占用且等待
    4. 循环等待
相关推荐
喜欢的名字被抢了3 分钟前
MySQL基础入门:从零理解数据库与SQL
数据库·mysql·教程
Elastic 中国社区官方博客1 小时前
如何比较两个 Elasticsearch 索引并找出缺失的文档
大数据·运维·数据库·elasticsearch·搜索引擎
DLYSB_1 小时前
生命通道:如何用 HIS 医疗系统直连网络声光终端,打造“零延误”的危急值响应网关?
java·网络·数据库·报警灯
儒雅的大叔1 小时前
MySQL数据库InnoDB数据恢复工具使用总结
数据库·mysql·adb
观远数据2 小时前
ChatBI选型对比:从意图识别到SQL修复,六个维度打分决定是否值得投产
数据库·人工智能·sql
嘉&年华2 小时前
【第008篇】通过dexp和dimp命令导出和导入dmp文件(适用于达梦数据库)
数据库·sql
哥是强混3 小时前
Means:基于 .NET 10 打造的开源自部署 S3 兼容对象存储服务
网络·数据库·.net
ffqws_4 小时前
redis面试:如何保证双写一致
数据库·redis·缓存
水无痕simon4 小时前
6 数据库同义词
数据库
杜子不疼.5 小时前
【Qt初识】信号槽(三):机制意义、断开连接与 Lambda 表达式
开发语言·数据库·qt