处理MySQL面试:update卡住了,是锁等待还是死锁?这类问题时,先确认目标场景,再按步骤核对配置或玩法细节。
我见过不少回答一上来就背“行锁、表锁、间隙锁”。面试官通常不满足,他会换成一句更具体的话:
“两个订单状态更新都卡住了,你怎么判断是锁等待,还是已经死锁?”
先把这段事务摆出来。假设库存表只有两条热点记录:
CREATE TABLE inventory (id BIGINT PRIMARY KEY,
stock INT NOT NULL
) ENGINE=InnoDB;
事务 T1 先扣 17,再扣 42:
BEGIN;UPDATE inventory
SET stock = stock - 1
WHERE id = 17;
-- 这里不要夹远程调用或长时间业务逻辑
UPDATE inventory
SET stock = stock - 1
WHERE id = 42;
COMMIT;
另一条请求 T2 恰好反过来,先更新 42,再更新 17。麻烦不是 SQL 慢,而是两个事务都先拿到一把锁,又都想拿对方那把。

锁等待只有一条等待边。
T1 已经锁住 id=17,T2 也要更新 id=17。这时 T2 的 update 会停住。只要 T1 提交或回滚,T2 就能继续。等得太久才可能报 Lock wait timeout exceeded。
死锁是一个环。
T1 拿着 17 等 42,T2 拿着 42 等 17。谁都不会主动释放,InnoDB 检测到这个环以后会挑一个事务回滚,应用侧通常看到 Deadlock found when trying to get lock。这不是“两个请求都等到超时”,而是数据库主动打断了其中一个。
面试里把这两个结果说开,后面的排查才不会跑偏。
MySQL 8 可以先看 Performance Schema 里的两张表:
SELECT *FROM performance_schema.data_lock_waitsG
SELECT
ENGINE_LOCK_ID,
ENGINE_TRANSACTION_ID,
OBJECT_SCHEMA,
OBJECT_NAME,
INDEX_NAME,
LOCK_TYPE,
LOCK_MODE,
LOCK_STATUS,
LOCK_DATA
FROM performance_schema.data_locksG
第一张表给出等待锁和阻塞锁的对应关系,第二张表补上事务、表、索引和具体锁数据。
我会先确认三件事:
死锁发生后,再补一条:
SHOW ENGINE INNODB STATUSG这里会保留最近一次 InnoDB 检测到的死锁。别把它当历史档案,它只够还原最近那一例。高频问题还得把应用错误码、慢 SQL 和事务耗时一起记下来。
老项目里有人会去查 INFORMATION_SCHEMA.INNODB_LOCKS。在 MySQL 8 里,优先看 performance_schema.data_locks 和 data_lock_waits,别把旧版本的排查命令直接搬进答案。
遇到这类问题,最有效的改法通常很朴素:统一拿锁顺序。
比如一次业务要操作多个库存行,就先把 id 排序,所有入口都按从小到大的顺序更新。这样 T1 和 T2 不会一个先锁 17、另一个先锁 42,也就很难构成等待环。
还有两个容易被忽略的点:
如果面试官继续问“为什么索引也会影响死锁”,就顺着这条线答:InnoDB 锁的是索引记录和范围。缺索引或范围条件过大,会让一次更新碰到更多记录,锁顺序和锁持有时间都会变得更难预测。
面试现场,把 T1 和 T2 的两条 update 按执行顺序画出来,锁等待和死锁就不容易答混。