一、InnoDB 存储引擎的锁粒度有哪些?
InnoDB 支持多种锁粒度,主要包括:
| 锁类型 | 锁粒度说明 | 应用场景 |
|---|---|---|
| 表锁(Table Lock) | 整张表,最粗粒度 | 非 InnoDB 引擎或显式 LOCK TABLE |
| 行锁(Record Lock) | 单条记录 | 主键 / 唯一索引精确匹配 |
| 间隙锁(Gap Lock) | 行之间的“间隙”,不锁实际数据行 | 范围查询防止插入幻读 |
| 临键锁(Next-Key Lock) | 行锁 + 间隙锁 | InnoDB 默认范围查询锁类型 |
| 意向锁(Intention Lock) | 表级标记,用于判断是否存在行锁 | 不会阻塞,只起协调作用 |
二、InnoDB 支持哪两种事务级锁?(S锁和X锁)
| 锁类型 | 作用 | 典型语句 |
|---|---|---|
| ✅ 共享锁(S锁) | 多个事务可同时读,不能写 | SELECT ... LOCK IN SHARE MODE |
| ✅ 排它锁(X锁) | 只允许当前事务读写,其他事务不能访问 | SELECT ... FOR UPDATE、UPDATE、DELETE |
三、SELECT FOR UPDATE 是否会触发行锁?与索引有何关系?
✅ 情况一:命中主键/唯一索引(精准锁)
SELECT * FROM user WHERE id = 10 FOR UPDATE;
-
id为主键或唯一索引 -
触发行锁(Record Lock),只锁住这一行
-
多事务不会互相阻塞(除非访问相同行)
⚠️ 情况二:未命中索引,或索引失效
SELECT * FROM user WHERE name = 'Tom' FOR UPDATE;
-
若
name没有索引,SQL 会执行 全表扫描 -
会锁住表中所有行(即每一行加上行锁)
-
实际上造成了“退化式的全表行锁”,严重影响并发
✅ 情况三:范围条件 + 索引命中(临键锁)
SELECT * FROM user WHERE id > 100 FOR UPDATE;
-
索引命中 + 范围查询
-
会触发 Next-Key Lock(临键锁)
锁住:-
满足条件的记录
-
满足条件之间的间隙
-
可能还会锁住末尾间隙
-
四、可视化理解 🔍(SELECT FOR UPDATE 加锁范围)
| SQL 语句 | 是否触发行锁 | 加锁粒度 | 并发影响 |
|---|---|---|---|
SELECT * FROM user WHERE id = 1 FOR UPDATE | ✅ 是 | 行锁(Record Lock) | 只锁住该行,影响最小 |
SELECT * FROM user WHERE name = 'Tom' FOR UPDATE | ❌(索引无) | 全表所有记录行锁 | 锁整个表,严重影响并发 |
SELECT * FROM user WHERE id > 1 FOR UPDATE | ✅ 是 | 临键锁(Next-Key Lock) | 锁满足条件的多行 + 间隙,防幻读 |
五、事务并发锁行为对比总结(S锁 vs X锁)
| 操作类型 | A 加 S 锁 | A 加 X 锁 |
|---|---|---|
| B 再加 S 锁 | ✅ 可并发读 | ❌ 阻塞 |
| B 加 X 锁(更新) | ❌ 阻塞 | ❌ 阻塞 |
| B 只读查询 | ✅ 可读 | ❌ 被阻塞(锁住行) |
| B 插入同区间记录 | ✅(不冲突) | ❌ 被 Next-Key Lock 阻塞 |
六、总结答题模板(面试用)
在 InnoDB 存储引擎中,锁粒度最小支持到行级,包括行锁、间隙锁、临键锁等。而事务级别的锁分为共享锁(S锁)和排它锁(X锁)。使用
SELECT FOR UPDATE时,如果条件字段命中了主键或唯一索引,则触发行锁,只锁定目标记录。如果没有索引,则会锁住全表每一行,影响并发性能。此外,范围查询会加临键锁,锁住范围内记录及其间隙,防止幻读。DML 操作如 UPDATE/DELETE 本身会隐式加排它锁。实际项目中,我们要结合事务隔离级别、加锁策略和索引设计综合考虑锁粒度与并发控制。
✅ 补充一:事务加锁冲突与死锁演示(事务 A/B)
🎯 场景:事务 A、B 加锁顺序不同导致死锁
-- 事务A
START TRANSACTION;
SELECT * FROM user WHERE id = 1 FOR UPDATE;
-- 模拟等待
-- 然后执行:
SELECT * FROM user WHERE id = 2 FOR UPDATE;
-- 事务B(并发)
START TRANSACTION;
SELECT * FROM user WHERE id = 2 FOR UPDATE;
-- 然后执行:
SELECT * FROM user WHERE id = 1 FOR UPDATE;
🔥 结果:
-
两个事务互相等待,触发死锁
-
InnoDB 会自动检测并回滚其中一个事务
✅ 补充二:如何查看当前锁和死锁信息?
1️⃣ 查看当前锁等待情况:
SHOW ENGINE INNODB STATUS \G
输出中查找:
-
Latest detected deadlock(死锁信息)
-
TRANSACTIONS 小节中看到被阻塞和等待锁的线程
-
Waiting for lock / holding lock 行显示加锁行为
2️⃣ 查询锁等待视图(MySQL 5.7+)
-- 当前锁等待事务
SELECT * FROM information_schema.innodb_lock_waits;
-- 当前正在加锁事务
SELECT * FROM information_schema.innodb_locks;
实战中,你可以联合
performance_schema.threads找到被阻塞线程对应的 SQL。
✅ 补充三:EXPLAIN 查看索引命中与是否触发行锁的实战技巧
示例 SQL:
EXPLAIN SELECT * FROM user WHERE name = 'Tom' FOR UPDATE;
重点观察:
| 字段 | 说明 |
|---|---|
| type | 若为 ALL,说明是全表扫描,索引未命中 |
| key | 哪个索引被使用(若为 NULL 表示没用索引) |
| rows | 预计扫描行数,多表示效率差、锁多 |
| extra | 包含 Using index 为覆盖索引;若无表示全表 |
实例输出(未建索引):
type: ALL
key: NULL
rows: 10000
extra: Using where; Using temporary
⚠️ 出现
type: ALL就要警惕,这种情况下SELECT FOR UPDATE会锁住整张表的每一行。
✅ 面试延展回答建议(进阶)
我在项目中遇到过因为 SELECT FOR UPDATE 没有命中索引导致锁表的问题,当时通过 EXPLAIN 发现查询走的是全表扫描(type: ALL),进而导致所有行都被锁住,引发后续事务阻塞。排查时我们使用了
SHOW ENGINE INNODB STATUS查看死锁堆栈,结合innodb_locks等信息定位到问题语句。最终通过为 WHERE 条件字段添加合适的索引,配合行锁机制控制好锁粒度,解决了并发瓶颈问题。

4779

被折叠的 条评论
为什么被折叠?



