MySQL 行级锁机制详解
InnoDB 引擎支持行级锁,而 MyISAM 不支持。顾名思义,行锁针对的是数据表中的具体行记录。例如事务 A 更新了一行,此时事务 B 若也要更新同一行,必须等待事务 A 完成。
普通 SELECT 语句属于快照读,不会加锁。若需在查询时加锁(锁定读),可使用 SELECT ... LOCK IN SHARE MODE 或 SELECT ... FOR UPDATE。
行锁类型
Record Lock(记录锁)
Record Lock 锁住一条具体记录,分为 S 锁(共享)和 X 锁(排他)。
- S 锁兼容 S 锁,但不兼容 X 锁。
- X 锁不兼容任何锁。
示例:
BEGIN;
SELECT * FROM t_test WHERE id = 1 FOR UPDATE;
此操作对主键 id=1 的记录加 X 型记录锁,其他事务无法修改该行,直到事务提交释放锁。
Gap Lock(间隙锁)
存在于可重复读隔离级别,目的是解决幻读问题。它锁住索引记录之间的间隙。 假设表中存在 id 为 (3, 5) 的间隙锁,其他事务无法插入 id=4 的记录。
Next-Key Lock(临键锁)
Next-Key Lock 是 Record Lock + Gap Lock 的组合,锁定一个范围并包含记录本身。 假设范围 id 为 (3, 5],其他事务既不能插入 id=4,也不能修改 id=5。
注意:如果一个事务获取了 X 型的 Next-Key Lock,另一个事务在相同范围获取 X 型锁时会被阻塞。
SELECT ... FOR UPDATE 的作用
在默认的可重复读隔离级别下,普通 SELECT 基于 MVCC 读取历史快照,不加锁。这虽保证了性能,但在特定场景(如库存扣减)会导致超卖。
场景演示:
- 商品 A 库存为 1。
- 事务 A 和 B 同时查询库存,均读到 1。
- 事务 A 下单扣减并提交,库存变为 0。
- 事务 B 也执行扣减,最终库存变为 -1。
解决方案:
使用 SELECT ... FOR UPDATE 锁定记录。
- 事务 A 查询并锁定库存(加排他锁)。
- 事务 B 尝试查询被阻塞,直到事务 A 释放锁。
- 事务 A 提交后,事务 B 获得锁,读取到更新后的库存 0,从而阻止后续错误扣减。
MySQL 是如何加行级锁的?
加锁的对象是索引,基本单位是 Next-Key Lock。但在特定场景下会退化。
实验环境:
CREATE TABLE `user` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(30) utf8mb4_unicode_ci ,
`age` ,
(`id`),
KEY `index_age` (`age`) BTREE
) ENGINEInnoDB CHARSETutf8mb4 utf8mb4_unicode_ci;

