MySQL锁锁的是索引结构,主键查询锁聚簇索引记录,二级索引查询需回表锁两级索引,无索引则全表扫描并锁每条聚簇索引记录;S锁与X锁可显式加在SELECT上,意向锁与DDL冲突会导致表级阻塞,间隙锁防止幻读但易引发插入性能瓶颈。
MySQL锁不是直接锁“数据行”或“表”本身,而是锁在**索引结构上**——尤其是InnoDB引擎。哪怕你没建任何索引,InnoDB也会自动生成一个隐藏的聚簇索引(GEN_CLUST_INDEX),所有行锁最终都落在这个索引记录上。
WHERE id = 100)→ 锁住聚簇索引中对应记录WHERE name = 'Alice')→ 先锁二级索引记录,再锁对应的聚簇索引记录(回表锁)WHERE status = 'pending' 且该字段无索引)→ 全表扫描,对**每条记录的聚簇索引都加锁**,等效于锁全表默认情况下,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)+ 表级操作冲突**导致的。例如:
SELECT ... FOR UPDATE → 拿到某行X锁,同时隐式持有表级 IX 锁ALTER TABLE users ADD COLUMN xxx → 需要获取表级 X 锁X 锁与已有的 IX 锁冲突 → 事务B被阻塞,看起来像“整表卡死”这类问题在DDL频繁的环境(如开发库)特别常见。解决办法不是删锁,而是避开高峰期执行 DDL,或使用支持在线DDL的MySQL版本(如 8.0+ 的 ALGORITHM=INSTANT)。
当你执行范围查询(如 SELECT * FROM orders WHERE amount > 100 FOR UPDATE),InnoDB不仅锁住现有记录,还会锁住“间隙”——即记录之间的空档。这就是间隙锁(Gap Lock),它防止其他事务在间隙中插入新行,从而避免幻读。
WHERE id = 5)且 id 是主键 → 只加记录锁(Record Lock),不加间隙锁
innodb_locks_unsafe_for_binlog=ON 关闭,但不推荐)真正容易被忽略的点是:**间隙锁的存在让“看似安全”的范围查询,实际阻塞了大量插入操作,尤其在高并发写入场景下,它比行锁更容易成为性能瓶颈。**