很多数据库面试题会问索引和事务,但真实项目里更容易让人措手不及的是锁等待。接口偶发卡住,数据库 CPU 不高,慢日志里出现更新语句,线程池开始堆积,最后用户看到的只是“提交失败”。面试官如果追问锁等待,其实是在看你能不能把 SQL、事务和业务流程连起来。
锁等待不是一句“有事务没提交”就能解释完。它可能来自事务范围过大,更新条件没走索引,多个接口更新顺序不一致,或者一个看似普通的查询在当前读下加了锁。
更新条件没走索引,锁会被放大
很多人以为 update 只会锁住命中的那一行。但如果条件没有合适索引,数据库需要扫描更多记录,锁范围就可能被放大。比如按状态更新一批订单,状态字段区分度低,扫描范围很大,别的事务更新同一批数据时就容易等待。
所以锁等待排查要先看执行计划。不是只有 select 需要 Explain,update 和 delete 的条件也要看是否能精准定位记录。一个没有命中索引的更新,在小数据量测试环境里不明显,上线后可能变成长时间持锁。
事务里做了太多事
事务应该保护必须一致的数据库状态,而不是包住整段业务流程。常见问题是:事务里调用外部接口,事务里做复杂计算,事务里循环处理大量记录,或者事务里等待用户态逻辑。事务越长,持锁时间越长,别人等待的概率越高。
更稳的做法是把事务缩短到关键写入段。外部调用能前置就前置,不能前置就用处理中状态和补偿任务收敛;批量处理要拆小批次;非关键日志、通知、统计不要放在核心事务里拖住锁。
死锁往往来自顺序不一致
两个事务分别更新 A 再更新 B,另一个更新 B 再更新 A,就可能互相等待形成死锁。数据库会选择回滚其中一个事务,但这不代表问题已经解决。如果业务层没有重试和幂等,用户可能看到失败,或者补偿任务再次造成混乱。
面试里可以主动提更新顺序:涉及多行、多表、多资源时,尽量保持固定顺序;写入失败后按错误类型判断是否可重试;重试必须保证幂等,不能因为一次死锁回滚就重复生成流水或重复发送消息。
排查要看证据,不要猜
线上遇到锁等待,可以从数据库会话、锁等待信息、慢日志、事务执行时间和应用线程栈一起看。应用侧如果大量线程卡在同一个 DAO 方法,数据库侧又能看到某个长事务持锁,方向就清楚了。
修复时也不要只把超时时间调大。超时调大只是让请求等得更久,可能进一步占满线程池。真正要处理的是索引、事务范围、更新顺序和失败重试。能把这些讲清楚,数据库题就从“会背事务隔离”变成了“能处理线上写入稳定性”。
修复锁等待以后,还要观察一段时间。只改 SQL 或缩短事务不代表问题完全消失,要继续看锁等待次数、事务平均时长、慢 SQL、接口超时和线程池排队。如果发布后这些指标同步下降,才能说明修复有效。
面试中把“修复后验证”讲出来很加分。很多候选人只讲怎么定位,却不讲怎么证明问题被解决。真实线上排查必须闭环,否则只是一次猜测。