mysql什么时候使用行锁_mysql行锁应用场景

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

UPDATE 或 DELETE 语句带 WHERE 条件且命中索引时会加行锁

MySQL 的行锁(Record Lock)不是由语法决定的,而是由执行计划和索引使用情况共同触发的。只有当

UPDATE
DELETE
语句能通过索引精确定位到某几行(即走索引扫描,而非全表扫描),InnoDB 才会对这些实际访问的记录加行锁。

常见错误现象:明明写了

WHERE id = 100
,却锁了整张表——大概率是
id
列没建索引,或索引失效(比如对
id
做了函数操作:
WHERE ABS(id) = 100
)。

必须使用支持行锁的存储引擎(仅
InnoDB
MyISAM
只有表锁)
WHERE 条件字段需有有效索引,且优化器实际选用该索引(可用
EXPLAIN
验证)
主键或唯一索引上的等值查询,只锁匹配行;普通索引等值查询,还会额外加
Gap Lock
Next-Key Lock

SELECT ... FOR UPDATE / LOCK IN SHARE MODE 显式加行锁的典型用法

这是应用层主动控制并发的核心手段,常用于“读-改-写”场景,避免脏读或更新丢失。例如秒杀扣库存、账户余额转账前校验。

注意:即使

SELECT
本身不修改数据,加上
FOR UPDATE
后,只要事务未提交,其他事务对同一行执行
UPDATE
SELECT ... FOR UPDATE
就会被阻塞。

SELECT * FROM order WHERE order_no = 'ORD123' FOR UPDATE;
—— 锁住匹配的订单行
order_no
是普通索引(非唯一),该语句可能锁住间隙,防止幻读
在可重复读(
REPEATABLE READ
)隔离级别下,
FOR UPDATE
默认使用
Next-Key Lock
(行锁 + 间隙锁)
如果只想锁记录不锁间隙,可改用
SELECT ... FOR UPDATE NOWAIT
(MySQL 8.0+)或搭配
READ COMMITTED
隔离级别

INSERT 语句也会触发行锁:不只是“插入”,还有“检查唯一性”

很多人忽略

INSERT
的锁行为——它不仅锁自己要插入的记录,还会在唯一索引上加
Insert Intention Lock
,并在插入位置的间隙上加锁,防止其他事务同时插入相同值或冲突值。

典型问题:两个事务几乎同时执行

INSERT INTO user (email) VALUES ('a@b.com');
,而
email
是唯一索引。其中一个会成功,另一个报错
Deadlock found when trying to get lock
Duplicate entry
,但背后其实是行锁等待或死锁检测介入。

唯一约束冲突检测过程需要扫描索引定位是否存在相同值,这个扫描过程会加锁 即使插入失败(如唯一键冲突),InnoDB 仍可能已持有部分锁,影响其他并发操作 批量
INSERT ... ON DUPLICATE KEY UPDATE
中,所有涉及的唯一键路径都会被扫描并加锁,范围可能比预期大

行锁不是万能的:容易被绕过或升级成表锁的几种情况

行锁生效的前提是“能精准定位到行”。一旦优化器放弃索引、或者锁粒度扩大,就可能退化为表级锁或锁住远超预期的范围。

最隐蔽的问题是“隐式类型转换”导致索引失效:比如

user_id
BIGINT
,但 SQL 写成
WHERE user_id = '123'
(字符串),MySQL 会转成
WHERE CAST(user_id AS CHAR) = '123'
,无法使用索引,最终锁全表。

WHERE 条件中对索引列使用函数、表达式、不同数据类型比较 → 索引失效 → 行锁失效 事务中先
SELECT ... FOR UPDATE
无索引条件 → 锁全表(或所有索引树叶子节点)
大范围扫描(如
WHERE create_time > '2020-01-01'
且该字段无索引)→ 锁所有扫描过的行,甚至演变为锁整个聚簇索引
大量行锁占用内存后,InnoDB 可能触发锁升级(虽然官方说不支持锁升级,但高并发下锁管理开销剧增,表现类似)

行锁的边界很依赖执行计划,光看 SQL 写法远远不够;真正上线前,务必用

EXPLAIN
INFORMATION_SCHEMA.INNODB_TRX
+
INNODB_LOCKS
(MySQL 5.7)或
performance_schema.data_locks
(8.0+)验证锁的实际行为。

相关推荐