mysql如何避免过多的锁竞争_mysql锁粒度调整

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

为什么
SELECT ... FOR UPDATE
一用就堵住其他事务?

根本原因是默认在可重复读(RR)隔离级别下,

SELECT ... FOR UPDATE
会走间隙锁(Gap Lock)+ 记录锁(Record Lock),不仅锁住命中行,还锁住索引间隙。哪怕只查一个主键,若该主键不存在,也会锁住前后两个值之间的空隙,导致看似无关的插入被阻塞。

实操建议:

确认是否真需要当前读:如果只是查数据做展示,别加
FOR UPDATE
LOCK IN SHARE MODE
尽量用主键或唯一索引等值查询:避免范围条件(如
WHERE id > 100
),否则锁住整个范围
在业务允许前提下,把事务拆小:长事务 = 长时间持锁,尽早
COMMIT
检查是否隐式升级为临键锁:比如
WHERE name = 'alice'
name
没有索引,MySQL 会全表扫描并锁所有行

怎么让 MySQL 只锁行、不锁间隙?

核心是关闭间隙锁,但不能直接关——它和 RR 隔离级别强绑定。真正可控的方式是降级隔离级别,或主动加索引抑制间隙锁触发。

实操建议:

改用读已提交(RC)隔离级别:
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED
。此时
SELECT ... FOR UPDATE
只锁匹配到的记录,不锁间隙(但注意:RC 下幻读可能产生)
确保查询条件走索引:尤其是
WHERE
中的字段必须有有效索引,否则即使 RC 级别也会退化为全表锁
避免
UPDATE ... WHERE
无索引条件:这种语句在任何隔离级别下都会升级为表级锁(
type: ALL
执行计划)

什么情况下 MySQL 会退化成表锁?

不是“想锁表才锁表”,而是优化器判断无法安全使用行锁时自动降级。典型场景包括:

对非唯一索引字段做范围更新,且该索引选择性极差(比如
status
只有 0/1 两个值)
UPDATE t SET x=1 WHERE y LIKE '%abc%'
—— 索引失效,走全表扫描
表没主键:InnoDB 要求每张表有聚簇索引,没定义主键时会自建隐藏
ROW_ID
,但无法用于高效定位,容易锁多行甚至整表
执行
ALTER TABLE
加列或改类型(尤其大表),8.0 前默认阻塞 DML;8.0+ 支持部分 ALGORITHM=INSTANT,但并非全部操作都免锁

如何快速定位谁在持有锁、谁被堵住了?

别只看

SHOW PROCESSLIST
,它不反映锁状态。关键看三张元数据表:

SELECT * FROM performance_schema.data_locks
:看到当前每个事务持有的锁类型、锁对象(如
PRIMARY
GEN_CLUST_INDEX
)、锁模式(
RECORD
/
INSERT_INTENTION
SELECT * FROM performance_schema.data_lock_waits
:直接列出阻塞链,
BLOCKING_TRX_ID
REQUESTING_TRX_ID
一目了然
SELECT * FROM information_schema.INNODB_TRX
:结合
TRX_STATE
(如
LOCK WAIT
)和
TRX_STARTED
判断事务是否卡太久
临时启用死锁日志:
SET GLOBAL innodb_print_all_deadlocks = ON
,错误日志里会输出完整冲突 SQL

锁粒度调得再细,也架不住一个没索引的

UPDATE
把整张表扫一遍。真正要盯的不是“怎么调锁”,而是“哪条 SQL 正在破坏锁的边界”。

相关推荐