mysql中InnoDB的行级锁与共享锁、排他锁的区别

来源:这里教程网 时间:2026-02-28 20:42:39 作者:

行级锁不是一种独立锁,而是锁的粒度

很多人误以为“行级锁”和“共享锁/排他锁”是并列关系,其实不是:行级锁描述的是锁作用范围(一行数据),而共享锁(S锁)和排他锁(X锁)描述的是锁的行为语义(读 or 写)。InnoDB 的行级锁,本质上就是加在索引记录上的 S 锁或 X 锁。

也就是说:

SELECT * FROM t WHERE id = 10 LOCK IN SHARE MODE
加的是「行级 + 共享锁」;
UPDATE t SET name='a' WHERE id = 10
默认加的是「行级 + 排他锁」。

没索引?InnoDB 找不到精准的索引项可锁,就会退化为表级锁(哪怕你只改一行)
WHERE
条件命中了二级索引?锁会加在该二级索引项上,且可能额外加主键索引上的锁(如当前读触发聚簇索引查找)
事务隔离级别影响锁行为:比如
READ COMMITTED
下,普通
SELECT
不加锁,但
SELECT ... FOR UPDATE
仍加行级 X 锁

共享锁与排他锁的兼容性决定并发表现

真正影响你程序是否卡住、是否死锁的,是 S 锁和 X 锁之间的兼容规则,而不是“是不是行级”。只要两个事务试图对同一行加不兼容的锁,后到的就会阻塞。

关键兼容表(仅针对同一行):

          | S锁 | X锁
--------|-----|-----
S锁     | ✅  | ❌
X锁     | ❌  | ❌
SELECT ... LOCK IN SHARE MODE
→ 加 S 锁 → 其他事务还能加 S 锁(并发读),但不能加 X 锁(阻塞 UPDATE/DELETE)
SELECT ... FOR UPDATE
→ 加 X 锁 → 其他事务既不能加 S 锁也不能加 X 锁(完全互斥)
INSERT/UPDATE/DELETE 自动加 X 锁,无需显式写
FOR UPDATE
,但要注意:如果
WHERE
条件没走索引,X 锁会升级为表锁

意向锁(IS/IX)是行锁和表锁共存的关键桥梁

当你执行

SELECT ... FOR UPDATE
,InnoDB 实际做了两件事:先对整张表加
IX
(意向排他锁),再对命中的行加 X 锁。这个
IX
是表级锁,但它本身不阻塞其他事务——它只起“声明作用”:告诉别人“我马上要对某些行加 X 锁了”。

没有
IX
,当有人想对整张表加表级写锁(
LOCK TABLES t WRITE
),就得逐行检查有没有行锁,性能崩盘
IS
IX
之间完全兼容,所以多个事务可以同时声明“我要读几行”或“我要改几行”,互不影响
IX
与表级
X
锁互斥,
IS
与表级
X
锁也互斥——这才是表锁能快速判断“有人正在操作行”的原理

容易被忽略的坑:锁不是加在“数据行”上,而是加在“索引项”上

这是最常踩的坑。InnoDB 行锁永远绑定索引,哪怕你

SELECT * FROM t WHERE name = 'alice'
,如果
name
没有索引,InnoDB 就无法定位具体哪几行,只能全表扫描并给所有扫描过的行(甚至整个聚簇索引)加锁——结果等效于表锁。

查执行计划:
EXPLAIN SELECT ... FOR UPDATE
,确认
type
const
/
ref
,不是
ALL
index
唯一索引和普通索引的锁范围不同:唯一索引等值查询只锁单条记录(Record Lock);非唯一索引等值查询会锁间隙(Gap Lock),防止幻读 显式开启事务后,哪怕只执行一条
SELECT ... FOR UPDATE
,锁也会持续到
COMMIT
ROLLBACK
,不是语句结束就释放

锁的复杂性不在概念多,而在它藏在索引结构、隔离级别、语句写法的交界处。调错一次,比写十次业务逻辑还耗神。

相关推荐