MySQL InnoDB 锁机制详解(粒度 + SELECT FOR UPDATE + S锁/X锁)

一、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 条件字段添加合适的索引,配合行锁机制控制好锁粒度,解决了并发瓶颈问题。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值