MySQL锁锁的是索引结构,主键查询锁聚簇索引记录,二级索引查询需回表锁两级索引,无索引则全表扫描并锁每条聚簇索引记录;S锁与X锁可显式加在SELECT上,意向锁与DDL冲突会导致表级阻塞,间隙锁防止幻读但易引发插入性能瓶颈。

MySQL锁到底锁的是什么?
MySQL锁不是直接锁“数据行”或“表”本身,而是锁在**索引结构上**——尤其是InnoDB引擎。哪怕你没建任何索引,InnoDB也会自动生成一个隐藏的聚簇索引(GEN_CLUST_INDEX),所有行锁最终都落在这个索引记录上。
- 用主键查询(如
WHERE id = 100)→ 锁住聚簇索引中对应记录 - 用二级索引查询(如
WHERE name = 'Alice')→ 先锁二级索引记录,再锁对应的聚簇索引记录(回表锁) - 没走索引(
WHERE status = 'pending'且该字段无索引)→ 全表扫描,对**每条记录的聚簇索引都加锁**,等效于锁全表
共享锁(S锁)和排他锁(X锁)怎么手动加?
默认情况下,SELECT 不加任何锁;而 UPDATE/DELETE/INSERT 自动加 X 锁。但你也可以显式控制读锁行为:
SELECT * FROM users WHERE id = 5 LOCK IN SHARE MODE; -- 加 S 锁(其他事务还能读,不能写) SELECT * FROM users WHERE id = 5 FOR UPDATE; -- 加 X 锁(其他事务既不能读也不能写)
-
LOCK IN SHARE MODE:适合“查后校验、不改但需防别人改”的场景,比如库存预占前确认余额 -
FOR UPDATE:适合“查-改”原子操作,比如扣款前锁定账户行,避免并发超扣 - 两者都只在事务内生效,
COMMIT或ROLLBACK后自动释放
为什么有时候明明只查一行,却把整张表都卡住了?
这不是锁错了,而是**意向锁(IS/IX)+ 表级操作冲突**导致的。例如:
- 事务A执行
SELECT ... FOR UPDATE→ 拿到某行X锁,同时隐式持有表级IX锁 - 事务B此时执行
ALTER TABLE users ADD COLUMN xxx→ 需要获取表级X锁 - 但表级
X锁与已有的IX锁冲突 → 事务B被阻塞,看起来像“整表卡死”
这类问题在DDL频繁的环境(如开发库)特别常见。解决办法不是删锁,而是避开高峰期执行 DDL,或使用支持在线DDL的MySQL版本(如 8.0+ 的 ALGORITHM=INSTANT)。
行锁 ≠ 安全锁:间隙锁(Gap Lock)才是幻读的幕后推手
当你执行范围查询(如 SELECT * FROM orders WHERE amount > 100 FOR UPDATE),InnoDB不仅锁住现有记录,还会锁住“间隙”——即记录之间的空档。这就是间隙锁(Gap Lock),它防止其他事务在间隙中插入新行,从而避免幻读。
- 间隙锁不锁记录本身,只锁索引区间,比如 (100, 200) 这个开区间
- 唯一等值查询(
WHERE id = 5)且id是主键 → 只加记录锁(Record Lock),不加间隙锁 - 非唯一索引或范围条件 → 默认启用间隙锁(可被
innodb_locks_unsafe_for_binlog=ON关闭,但不推荐)
真正容易被忽略的点是:**间隙锁的存在让“看似安全”的范围查询,实际阻塞了大量插入操作,尤其在高并发写入场景下,它比行锁更容易成为性能瓶颈。**










