MySQL 默认隔离级别下,InnoDB 的行锁为什么还会锁表?
不是锁表,是锁索引。当查询条件未命中索引(比如
WHERE status = 'pending'但
status列没建索引),InnoDB 会退化为聚簇索引的全扫描,对所有扫描到的记录加
Next-Key Lock,看起来像“锁表”。更隐蔽的是,即使有索引,若使用了函数或类型隐式转换(如
WHERE DATE(create_time) = '2024-01-01'),也会导致索引失效,触发范围锁膨胀。 用
EXPLAIN确认
type是
ref/
range,而非
ALL或
index避免在索引列上使用函数、
LIKE '%xxx'、
IS NULL(除非该列有单独的
IS NULL索引) 执行
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX查看当前事务持有的锁范围,配合
INNODB_LOCK_WAITS定位阻塞源头
READ COMMITTED 和 REPEATABLE READ 在高并发更新时的实际差异
很多人以为
REPEATABLE READ(RR)只是“可重复读”,其实它在写场景影响更大:RR 下,普通
UPDATE语句会基于事务启动时的快照做条件判断,但加锁仍按最新版本进行;而
READ COMMITTED(RC)每次读都取最新已提交版本,锁只加在真正命中的记录上,锁持有时间更短,冲突概率更低。 电商库存扣减类场景,强烈建议用 RC:避免“幻读”锁住不该锁的间隙,提升并发吞吐 RC 下
UPDATE ... WHERE id = ?只锁匹配到的行;RR 下相同语句可能额外锁住
id附近的间隙(防止新插入) 切换前确认业务能接受“非一致性读”——比如报表类逻辑依赖多次读结果一致,就不能切 RC
如何让 INSERT 不成为高并发瓶颈?
INSERT慢不一定是磁盘 I/O,很可能是自增主键争用或唯一索引冲突检查。InnoDB 的
auto_increment锁在高并发插入时会串行化获取值,尤其当
innodb_autoinc_lock_mode = 0(传统模式)时最严重。 设
innodb_autoinc_lock_mode = 2(交错模式),允许批量插入预分配 ID,大幅降低锁等待 避免高频单条
INSERT ... SELECT,改用批量
INSERT VALUES (),(),()(最多 1000 行/批) 如果主键非业务强相关,考虑用雪花算法生成 ID,绕过自增锁和聚集索引热点 唯一约束校验成本高,高频插入场景慎用多列组合唯一索引,优先用应用层幂等控制
事务里混用 SELECT 和 UPDATE 为什么容易死锁?
典型模式:
SELECT ... FOR UPDATE先查再改,但查的顺序与另一事务不一致(比如一个按
user_id查,一个按
order_id查),就会形成循环等待。InnoDB 死锁检测开销小,但频繁死锁本身说明访问路径设计混乱。 所有涉及加锁的读操作,必须按同一索引、同一顺序访问(例如固定先查
PRIMARY KEY,再查二级索引) 避免在事务中执行不确定行数的
SELECT ... FOR UPDATE LIMIT N,N 变化会导致锁范围不可预测 用
SHOW ENGINE INNODB STATUS查看最近死锁详情,重点关注
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:后面的 SQL 和索引名
实际高并发场景中,锁行为比隔离级别更关键;很多问题表面是“事务慢”,根因是索引没走对、锁范围失控、或事务粒度太大。调试时优先看
INNODB_TRX和执行计划,而不是直接调大
innodb_buffer_pool_size。
