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. 循环等待
相关推荐
Access开发易登软件几秒前
Access 怎么做前后端分离?用 Web API 读写 SQL Server
前端·数据库·人工智能·microsoft·excel·access
Juicedata4 分钟前
腾讯云 x JuiceFS:基于 FoundationDB 的企业级统一存储实践
数据库·人工智能·科技·云计算·腾讯云
总捣什么乱113 分钟前
MySQL MySQL是怎么保证主备一致的?
android·mysql·adb
小罗水15 分钟前
第13章 Redis 缓存、幂等锁与任务状态
数据库·redis·缓存
SelectDB技术团队25 分钟前
当 PostgreSQL 面临性能瓶颈:80TB 电商业务迁移至 Apache Doris 的实践思考
数据库·postgresql·apache
心念枕惊28 分钟前
新写了个直播录制工具,可录制抖音快手斗鱼直播
运维·服务器·数据库
逐米时代1 小时前
向量数据库选型——Chroma、Qdrant、Milvus到底怎么选
数据库·milvus
段一凡-华北理工大学1 小时前
AI推动工业智能化转型~系列文章05:特征工程:工业 AI 的第一生产力
数据库·人工智能·分布式·搜索引擎·特征工程·高炉智能化
半桶水专家1 小时前
SQL Server DML 操作语句完全指南
数据库·sqlserver
Irene19912 小时前
Oracle 连接避坑指南:环境+配置+账号
数据库·oracle