加索引仍锁表是因为查询未精确命中索引,导致innodb升级为间隙锁、临键锁甚至表级锁;需通过explain确认是否真正使用索引,并优先使用唯一索引以减少锁范围。

为什么加了索引还是被锁住整张表
MySQL 的行级锁只在能精确命中索引时才生效;如果查询条件没走索引、或走了索引但实际扫描了多行(比如范围查询 + 无覆盖索引),InnoDB 就可能升级为间隙锁、临键锁,甚至退化成表级锁(尤其在 READ COMMITTED 以下隔离级别)。
- 常见错误现象:
SELECT ... FOR UPDATE或UPDATE执行时阻塞了不相关的行,SHOW ENGINE INNODB STATUS显示锁住了远超预期的记录数 - 典型场景:对
name字段做模糊查询WHERE name LIKE 'zhang%',但该字段没建索引,或只有前缀索引且长度太短(如INDEX(name(10)),而实际值都超过 10 字节) - 参数差异:
innodb_lock_wait_timeout控制等待时长,但它不解决锁范围问题;真正影响锁粒度的是执行计划是否用上索引、以及索引类型(主键 vs 二级索引、唯一 vs 非唯一) - 性能影响:全表扫描触发的锁会极大降低并发写入能力,尤其在高 QPS 更新场景下,可能让事务排队变成线性瓶颈
如何确认你的查询真的用了索引
别信“我建了索引就一定生效”,必须看 EXPLAIN 输出里的 key 和 rows 字段——前者为空说明根本没走索引,后者远大于你要更新的行数,说明索引选择性差或被优化器弃用。
- 实操建议:在执行 DML 前先跑
EXPLAIN SELECT ...模拟相同 WHERE 条件,重点检查type是否为const/ref/range(安全),而非ALL或index - 容易踩的坑:
OR条件中只要有一边无法使用索引,整个查询就可能放弃索引;隐式类型转换(如字符串字段与数字比较:WHERE user_id = 123但user_id是VARCHAR)也会导致索引失效 - 兼容性注意:MySQL 8.0+ 对函数索引支持更好,但 5.7 不支持;如果要用
JSON_EXTRACT或LOWER()做条件,得提前建好函数索引,否则照样全扫
唯一索引和普通索引对锁行为的区别
这是最容易被忽略的关键点:唯一索引能帮 InnoDB 快速确认“只有一行匹配”,从而避免加间隙锁;普通索引查到相同值时,无法排除其他行也满足条件,所以必须加临键锁(Next-Key Lock)来防止幻读。
- 示例对比:
UPDATE users SET status=1 WHERE id=100(id是主键)→ 只锁这一行;UPDATE users SET status=1 WHERE email='a@b.com'(email是普通索引且非唯一)→ 可能锁住'a@b.com'前后一段索引区间 - 使用场景:高频更新的查询条件,优先建
UNIQUE INDEX;如果业务上允许重复但你又想减小锁范围,可以考虑把普通索引改成唯一索引 + 应用层保证唯一性 - 性能影响:临键锁比行锁更重,会显著增加死锁概率,尤其在并发插入/更新相邻值时(比如按时间戳批量导入)
什么时候即使有索引,锁竞争依然严重
索引只是前提,不是万能解药。当多个事务频繁修改同一索引页上的不同记录(比如时间戳递增字段的索引),或者大量事务集中在某几个热点值上(如状态字段 status=0),锁冲突依然会爆发。
- 常见错误现象:监控显示
Innodb_row_lock_waits持续升高,但单条 SQL 的EXPLAIN看起来完全正常 - 实操建议:用
SELECT * FROM information_schema.INNODB_TRX查当前长事务,结合INNODB_LOCKS和INNODB_LOCK_WAITS(5.7+ 已废弃,改用performance_schema.data_locks)定位具体锁在哪几行 - 容易被忽略的地方:二级索引更新会引发“回表”,即先锁二级索引记录,再锁聚簇索引记录;如果两个事务更新不同二级索引但对应同一主键行,依然会冲突
索引能不能减少锁竞争,最终取决于查询是否精准命中、索引是否唯一、以及数据分布是否均匀——这三件事没理清,建再多索引也只是把锁从表级挪到页级而已。










