mysql并发下行锁和表锁如何选择_mysql锁粒度对比

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

行锁在什么情况下会退化成表锁

MySQL 的

InnoDB
默认用行锁,但不是所有带索引的
WHERE
条件都能命中行锁。一旦优化器判定无法使用索引、或用了不走索引的条件(比如
LIKE '%abc'
、隐式类型转换、函数包裹字段),就会触发全表扫描,进而升级为表级意向锁 + 实际的表锁行为。

常见退化场景:

UPDATE user SET status=1 WHERE name LIKE '%admin%'
——
name
有索引但
LIKE
左模糊导致索引失效
SELECT * FROM order WHERE user_id = '123'
,而
user_id
INT
类型,传入字符串
'123'
触发隐式转换,索引失效
事务中执行了未提交的
SELECT ... FOR UPDATE
,但查询条件没走索引,InnoDB 会对所有聚集索引记录加锁,效果等同于锁表

INSERT … ON DUPLICATE KEY UPDATE 用的是行锁还是表锁

这个语句本质是「先尝试插入,冲突则更新」,InnoDB 对它做了专门优化:只对将要插入/更新的那几行加

next-key lock
(间隙锁 + 行锁),不会锁整张表。

但要注意前提:

必须有
PRIMARY KEY
UNIQUE KEY
,否则冲突判断无依据,语句会报错,更谈不上加什么锁
冲突发生在唯一索引上时,InnoDB 会在该唯一索引对应记录上加
record lock
;如果插入位置存在间隙,还会额外加
gap lock
如果语句里
UPDATE
部分修改了唯一索引字段本身(比如
UPDATE id = id + 1
),可能导致锁范围扩大,甚至触发死锁

如何用 SELECT … FOR UPDATE 精确控制行锁范围

SELECT ... FOR UPDATE
是否锁住多行、是否锁间隙,完全取决于你的
WHERE
条件是否能走索引,以及索引类型。

实操建议:

只对主键或唯一索引字段做等值查询(如
WHERE id = 100
),锁的是单条记录的
record lock
对非唯一索引等值查询(如
WHERE status = 1
),InnoDB 会加
next-key lock
,即锁住匹配记录 + 该记录前的间隙,防止幻读
范围查询(如
WHERE created_at > '2024-01-01'
)一定包含间隙锁,范围越大,锁越多,容易阻塞其他事务
想避免间隙锁?可临时把事务隔离级别降为
READ COMMITTED
(需确认业务能否接受不可重复读)

SHOW ENGINE INNODB STATUS 能看到哪些锁信息

这是诊断锁问题最直接的命令,输出里

TRANSACTIONS
部分会列出当前阻塞关系和持锁情况。重点关注:

Trx id
Trx mysql thread id
:定位具体哪个线程卡住了
Trx state: LOCK WAIT
表示正在等锁;
Trx state: RUNNING
不代表没锁,只是当前没在等
lock_mode X locks rec but not gap
:说明是纯行锁;
lock_mode X locks gap before rec
是间隙锁;
lock_mode X
单独出现大概率是表锁或意向锁
Record lock, heap no 123
中的
heap no
是聚簇索引堆记录号,结合
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX
可交叉验证
mysql> SHOW ENGINE INNODB STATUS\G
...
---TRANSACTION 42156789, ACTIVE 12 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)
MySQL thread id 123, OS thread handle 140234567890176, query id 456789 localhost root updating
UPDATE account SET balance = balance - 100 WHERE id = 5

上面这段输出里,“locked 1” 是指锁了一张表(逻辑),但实际是这张表里的某一行被

X
锁住了——粒度得看后面的
row lock(s)
数量和类型,不能只看
locked 1
就认为是表锁。

锁粒度不是写死的,它由查询路径、索引结构、隔离级别共同决定。很多人调优时只改 SQL 写法,却忘了检查执行计划是否真走了索引,或者忽略了

autocommit=0
下事务长期不提交导致锁一直挂着——这才是并发下锁问题最常被忽略的根因。

相关推荐