RC 隔离级别下 MySQL InnoDB 死锁典型案例

📅 2026/7/5 8:47:23
RC 隔离级别下 MySQL InnoDB 死锁典型案例
RC 隔离级别下 MySQL InnoDB 死锁典型案例前置知识点RC 没有间隙锁只有记录锁死锁只来自不同事务加锁顺序不一致RR 有间隙锁/临键锁死锁场景更多RC 死锁全部是「行锁争抢顺序颠倒」导致死锁四条件互斥、持有并等待、不可剥夺、循环等待。案例1两个事务更新两条记录加锁顺序相反最常见表 accountsqlCREATE TABLE account (id BIGINT PRIMARY KEY,balance INT);– 数据insert into account values(1,1000),(2,1000);场景转账A转B、B转ARC级别。事务T11→2转账sqlbegin;update account set balancebalance-100 where id1; – 锁id1sleep(2);update account set balancebalance100 where id2; – 申请锁id2commit;事务T22→1转账sqlbegin;update account set balancebalance-100 where id2; – 锁id2sleep(2);update account set balancebalance100 where id1; – 申请锁id1commit;死锁形成T1持有1锁等2锁T2持有2锁等1锁循环等待 → 死锁。RC / RR 都会出现这个死锁和隔离级别无关纯粹加锁顺序颠倒。案例2update select for update 混合顺序颠倒同一张account表。T1sqlbegin;update account set balancebalance-50 where id1; – 锁1sleep(2);select * from account where id2 for update; – 等2锁T2sqlbegin;select * from account where id2 for update; – 锁2sleep(2);update account set balancebalance-50 where id1; – 等1锁同样循环等待死锁。RC下 for update 依然加行排他锁会产生死锁。案例3批量更新in 集合顺序不一致引发死锁商品库存表 stock(product_id, stock) 主键 product_id。需求一次扣减多个商品库存。T1 扣 [1001,1002]sqlbegin;update stock set stockstock-1 where product_id in (1001,1002);InnoDB 执行 in 会按主键从小到大依次加锁先锁1001再锁1002。T2 扣 [1002,1001]sqlbegin;update stock set stockstock-1 where product_id in (1002,1001);依然按主键排序加锁先锁1001再锁1002 → 不会死锁。会死锁的写法分开多条update顺序相反T1sqlbegin;update stock set stockstock-1 where product_id1001;sleep(1);update stock set stockstock-1 where product_id1002;T2sqlbegin;update stock set stockstock-1 where product_id1002;sleep(1);update stock set stockstock-1 where product_id1001;循环等待死锁。大厂规范批量操作必须统一按主键升序加锁避免该死锁。案例4先查询for update再更新事务锁获取顺序交叉订单表 order(id, user_id, status) 主键id。T1 操作订单1、再订单2sqlbegin;select * fromorderwhere id1 for update;sleep(2);updateorderset status2 where id2;T2 操作订单2、再订单1sqlbegin;select * fromorderwhere id2 for update;sleep(2);updateorderset status2 where id1;死锁。RC下for update 是排他行锁和RR行为一致。案例5唯一索引冲突 插入更新交叉死锁RC特有场景无间隙锁表sqlCREATE TABLE goods (id BIGINT PRIMARY KEY AUTO_INCREMENT,sn VARCHAR(32) UNIQUE,num INT);数据sn‘A’ 已存在。T1sqlbegin;– 更新已有snA行加该行记录锁update goods set numnum1 where sn‘A’;sleep(2);– 插入sn‘B’无锁冲突insert into goods(sn,num) values(‘B’,10);T2sqlbegin;– 插入sn‘B’无冲突持有B行锁insert into goods(sn,num) values(‘B’,10);sleep(2);– 更新sn‘A’申请A行锁update goods set numnum1 where sn‘A’;T1持有A锁等B锁T2持有B锁等A锁 → 死锁。RC没有间隙锁这里死锁完全来自两条独立行锁循环等待。RC 死锁核心特点总结RC 死锁全部源于行锁获取顺序不一致不存在RR那种间隙锁导致的诡异死锁只要所有事务访问资源统一按主键升序获取锁就能彻底杜绝RC下死锁RC下 for update / update / delete 都加记录排他锁相互阻塞交叉顺序必死锁对比RRRR除了行锁顺序问题还会因为间隙锁、临键锁出现更多无规律死锁这也是大厂高并发选用RC的原因之一——死锁更容易分析、规避。通用解决方案线上落地1. 所有多资源更新强制按主键ID升序操作2. 缩短事务不要事务内sleep、远程调用3. 批量更新统一用in让数据库按主键排序加锁4. 超高并发前置分布式锁从业务层避免多事务同时争抢多行。5、减少悲观锁 for update 大范围锁定优先乐观锁version 版本号