MySQL 的行级锁到底锁的是什么东西?
MySQL 的行级锁(Row-Level Lock)是InnoDB 存储引擎特有的锁机制,用于锁定表中的 “单行数据”,但本质上 “锁的是索引记录,而非物理行”,具体锁对象和机制如下:
# 1. 行级锁的核心:锁索引记录
InnoDB 采用 “聚簇索引” 结构,数据和索引紧密关联,行级锁的锁定对象是 “索引记录”,而非直接锁定物理行(磁盘上的行数据):
- 若表有显式主键(如
id),行级锁锁定 “主键索引记录”; - 若表无显式主键,但有非空唯一索引(如
phone),行级锁锁定 “非空唯一索引记录”; - 若表无主键和非空唯一索引,行级锁锁定 InnoDB 生成的隐藏
row_id索引记录; - 示例:执行
UPDATE user SET name='zhangsan' WHERE id=100,InnoDB 会锁定主键索引中id=100的记录,而非直接锁定物理行。
# 2. 行级锁的类型
InnoDB 的行级锁分为两类,对应不同的操作场景:
# (1)共享锁(S 锁,Shared Lock)
- 作用:允许事务读取数据,多个事务可同时持有同一行的 S 锁(“读共享”);
- 获取方式:执行
SELECT ... LOCK IN SHARE MODE; - 兼容性:与 S 锁兼容(多个事务可同时加 S 锁),与 X 锁互斥(加 S 锁后,其他事务无法加 X 锁,反之亦然);
- 使用场景:需要确保数据读取时不被修改(如统计数据时,防止其他事务修改数据导致统计结果不准确)。
# (2)排他锁(X 锁,Exclusive Lock)
- 作用:允许事务修改或删除数据,同一行数据仅允许一个事务持有 X 锁(“写排他”);
- 获取方式:执行
INSERT/UPDATE/DELETE或SELECT ... FOR UPDATE; - 兼容性:与 S 锁、X 锁均互斥(加 X 锁后,其他事务无法加 S 锁或 X 锁);
- 使用场景:修改或删除数据时,防止其他事务同时修改(如更新用户余额时,避免并发更新导致余额错误)。
# 3. 行级锁的锁定范围:Next-Key Lock(行锁 + 间隙锁)
在 RR 隔离级别下,InnoDB 为避免幻读,会将行级锁扩展为 “Next-Key Lock”,即 “行锁 + 间隙锁”,锁定范围包括 “已存在的行” 和 “行之间的间隙”:
- 行锁:锁定已存在的索引记录(如
id=100的行); - 间隙锁:锁定索引记录之间的间隙(如
id=100和id=200之间的间隙),防止其他事务在间隙中插入数据; - 示例:
- 表
user的id值为 100、200、300; - 执行
SELECT * FROM user WHERE id > 100 FOR UPDATE,InnoDB 会锁定:- 行锁:
id=200、id=300的索引记录; - 间隙锁:
100~200、200~300、300~+∞的间隙;
- 行锁:
- 其他事务无法插入
id=150、id=250等数据,避免幻读。
- 表
# 4. 行级锁的释放时机
行级锁由事务控制,事务提交(COMMIT)或回滚(ROLLBACK)时释放,而非语句执行完成后释放;
示例:
BEGIN; -- 事务启动 UPDATE user SET name='zhangsan' WHERE id=100; -- 加X锁 -- 此时,其他事务执行`UPDATE user SET name='lisi' WHERE id=100`会被阻塞 COMMIT; -- 事务提交,释放X锁 -- 其他事务可正常执行更新1
2
3
4
5
# 5. 行级锁的常见问题:间隙锁导致的死锁
由于 Next-Key Lock 的存在,若两个事务在同一间隙加锁,可能导致死锁:
- 示例:
- 事务 A:
UPDATE user SET name='a' WHERE id=150(锁定100~200间隙); - 事务 B:
UPDATE user SET name='b' WHERE id=160(尝试锁定100~200间隙,被事务 A 阻塞); - 事务 A:
UPDATE user SET name='c' WHERE id=160(尝试锁定100~200间隙,被事务 B 阻塞); - 两个事务互相等待,导致死锁;
- 事务 A:
- 解决方案:
- 尽量使用主键或唯一索引作为查询条件,减少间隙锁的范围;
- 控制事务大小,缩短事务执行时间,减少锁持有时间;
- 避免在同一间隙中并发执行更新操作。
上次更新: 12/30/2025