x锁(排他锁)和s锁(共享锁)锁定的是数据行的索引记录而非表或库;x锁允许当前事务读写该记录且排斥其他任何锁,s锁允许多个事务并发读但要求无x锁存在。

什么是X锁和S锁,它们到底锁了什么
X锁(排他锁)和S锁(共享锁)不是锁“表”或“库”,而是锁“数据行的索引记录”。哪怕你 SELECT * FROM t WHERE id = 5 这样查,InnoDB 也会在 id = 5 对应的聚簇索引记录上加 S 锁(如果启用了 READ COMMITTED 或更高隔离级别且没走索引,则可能锁全表——但那是另一回事)。X 锁则意味着:当前事务能读能写这条记录,其他任何事务不能对它加 X 锁或 S 锁。
- 加 S 锁的前提是:没有事务在这条记录上持有 X 锁
- 加 X 锁的前提是:没有事务在这条记录上持有 X 锁或 S 锁
- 锁是加在“索引项”上的,不是加在“值”或“字段”上;没索引的
WHERE条件容易升级成间隙锁或表锁
UPDATE/DELETE 自动加 X 锁,但 SELECT 不一定加锁
UPDATE 和 DELETE 语句默认会为扫描到的每条匹配记录加上 X 锁(即使最终没修改数据,只要命中了,锁就上了)。而普通 SELECT 默认不加任何锁,除非显式写 SELECT ... FOR UPDATE(加 X 锁)或 SELECT ... LOCK IN SHARE MODE(加 S 锁)。
-
SELECT ... FOR UPDATE在可重复读下还会锁住“间隙”,防止幻读;但READ COMMITTED下只锁记录本身,不锁间隙 - 如果
WHERE条件没走索引,InnoDB 可能退化为锁全表(尤其在READ COMMITTED下仍可能锁所有聚簇索引记录) -
INSERT会对插入位置的间隙加插入意向锁(Insert Intention Lock),它和间隙锁冲突,但和 S/X 锁不直接冲突
常见死锁场景:两个事务交叉申请 X 锁
最典型的死锁不是“一个事务卡住”,而是两个事务互相等待对方释放 X 锁。比如事务 A 先更新 id = 1,再试图更新 id = 2;事务 B 正好相反。只要执行顺序稍有交错,InnoDB 就会检测到循环等待并回滚其中一个。
- 死锁检测开销小,但频繁发生说明业务逻辑存在资源争用模式
- 不要依赖“先查再更”的方式做并发控制:
SELECT ... FOR UPDATE必须覆盖所有后续要改的行,否则可能漏锁 -
SHOW ENGINE INNODB STATUS输出里的WAITING FOR THIS LOCK TO BE GRANTED行,后面跟着的lock_mode X locks rec but not gap能帮你确认锁类型和范围
如何验证当前 SQL 加了什么锁
没有“实时查看某条记录被谁锁了”的标准命令,但可以通过 INFORMATION_SCHEMA.INNODB_TRX、INNODB_LOCKS(MySQL 5.7)、INNODB_LOCK_WAITS 查到正在等待或持有的锁信息。MySQL 8.0 后 INNODB_LOCKS 被移除,得靠 performance_schema.data_locks。
- 执行前先开两个会话,分别
BEGIN,再各自执行带锁语句,然后在第三会话查performance_schema.data_locks - 注意
LOCK_DATA字段显示的是被锁的索引值(如5),不是主键名;如果是间隙锁,会显示类似5, 10的区间 -
LOCK_MODE值为X或S表示记录锁;X,gap before rec表示间隙锁;X,insert intention是插入意向锁
锁的本质是协调对同一索引记录的并发访问,而不是控制“哪条 SQL 能跑”。真正难的不是理解 X/S 区别,而是判断你的查询是否命中了索引、是否触发了间隙锁、以及事务中锁的范围是否覆盖了所有可能冲突的路径。











