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关闭,但不推荐)
真正容易被忽略的点是:**间隙锁的存在让“看似安全”的范围查询,实际阻塞了大量插入操作,尤其在高并发写入场景下,它比行锁更容易成为性能瓶颈。**
