SQL事务与锁的核心是保障一致性:事务通过BEGIN/COMMIT控制生命周期,InnoDB用记录锁、间隙锁、临键锁实现不同隔离级别下的并发控制,RR靠临键锁防幻读,RC仅用记录锁导致幻读,死锁由循环等待引发并自动检测回滚。

面试中问到 SQL 事务和锁机制,核心是考察你是否真正理解“一致性”背后的实现逻辑,而不是只会背 ACID 或隔离级别定义。关键要能说清:事务怎么开始、怎么结束、哪些操作会触发锁、不同隔离级别下锁的范围和持续时间有何差异、以及常见并发问题(如脏读、幻读)到底由什么锁行为导致。
事务的生命周期与隐式/显式控制
很多候选人混淆了“自动提交”和“事务开启”的关系。MySQL 默认 autocommit=1,意味着每条 DML(INSERT/UPDATE/DELETE)单独成一个事务;但只要执行 BEGIN、START TRANSACTION 或 SET autocommit = 0,后续语句就进入同一个事务,直到 COMMIT 或 ROLLBACK 显式结束。
注意:SELECT 不触发事务开启(除非加 FOR UPDATE / LOCK IN SHARE MODE),也不受 autocommit 影响;但它是隔离级别的作用对象——比如在 REPEATABLE READ 下,第一次 SELECT 会建立快照,后续同事务内 SELECT 都读该快照,哪怕其他事务已提交修改。
行锁、间隙锁与临键锁:InnoDB 的三把锁
InnoDB 的行锁不是“锁某一行”,而是“锁索引记录”。这直接决定锁的粒度和范围:
-
记录锁(Record Lock):锁定索引中的单个记录,如主键等值查询
WHERE id = 10 -
间隙锁(Gap Lock):锁定索引记录之间的“间隙”,防止插入新记录,如
WHERE id BETWEEN 5 AND 10在 RR 级别下会锁 (5,10) 这个区间 -
临键锁(Next-Key Lock):记录锁 + 间隙锁的组合,即“左开右闭”区间,是 RR 级别的默认锁。例如
WHERE age > 25可能锁住 (25, +∞),从而避免幻读
重点:唯一索引等值查询(含主键)只加记录锁;非唯一索引等值查询、范围查询,在 RR 下必然加临键锁;而 RC 级别下只加记录锁,不加间隙锁——这也是 RC 无法解决幻读、但写冲突更少的原因。
隔离级别如何影响锁行为与并发问题
四个标准隔离级别不是孤立概念,必须结合锁来解释现象:
- READ UNCOMMITTED:不加读锁,可能读到未提交数据(脏读),写操作仍加行锁
- READ COMMITTED:每次 SELECT 都新建快照,可避免脏读;但不可重复读(同事务两次 SELECT 同一记录,结果不同);间隙锁被禁用,幻读可能发生(如新增记录)
- REPEATABLE READ(MySQL 默认):首次 SELECT 建立快照,后续读都基于它,解决不可重复读;通过临键锁阻止区间插入,基本解决幻读(注意:仅针对当前读,如 SELECT ... FOR UPDATE;快照读仍看不到新插入)
- SERIALIZABLE:所有 SELECT 隐式转为 SELECT ... LOCK IN SHARE MODE,强制加读锁,彻底串行化,但性能最差
面试常考对比题:为什么 RR 能防幻读,RC 不能?答案不在“是否可重复读”,而在“RR 用临键锁锁住范围,RC 只锁命中的行”。
死锁检测与避免:不只是“两个事务互相等待”
死锁本质是资源循环等待。InnoDB 每次加锁前会做死锁检测(wait-for graph),一旦发现立即回滚其中一个事务(通常代价小的那个),抛出 Deadlock found when trying to get lock。
高频诱因和建议:
- 多个事务以不同顺序更新同一组行(如事务 A 先更新 user 表再更新 order 表,事务 B 反过来)→ 统一操作顺序
- 在长事务中执行大量无关查询后才更新,扩大锁持有时间 → 拆分事务,只在必要时开启
- 使用非唯一索引条件更新,导致锁住更大范围 → 尽量走主键或唯一索引
- 忽略锁超时参数 innodb_lock_wait_timeout(默认 50 秒),导致应用卡顿 → 应用层需捕获 Deadlock 异常并重试
不复杂但容易忽略:死锁日志(SHOW ENGINE INNODB STATUS)里会明确列出每个事务持有哪些锁、等待哪个锁、涉及哪些行——这是定位问题的第一手材料。










