MySQL 锁机制容易被记成名词:行锁、表锁、间隙锁、next-key lock、MVCC、可重复读。真正排查问题时,更有用的是先分清两件事:这条 SQL 是当前读还是快照读,它有没有走到合适索引。
快照读和当前读
普通 select 通常是快照读。InnoDB 会通过 MVCC 读取一个一致性版本,不直接加行锁。
select * from orders where id = 1;
带锁读、更新和删除是当前读,读的是最新版本,并且需要加锁:
select * from orders where id = 1 for update;
update orders set status = 2 where id = 1;
delete from orders where id = 1;
所以不要只看“是不是 select”。select for update 和普通 select 的并发行为完全不同。
行锁依赖索引
InnoDB 的行锁锁在索引记录上。where 条件能命中索引,锁范围通常比较小;没有合适索引,可能扫描大量记录,锁范围也会变大。
例如:
update orders set status = 2 where order_no = 'A100';
如果 order_no 有唯一索引,锁定目标记录即可。如果没有索引,数据库要扫描更多记录,可能造成大量行被锁,甚至表现得像表锁。
排查锁等待时,第一步要看执行计划:
explain update orders set status = 2 where order_no = 'A100';
没有索引的更新语句,是高并发业务里的重点风险。
间隙锁解决什么问题
MySQL InnoDB 默认可重复读隔离级别下,为了避免幻读,范围查询加锁时可能产生间隙锁。
例如:
select * from orders
where amount between 100 and 200
for update;
数据库不仅可能锁住已有记录,还会锁住范围之间的空隙,阻止其他事务往这个范围插入新记录。
这对一致性有帮助,但也会降低并发。业务里如果大量使用范围条件 for update,要特别关注索引选择和事务时长。
死锁并不等于数据库坏了
死锁是两个事务互相等待对方持有的锁。InnoDB 会检测死锁,并回滚其中一个事务。应用看到死锁异常时,通常应该做有限重试,而不是认为数据库不可用。
常见死锁原因:
- 多个事务更新相同表,但更新顺序不同。
- 范围更新没有合适索引。
- 批量更新时排序不稳定。
- 事务里混入慢操作,持锁时间太长。
排查死锁看:
show engine innodb status;
重点关注 LATEST DETECTED DEADLOCK 里的两条事务、SQL、锁类型和索引名。
事务越短越好
锁问题很多时候不是锁类型选错,而是事务太大。一个事务里如果包含 RPC、HTTP、文件处理、复杂循环和人工等待,数据库连接和锁都会被长时间占用。
更稳的做法是:
- 事务里只放必须原子提交的数据库操作。
- 外部 IO 放到事务外,或用事务消息/任务表异步处理。
- 批量更新先拆批,并固定排序。
- 热点行更新考虑乐观锁、队列化或拆分计数。
最后抓住一句话
MySQL 锁排查的主线是:当前读会加锁,锁范围依赖索引,范围锁可能带来间隙锁,长事务会放大一切问题。先看 SQL 类型和执行计划,再看事务边界,通常比背锁名更有效。




