死锁报错不说明sql写错,而是多个事务争抢同一组行时加锁顺序不一致;修复核心是收敛锁范围、统一加锁顺序、缩短锁持有时间,需用show engine innodb status\g定位latest detected deadlock段,比对两事务的sql、持有锁与等待锁,并检查索引是否匹配where条件、外键是否建索引、事务是否混入耗时操作。

死锁报错本身不说明SQL写错了,而是多个事务在争抢同一组行时加锁顺序不一致;修复核心是收敛锁范围、统一加锁顺序、缩短锁持有时间——光靠重试或重启数据库只会掩盖问题。
查清谁在等谁:用 SHOW ENGINE INNODB STATUS\G 定位真实死锁现场
这是唯一能看清“循环等待”全貌的方式。执行后重点盯住 LATEST DETECTED DEADLOCK 段,不是看错误消息,而是比对:
- 两个事务各自的
UPDATE或INSERT语句(注意 WHERE 条件值) - 各自持有的锁:
RECORD LOCKS on index PRIMARY of table `db`.`t1`这类描述 - 各自等待的锁:
WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS ... - 锁是否集中在外键列、非索引字段、或间隙锁(
gap before rec)上
如果看到一个事务锁着 PRIMARY,另一个锁着 idx_foreign,大概率是外键没索引;如果都锁在 idx_created_at 但条件是 WHERE status = ? AND created_at > ?,说明索引顺序错了。
索引顺序不对:联合索引字段顺序直接决定死锁频率
MySQL 行锁加在索引记录上,索引结构不匹配 WHERE 条件,就会退化为全扫描或间隙锁扩散。常见错误包括:
-
WHERE pool_id = ? AND status = ?却建了(pool_id, identifier, status)——identifier允许 NULL 时,InnoDB 可能无法跳过它,导致只用到pool_id前缀,锁住大量无关行 - 把范围查询字段(如
created_at > '2024-01-01')放在联合索引中间,后面字段彻底失效 - JOIN 场景下,
ON order_items.order_id = orders.id WHERE order_items.created_at > ?,却只在order_items上建了单列created_at索引,应改为(order_id, created_at)
正确做法:高频等值条件放最左,范围查询放最右。例如总按 pool_id 和 status 查询,就建 (pool_id, status, is_redeemed, identifier)。
事务内混着耗时操作:锁不会因为你正在发 HTTP 请求而释放
很多人以为 SELECT 安全、外部调用不影响数据库,其实只要事务没提交,所有已加的锁都一直占着。典型高危模式:
- 事务开头
SELECT ... FOR UPDATE查一堆数据,中间调用第三方 API 或做复杂计算,最后才UPDATE - 批量
UPDATE WHERE id IN (105, 101, 102)——IN子句不保证顺序,不同事务加锁顺序天然不一致 - 事务里做日志记录、缓存更新等非核心写操作,延长锁持有时间
安全拆分方式:SELECT id FROM t WHERE ... ORDER BY id 获取有序 ID 列表,再按主键升序分批 UPDATE ... WHERE id BETWEEN ? AND ?,每批控制在 100–500 行;非核心更新一律移出事务,异步处理。
外键列没走索引:隐式锁冲突的重灾区
外键约束本身不直接加锁,但 INSERT/UPDATE 时会触发对父表的 SELECT LOCK IN SHARE MODE 或从表扫描,若外键列无有效索引,就会全表加锁。检查方法:
- 执行
SHOW CREATE TABLE child_table,确认外键列(如order_id)是否是独立索引,或是复合索引的最左前缀 - 错误示例:
FOREIGN KEY (order_id) REFERENCES orders(id),但索引只有INDEX (status, order_id)—— 这不算有效索引 - 修复命令:
ALTER TABLE child_table ADD INDEX idx_order_id (order_id)
特别注意:级联操作(ON UPDATE CASCADE)更新父表主键时,会批量锁定所有从表匹配行,高并发下极易因锁顺序不一致死锁,线上慎用。
真正难的不是加索引或改 SQL,而是确认每个 WHERE 条件、每个 JOIN 的 ON 字段、每个外键列,是否都在执行计划中被实际用上——EXPLAIN 输出里的 key 列是否为空、type 是否为 const 或 range,比任何经验都可靠。











