InnoDB行锁实际锁的是索引记录而非数据行;主键/唯一索引等值查询加记录锁,普通索引等值查询同时锁二级索引和主键,范围查询加间隙锁+记录锁,无索引时退化为近似表锁。
InnoDB 的行锁到底锁的是什么
InnoDB 实现行锁,**不是直接锁住某一行数据本身**,而是通过在索引记录上加锁来实现的。这意味着:如果没有合适的索引,UPDATE 或 DELETE 语句可能退化为表锁(或间隙锁组合),而不是你期望的“只锁一行”。
常见错误现象:SELECT ... FOR UPDATE 在无索引字段上执行后,其他事务更新同表任意行都被阻塞——这说明实际加了表级意向锁+大量记录锁,而非行锁。
- 主键或唯一索引等值查询(如
WHERE id = 100)→ 加 **记录锁(Record Lock)** - 普通索引等值查询 → 同样加记录锁,但会同时在主键上加锁(聚簇索引锁定)
- 范围查询(如
WHERE age > 25)→ 加 **间隙锁(Gap Lock) + 记录锁**,防止幻读 - 无索引字段查询 → 优化器可能走全表扫描,对所有扫描到的记录加锁,效果接近表锁
为什么 SELECT FOR UPDATE 有时不生效
行锁只在事务中、且隔离级别 ≥ READ COMMITTED 时才起作用;但如果语句没走索引,或者被 MySQL 优化器判定为不可加锁(比如查询条件含函数、隐式类型转换),SELECT ... FOR UPDATE 可能不加任何行级锁,仅加意向锁。
典型场景:执行 SELECT * FROM user WHERE name = '张三' FOR UPDATE,但 name 字段没建索引 → InnoDB 无法精确定位记录,只能对聚集索引的每条记录尝试加锁,最终可能因锁冲突或死锁检测提前释放,看起来“没锁住”。
- 检查执行计划:
EXPLAIN确认是否用到索引,type字段不能是ALL - 避免隐式转换:比如
WHERE mobile = 13800138000(mobile 是 VARCHAR),会导致索引失效 - 注意隔离级别:
READ UNCOMMITTED和READ COMMITTED下不使用间隙锁,但幻读风险上升
REPEATABLE READ 下的间隙锁与死锁风险
MySQL 默认隔离级别 REPEATABLE READ 会启用间隙锁(Gap Lock),它锁住的是索引记录之间的“空隙”,用于阻止其他事务插入新记录,从而解决幻读。但这也带来更高死锁概率。