InnoDB行锁仅作用于索引列,无索引或无法走索引时会退化为全表扫描锁;联合索引需最左匹配才生效;临键锁(记录锁+间隙锁)是RR级别默认机制,易引发死锁与阻塞。

InnoDB 行锁只在索引列上生效
InnoDB 的行级锁(RECORD LOCK)本质是加在索引记录上的,不是加在数据行本身。如果 WHERE 条件中用的列没有索引,或者用了无法走索引的表达式(比如 WHERE name LIKE '%abc'),InnoDB 会退化为锁全表(实际是锁所有扫描到的索引记录,可能等效于表锁)。
常见错误现象:UPDATE users SET status = 1 WHERE phone = '138...' 执行很慢、并发更新阻塞严重——先查 SHOW INDEX FROM users,确认 phone 是否有单列索引或作为联合索引最左前缀。
- 联合索引
(a, b, c)只能支持WHERE a = ?、WHERE a = ? AND b = ?等最左匹配的行锁 -
WHERE b = ?即使b在联合索引中,也不会走索引,大概率触发间隙锁+临键锁的全范围扫描锁定 -
SELECT ... FOR UPDATE或UPDATE/DELETE语句若未命中任何记录,仍可能加GAP LOCK(间隙锁),阻塞插入
临键锁(Next-Key Lock)是默认行为,也是死锁主因
InnoDB 默认事务隔离级别 REPEATABLE READ 下,行锁 = 记录锁 + 间隙锁 → 合称临键锁。它锁住的是「索引记录 + 该记录前的间隙」,目的是防止幻读。但这也意味着:即使你只更新一行,也可能锁住一大段索引区间。
典型死锁场景:
SESSION A: UPDATE t SET x = 1 WHERE id = 5; SESSION B: UPDATE t SET x = 2 WHERE id = 10; SESSION A: UPDATE t SET x = 3 WHERE id = 10; -- 等待 B SESSION B: UPDATE t SET x = 4 WHERE id = 5; -- 等待 A → 死锁根本原因不是 id=5 和 id=10 冲突,而是两个事务在相同索引范围(比如主键 B+ 树的同一页面)内申请了重叠的临键锁。
- 用
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX查当前运行事务 - 用
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS查谁在等谁 - 临时关闭临键锁:改为
READ COMMITTED隔离级别(但会丢失可重复读语义,且仍可能有间隙锁)
隐式主键与二级索引锁的双重开销
即使表显式定义了 PRIMARY KEY,InnoDB 也会用它做聚簇索引;但如果查询走的是二级索引(比如 INDEX idx_email(email)),InnoDB 会先锁二级索引记录,再回表去聚簇索引找主键,此时会额外加一把对应主键记录的锁。
这意味着:一个 UPDATE ... WHERE email = ? 可能同时持有两把锁 —— 二级索引页上的记录锁 + 聚簇索引页上的记录锁。并发高时,锁竞争放大。
citySHOP是一款集CMS、网店、商品、分类信息、论坛等为一体的城市多用户商城系统,已完美整合目前流行的Discuz! 6.0论坛,采用最新的5.0版PHP+MYSQL技术。面向对象的数据库连接机制,缓存及80%静态化处理,使它能最大程度减轻服务器负担,为您节约建设成本。多级店铺区分及联盟商户地图标注,实体店与虚拟完美结合。个性化的店铺系统,会员后台一体化管理。后台登陆初始网站密匙:LOVES
- 避免在高频更新场景中,对非主键字段建过多二级索引
- 如必须按
email更新,考虑将email设为联合主键一部分(谨慎评估业务主键语义) -
EXPLAIN FORMAT=tree可看是否走了索引、是否发生回表
锁监控与诊断必须依赖 INFORMATION_SCHEMA 表
InnoDB 不提供类似 Oracle 的 v$lock 视图,所有实时锁信息都藏在 INFORMATION_SCHEMA 下的几张表里。靠 SHOW ENGINE INNODB STATUS 只能看到最近一次死锁,无法定位长期阻塞。
关键组合查询:
SELECT trx_id, trx_state, trx_started, trx_query, lock_trx_id, lock_mode, lock_type, lock_table, lock_index, lock_data FROM INFORMATION_SCHEMA.INNODB_TRX t JOIN INFORMATION_SCHEMA.INNODB_LOCKS l ON t.trx_id = l.lock_trx_id JOIN INFORMATION_SCHEMA.INNODB_LOCK_WAITS w ON w.blocking_lock_id = l.lock_id;
注意:lock_data 显示被锁的具体索引值(如 5、'alice@example.com'),这是判断锁粒度是否合理的直接依据。很多线上慢锁问题,就卡在没查这列,只看到“有锁”却不知道锁了哪几条记录。
真正难处理的,永远不是“有没有锁”,而是“为什么这行被锁了却不在你的 WHERE 条件里”——往往是因为临键锁扩大了范围,或者唯一索引查找失败后退化为范围扫描。










