一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

MySQL面试:update卡住了,是锁等待还是死锁?

时间:2026-09-02 18:54:50 编辑:袖梨 来源:一聚教程网

处理MySQL面试:update卡住了,是锁等待还是死锁?这类问题时,先确认目标场景,再按步骤核对配置或玩法细节。

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

第一张表给出等待锁和阻塞锁的对应关系,第二张表补上事务、表、索引和具体锁数据。

我会先确认三件事:

  1. 是不是同一张业务表和同一个索引范围。
  2. 阻塞方是在执行很长的事务,还是已经进入空闲状态却没提交。
  3. 等待链是单向的,还是两边互相指向。

死锁发生后,再补一条:

SHOW ENGINE INNODB STATUSG

这里会保留最近一次 InnoDB 检测到的死锁。别把它当历史档案,它只够还原最近那一例。高频问题还得把应用错误码、慢 SQL 和事务耗时一起记下来。

老项目里有人会去查 INFORMATION_SCHEMA.INNODB_LOCKS。在 MySQL 8 里,优先看 performance_schema.data_locksdata_lock_waits,别把旧版本的排查命令直接搬进答案。

修复不是把超时调大

遇到这类问题,最有效的改法通常很朴素:统一拿锁顺序。

比如一次业务要操作多个库存行,就先把 id 排序,所有入口都按从小到大的顺序更新。这样 T1 和 T2 不会一个先锁 17、另一个先锁 42,也就很难构成等待环。

还有两个容易被忽略的点:

  1. 事务里别夹 HTTP、消息发送、文件上传这些慢操作。锁拿着等外部系统,等待队列会越拖越长。
  2. 业务要能重试,但先把整段业务事务回滚干净。死锁错误和锁等待超时都不能靠“原地再执行一次 SQL”糊过去,扣库存、写订单这类动作还要有幂等键。

如果面试官继续问“为什么索引也会影响死锁”,就顺着这条线答:InnoDB 锁的是索引记录和范围。缺索引或范围条件过大,会让一次更新碰到更多记录,锁顺序和锁持有时间都会变得更难预测。

面试现场,把 T1 和 T2 的两条 update 按执行顺序画出来,锁等待和死锁就不容易答混。

热门栏目