mysql中InnoDB的行锁机制与性能分析

来源:这里教程网 时间:2026-02-28 20:39:32 作者:

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 = ?
可能同时持有两把锁 —— 二级索引页上的记录锁 + 聚簇索引页上的记录锁。并发高时,锁竞争放大。

避免在高频更新场景中,对非主键字段建过多二级索引 如必须按
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 条件里”——往往是因为临键锁扩大了范围,或者唯一索引查找失败后退化为范围扫描。

相关推荐