线上碰到 MySQL 锁问题,不少人第一反应是调大 innodb_lock_wait_timeout。这个动作有点像给发烧的人多盖被子:表面在处理问题,实际可能让连接等得更久,把线程池和连接池一起拖垮。这篇按一次转账服务的事故复盘来写,重点说清两类锁报错的区分、用 performance_schema 找阻塞者、以及怎么把死锁概率降下来。适用 MySQL 8.0 / 8.4,引擎默认 InnoDB。
先分清 1213 和 1205
同样是等锁,根因和处置完全不同。
- ERROR 1213 (40001):Deadlock found when trying to get lock。两事务互相持锁等待成环,InnoDB 检测后自动回滚其一。
- ERROR 1205 (HY000):Lock wait timeout exceeded。一事务等不到另一个释放锁,不一定成环。
1213 看事务加锁顺序和重试策略;1205 先定位阻塞者,通常是长事务、慢 SQL 或没提交的事务。拿一个超时参数应对所有锁问题,是不少事故最终打到连接池满的放大器。
复现一个典型现场
两个会话互相转账就能造出来。A 锁 id=1 再锁 id=2,B 反过来:
-- 会话 A
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
-- 会话 B
BEGIN;
UPDATE accounts SET balance = balance - 50 WHERE id = 2;
UPDATE accounts SET balance = balance + 50 WHERE id = 1;
时序卡住就报 1213。能稳定复现,问题就排查了一半。
看最近一次死锁现场
SHOW ENGINE INNODB STATUS\G
输出很长,重点抓几块:哪个事务在等哪把锁、各自持有哪把锁、执行的是哪条 SQL、最后谁被回滚。死锁频繁时,可以临时开 innodb_print_all_deadlocks=ON 把死锁信息写入错误日志,但这是排障手段,不是长期监控的替代。
线上排查优先走 performance_schema
SHOW ENGINE INNODB STATUS 只能看最近一次,而且是文本格式。并发堆叠时,用两张表可以直接把等待者与阻塞者的关系关联出来:
SELECT w.REQUESTING_ENGINE_TRANSACTION_ID AS waiting_trx,
w.BLOCKING_ENGINE_TRANSACTION_ID AS blocking_trx
FROM performance_schema.data_lock_waits AS w;
拿到事务 ID 后,连 data_locks 看细节:
SELECT ENGINE_TRANSACTION_ID, OBJECT_SCHEMA, OBJECT_NAME, INDEX_NAME, LOCK_MODE, LOCK_STATUS
FROM performance_schema.data_locks
WHERE LOCK_STATUS IN ('WAITING','GRANTED');
不要只看 LOCK_MODE,INDEX_NAME 也要盯。很多锁范围过大,本质是 SQL 没走合适索引,InnoDB 实际锁住的范围比预想的大。
三个落地修复方向
统一加锁顺序:转账按账户 id 从小到大拿锁:
SELECT id FROM accounts WHERE id IN (1,2) ORDER BY id FOR UPDATE;
ORDER BY 只表达意图,还要用 EXPLAIN 确认没有扩大扫描范围。
让锁更小更短:事务里不放远程调用、复杂计算、用户交互等待;更新条件要命中精确索引,没有索引的 UPDATE ... WHERE 会显著扩大锁范围。
业务必须能安全重试:死锁不代表数据库坏了。关键在于重试要幂等,不能重复扣款、发券或写流水。事务里只做可回滚的修改,外部通知放到事务提交后。遇到 1213 短暂退避后重试 1~3 次,超过次数记录现场。
排查顺序
先看报错是 1213 还是 1205;然后 SHOW ENGINE INNODB STATUS 拿最近一次现场;再用 data_lock_waits 定位等待者与阻塞者;接着回业务代码查事务边界和加锁顺序;最后才决定改索引、改 SQL、拆事务或补重试。这个顺序能绕开"一上来调大超时,把问题藏得更深"的坑。
锁问题不是靠等解决的,是靠缩短事务、缩小锁范围、统一加锁顺序和安全重试来解决。每次死锁都当成一次事务设计上的 review,记录下 SQL、锁等待图、加锁顺序、索引情况和重试结果。