行锁仅在InnoDB引擎且索引命中时生效;否则退化为表锁,间隙锁在RR级别下还会锁索引间隙,事务提交前不释放锁。
MySQL 不是“一执行 UPDATE 就加行锁”,而是取决于存储引擎和查询条件是否能走索引。MyISAM 引擎压根不支持行锁,所有 DML 都是表锁;InnoDB 虽默认支持行锁,但一旦 WHERE 条件没命中索引(比如对无索引字段更
新),就会退化为全表扫描 → 触发隐式表锁。
UPDATE users SET status = 1 WHERE id = 1001;(
id 是主键或唯一索引)UPDATE users SET status = 1 WHERE name = 'Alice';(
name 字段无索引)普通 SELECT 在可重复读(RR)或读已提交(RC)隔离级别下,默认走 MVCC 快照读,**完全不加锁**。只有显式带上锁提示,才会真正申请行级排他锁或共享锁。
SELECT * FROM orders WHERE order_id = 123 FOR UPDATE; → 加 X 锁(阻塞其他 X/S 锁)SELECT * FROM orders WHERE order_id = 123 LOCK IN SHARE MODE; → 加 S 锁(允许多个 S 锁,但阻塞 X 锁)WHERE 条件没走索引,这两条语句也会锁整张表,不是只锁“看起来匹配的那几行”在默认隔离级别 REPEATABLE READ 下,InnoDB 不仅锁记录,还会锁住索引间隙(Gap Lock)甚至记录+间隙(Next-Key Lock)。这会导致看似无关的 INSERT 或 UPDATE 被阻塞。
SELECT * FROM users WHERE age BETWEEN 25 AND 35 FOR UPDATE;→ 实际会锁住
(20, 25]、(25, 35]、(35, 40) 等多个间隙(取决于索引分布)age = 28 的新用户会被阻塞,哪怕原表里没有 age = 28 的记录READ COMMITTED(需权衡幻读风险),此时间隙锁被禁用,只保留记录锁InnoDB 行锁不是“查完就放”,而是遵循两阶段锁协议:锁在需要时加,但直到 COMMIT 或 ROLLBACK 才统一释放。这意味着锁持有时间 ≈ 整个事务执行时间,而非单条 SQL 时间。
UPDATE ... FOR UPDATE → 锁被长时间占用,拖垮并发SHOW ENGINE INNODB STATUS\G查看
TRANSACTIONS 部分的锁等待与持有信息行锁不是开关,而是一套依赖索引、隔离级别、事务边界和执行路径的联动机制。最容易被忽略的,其实是「无索引导致的锁升级」和「间隙锁在范围查询中的扩散效应」——它们往往在压测时才突然暴露,且很难从 SQL 表面看出问题。