mysql死锁不是必须修复的错误,而是innodb主动终止低优先级事务的结果;真正要做的是降低发生频率——核心手段是调低隔离级别(如改为read committed)并补全缺失索引。

Deadlock found when trying to get lock; try restarting transaction 这个错误不是配置调高就能消失的,它暴露的是事务行为与数据库锁机制之间的错配。盲目把 isolation_level 设成 SERIALIZABLE 反而会让 MySQL 死锁更频繁,尤其在高并发扣减类场景。
为什么 MySQL 的 REPEATABLE READ 容易死锁
MySQL(InnoDB)在 REPEATABLE READ 下默认加间隙锁(gap lock)+ 记录锁(record lock),哪怕 WHERE 条件匹配不到数据,也会锁住索引范围。两个事务若按不同顺序更新重叠主键(比如 A 先改 id=5 再改 id=3,B 反过来),就会形成循环等待。
- 典型触发点:库存扣减、积分转账、并发生成唯一编号
- PostgreSQL 不同:它的
REPEATABLE READ是快照隔离(SI),不加间隙锁,死锁概率低得多 -
READ COMMITTED是更安全的起点——它只锁命中的行,不锁间隙,但需配合SELECT ... FOR UPDATE显式控制锁粒度
Flask-SQLAlchemy 中 isolation_level 配置不生效的真相
SQLALCHEMY_ENGINE_OPTIONS = {"isolation_level": "READ COMMITTED"} 只影响新建立的连接,对已开启的事务或正在运行的 db.session 完全无效。很多开发者改完配置就以为“全局生效”,结果线上还在用默认的 REPEATABLE READ。
-
db.session.begin()不会覆盖连接的隔离级别,它只是启动一个事务上下文 - 临时提级某次操作?必须用原生 SQL:
await conn.execute(text("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE")),且得在conn.begin()之前执行 - SQLite 是例外:
SERIALIZABLE实际是全局排他锁,高并发下直接退化为单线程,慎用
如何验证当前事务实际用的隔离级别
不能只信配置文件或 ORM 设置,必须查数据库真实反馈。不同数据库查法不同,而且必须在事务内查,否则返回的是会话默认值,不是当前事务生效值。
仅使用 Python 将 Claw Code 虚拟化框架与 OpenClaw 集成,提供 184 种工具和 200 多条命令,用于安全分析和代码质量...
- PostgreSQL:
SELECT current_setting('transaction_isolation') - MySQL 8.0+:
SELECT @@transaction_isolation - 封装一个调试函数,在关键事务开头执行并记录日志,比出问题后靠猜强得多
真正难处理的不是隔离级别,而是外部锁干扰
当多个服务共用同一张表,或者有触发器、存储过程、第三方工具(如 binlog 同步、审计插件)介入时,锁行为会脱离 SQLAlchemy 控制。这时候看日志、抓 pg_locks(PG)或 INFORMATION_SCHEMA.INNODB_TRX(MySQL)比反复调参管用。
最容易被忽略的一点:死锁日志里显示的“事务持有锁 A 等待锁 B”,往往不是你代码里写的那两行 SQL,而是另一条隐式语句(比如触发器里的 UPDATE)悄悄加的锁。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










