MySQL 死锁总复发?用螺旋事务相位互逆法定位加锁顺序(附 Python 诊断脚本)

你一定见过这种报警:

复制代码
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

复盘的时候大家常说:

  • "并发太高了"
  • "加个重试就行"
  • "把事务写小一点"

但下周死锁又来了。

用螺旋生成论的话,结论很直接:

死锁不是"并发问题",而是事务加锁相位互逆,两个事务各自拿着对方想要的锁,在相位空间里转成闭环。


1. 先造一个最小死锁场景

两张表:

复制代码
CREATE TABLE account (
  id BIGINT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  balance INT NOT NULL,
  INDEX idx_user (user_id)
) ENGINE=InnoDB;

INSERT INTO account VALUES (1, 101, 1000), (2, 102, 1000);

事务 A:

复制代码
BEGIN;
UPDATE account SET balance = balance - 100 WHERE id = 1;
-- 稍等
UPDATE account SET balance = balance + 100 WHERE id = 2;
COMMIT;

事务 B:

复制代码
BEGIN;
UPDATE account SET balance = balance - 100 WHERE id = 2;
-- 稍等
UPDATE account SET balance = balance + 100 WHERE id = 1;
COMMIT;

A 持有 id=1 的行锁,等 id=2;

B 持有 id=2 的行锁,等 id=1。

这就是最经典的相位互逆


2. 什么是"事务相位"

给每个事务定义一个加锁相位序列:

复制代码
T_A: lock(1) -> wait(2)
T_B: lock(2) -> wait(1)

如果把锁对象看成圆周上的点:

  • id=1 在相位 0°
  • id=2 在相位 180°

那么:

  • T_A 的加锁方向是 0° -> 180°
  • T_B 的加锁方向是 180° -> 0°

两条路径反向,迟早撞成环。

所以死锁的根因不是"锁多",而是:

多个事务对相同资源集合的加锁顺序不一致。


3. 用 Python 把锁等待画成相位图

不需要真的去扒 information_schema.INNODB_LOCKS(新版 MySQL 已迁移到 performance_schema),我们可以先把应用层事务抽象出来:

复制代码
from collections import defaultdict

# txn: 事务号 -> 加锁顺序
txns = {
    "T_A": [1, 2],
    "T_B": [2, 1],
}

# 构造等待边:当前事务在等它下一个还没拿到的锁
wait_graph = defaultdict(list)
for txn, locks in txns.items():
    for i in range(len(locks) - 1):
        held = locks[i]
        want = locks[i + 1]
        wait_graph[txn].append((held, want))

# 检测互逆:如果存在 T1: a->b,T2: b->a
edges = set()
for txn, lst in wait_graph.items():
    for held, want in lst:
        edges.add((held, want))

def has_deadlock_phase(edges):
    for a, b in edges:
        if (b, a) in edges:
            return True, (a, b)
    return False, None

flag, pair = has_deadlock_phase(edges)
print("死锁相位互逆:", flag)
print("互逆锁对:", pair)

输出:

复制代码
死锁相位互逆: True
互逆锁对: (1, 2)

只要出现 (a,b)(b,a) 同时存在,就可以判定:加锁相位互逆,死锁闭环已形成


4. 工程上怎么治

方案 1:统一加锁顺序(最稳)

所有事务都按 id 升序加锁:

复制代码
-- 事务 A 和事务 B 都先锁 1,再锁 2
UPDATE account SET balance = balance - 100 WHERE id = 1;
UPDATE account SET balance = balance + 100 WHERE id = 2;

相位变成:

复制代码
T_A: 0° -> 180°
T_B: 0° -> 180°

方向一致,环打不开。

方案 2:一次拿到所有锁
复制代码
SELECT * FROM account WHERE id IN (1,2) FOR UPDATE;

先占住资源,再算业务,再更新。

方案 3:缩短锁窗口
  • 事务里别调外部接口
  • 别在事务里 sleep
  • 别在事务里写日志/发消息

5. 螺旋视角的一句话总结

MySQL 死锁 = 事务加锁相位互逆。

排查死锁,不要只看"报错那一行",要看多个事务的加锁顺序是否形成反向相位

统一加锁顺序,就是把所有事务的相位箭头掰到同一个方向。

下一篇写:K8s Pod 总被调度到同一批节点?用螺旋调度相位饱和法看 kube-scheduler 的打分偏置。


参考文献

  • 张智明. 螺旋生成元:跨学科统一数学框架. Zenodo. DOI: 10.5281/zenodo.21555082
  • 张智明. 螺旋计算. Zenodo. DOI: 10.5281/zenodo.21356615
  • 张智明. 螺旋数原理. Zenodo. DOI: 10.5281/zenodo.20602099

本文由作者原创,部分代码由 AI 辅助生成。


相关推荐
DQQzero1 小时前
豆包工作来了:AI办公三国杀的牌桌重组
人工智能·ai·大模型·办公
VIP_CQCRE2 小时前
Visual Studio 也能丝滑接入大模型:用 Ace Data Cloud + LMLocal 打造 AI 编程体验
ai·大模型·visual studio·ace data cloud·lmlocal
BullSmall2 小时前
RAG 知识库专项测试
功能测试·大模型·测试
星光开发者3 小时前
基于Spring Boot的充电桩管理系统的设计与实现-计算机毕设【课程设计】57105
vue.js·spring boot·vscode·mysql·django·php·express
imDwAaY3 小时前
MySQL MVCC 详解:原理、版本链、Read View 与可见性判断
数据库·sql·mysql
Java成神之路-3 小时前
MySQL 约束与索引完全指南
mysql
百万蹄蹄向前冲17 小时前
密码都对Node.js却连不上Linux数据库
linux·mysql
华章酱17 小时前
MySQL幻读是怎么出现的
数据库·mysql·幻读
咕泡科技17 小时前
咕泡科技FDE系列最新产品重磅发布!
人工智能·大模型·ai落地·fde·前沿部署工程师