mysql如何使用for update加锁_mysql行锁与阻塞

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

FOR UPDATE 必须在事务中才生效

单独执行

SELECT ... FOR UPDATE
不会加锁,MySQL 会直接报错或静默忽略(取决于 SQL 模式),真正起作用的前提是当前连接已开启事务且未提交。常见错误是写完
SELECT ... FOR UPDATE
就立刻执行
UPDATE
,但没用
BEGIN
START TRANSACTION
包裹,结果锁根本没拿到,后续更新发生并发覆盖。

实操建议:

务必显式开启事务:
BEGIN
SELECT ... FOR UPDATE
→ 业务逻辑 →
COMMIT
ROLLBACK
避免在事务里做耗时操作(如远程调用、大循环),否则锁持有时间过长,阻塞其他事务 确认隔离级别:默认
REPEATABLE READ
下,
FOR UPDATE
会对扫描到的索引记录加 next-key lock;若降为
READ COMMITTED
,则只加 record lock(不锁间隙)

行锁是否生效,取决于 WHERE 条件是否命中索引

FOR UPDATE
看似“锁行”,实际锁的是索引项。如果
WHERE
条件没走索引(例如对非索引字段查询、或用了函数/隐式类型转换),MySQL 会退化为锁全表(更准确地说:锁所有聚簇索引记录,即整张表的主键行),并发性能骤降。

验证方法:

执行前先看执行计划:
EXPLAIN SELECT ... FOR UPDATE
,确认
key
列非
NULL
,且
rows
尽量小
检查是否因字符集/排序规则不一致导致索引失效,例如
WHERE name = 'abc'
,但
name
utf8mb4_0900_as_cs
而参数是
utf8mb4_general_ci
联合索引要注意最左匹配:若索引是
(a, b)
WHERE b = 1
无法使用该索引,自然也不触发行锁

阻塞不是 bug,而是锁等待超时触发的 ERROR 1205 或 ERROR 1213

两个事务同时尝试

FOR UPDATE
同一行,后到者不会立即失败,而是进入锁等待状态,默认等待 50 秒(由
innodb_lock_wait_timeout
控制),超时后抛出
ERROR 1205 (HY000): Deadlock found when trying to get lock
(死锁)或
ERROR 1213 (40001): Deadlock found when trying to get lock
(注意:1205 和 1213 实际都表示锁等待失败,具体报哪个取决于是否被检测为死锁)。

关键点:

死锁检测是自动的,但代价不低;高并发下频繁死锁说明业务逻辑存在竞争热点,比如多个服务争抢同一账户余额 不要依赖超时来“兜底”,应在应用层设计重试机制,并限制最大重试次数(避免雪崩)
SHOW ENGINE INNODB STATUS\G
查看最近死锁详情,重点关注
TRANSACTION
WAITING FOR THIS LOCK TO BE GRANTED
部分

UPDATE 语句本身也会隐式加锁,不一定非要写 FOR UPDATE

很多人以为只有显式

SELECT ... FOR UPDATE
才加锁,其实普通
UPDATE
在执行时,MySQL 会对将要修改的行自动加 X 锁(排他锁),效果等同于先
SELECT ... FOR UPDATE
UPDATE
。区别在于:前者可提前暴露锁冲突(在 SELECT 阶段就阻塞),后者把冲突推迟到 UPDATE 阶段。

适用场景选择:

需要基于当前值做判断再决定是否更新(如“余额 > 100 才扣款”),必须用
SELECT ... FOR UPDATE
,否则可能读到旧值导致逻辑错误
只是简单 SET 字段(如
UPDATE t SET status = 'done' WHERE id = 123
),直接
UPDATE
更简洁,也少一次网络往返
注意:
UPDATE
若影响 0 行,锁也会释放;而
SELECT ... FOR UPDATE
即使查不到记录,也可能锁住间隙(gap lock),防止幻读
锁的粒度、行为和触发条件高度依赖索引结构与事务上下文,脱离具体表结构和查询条件谈“FOR UPDATE 是否加行锁”,大概率会误判。

相关推荐