锁的是索引项而非数据行;InnoDB行级锁实际作用于聚簇索引或二级索引记录,无索引时锁全表索引项,导致等效表锁。

锁到底锁的是什么?不是数据行,而是索引项
MySQL 的行级锁(尤其是 InnoDB)**不直接锁数据行,而是锁索引上的记录**。哪怕你没建任何索引,InnoDB 也会创建一个隐藏的聚簇索引(GEN_CLUST_INDEX),所有行锁都落在这个索引结构上。
这意味着:
- 用主键(如
WHERE id = 100)更新,只在聚簇索引上加一把X锁; - 用二级索引(如
WHERE name = 'Alice')更新,则先在name索引上加X锁,再回表到聚簇索引上对对应主键加X锁——共两把锁; - 如果
WHERE条件没走索引(例如WHERE status+0 = 1或LIKE '%abc'),InnoDB 无法精准定位,会退化为扫描全表,并对**每条匹配记录的聚簇索引项加锁**,极端情况下等效于锁整张表。
共享锁 vs 排他锁:什么时候该用 LOCK IN SHARE MODE?
S 锁(共享锁)和 X 锁(排他锁)是底层基础,但日常开发中你几乎不会手动加 S 锁——除非你需要显式阻塞其他写操作,同时允许并发读。
典型场景是「防超卖」中的读-改-写闭环:
- 普通
SELECT是一致性读(MVCC),不加锁,可能读到旧库存; - 用
SELECT stock FROM goods WHERE id = 123 LOCK IN SHARE MODE,能确保读到当前最新值,且阻止其他事务对这行加X锁(即不能扣减); - 后续紧跟
UPDATE goods SET stock = stock - 1 WHERE id = 123,此时已持有S锁,InnoDB 会自动升级为X锁完成更新; - 若跳过
LOCK IN SHARE MODE直接UPDATE,虽也加X锁,但中间存在窗口:两次请求可能同时读到 stock=1,然后都执行 -1 → 变成 -1。
别被“行锁”骗了:为什么 UPDATE 有时卡住整张表?
InnoDB 的“行锁”只是默认行为,**是否真锁单行,完全取决于执行计划是否命中索引**。
常见踩坑点:
-
UPDATE users SET status = 1 WHERE phone LIKE '%138%':无索引 + 模糊前缀 → 全表扫描 → 对每行聚簇索引加X锁 → 等效表锁; -
UPDATE orders SET paid = 1 WHERE created_at > '2025-01-01':若created_at无索引,同样锁全表; - 复合查询中用了函数:
WHERE DATE(create_time) = '2025-01-01'→ 索引失效 → 锁范围扩大; - 即使有索引,若统计信息过期(
ANALYZE TABLE未执行),优化器也可能误判为全表扫描。
全局锁 FLUSH TABLES WITH READ LOCK 的真实代价
它确实能保证备份一致性,但代价是整个实例只读——所有 DML、DDL、甚至 COMMIT 都会被阻塞,业务写入直接挂起。
所以生产环境慎用,尤其高流量系统:
-
mysqldump --single-transaction是更优解(依赖 MVCC,仅对 InnoDB 有效),它不加全局锁,靠事务快照保证一致性; -
FLUSH TABLES WITH READ LOCK主要用于 MyISAM 表或混合引擎库的备份; - 执行后必须配对
UNLOCK TABLES,否则锁一直挂着——曾有案例因忘记解锁,导致线上订单积压数小时; - 注意:它不阻塞
SELECT,但会阻塞任何修改元数据的操作(比如ALTER TABLE),而这类操作常被运维后台静默触发。
锁机制不是黑盒,它的行为直接受索引设计、SQL 写法、隔离级别共同决定。最危险的不是锁本身,而是你以为加了行锁,实际锁了一片索引范围,甚至整张表。










