SELECT ... FOR UPDATE 是 InnoDB 提供的行级排他锁 语法,用来在事务内 把"查到的行"锁住,防止其他事务同时改或删,实现悲观锁流程。
1. 最基本用法
sql
START TRANSACTION;
SELECT balance
FROM account
WHERE id = 100
FOR UPDATE; -- 给 id=100 这一行加排他锁(X锁)
-- 业务逻辑:判断、计算、更新
UPDATE account
SET balance = balance - 100
WHERE id = 100;
COMMIT; -- 锁自动释放
- 锁范围 :命中索引 时是行锁 ;无索引 退化为间隙锁 或表锁 → 注意覆盖索引/主键
- 持有时间 :从语句执行成功直到事务提交/回滚
2. 多行/范围锁定
sql
-- 锁住余额小于 0 的所有账户(范围锁)
SELECT id
FROM account
WHERE balance < 0
FOR UPDATE;
其他事务无法插入/更新/删除 balance < 0 的行,直到本事务结束。
3. 与 LOCK IN SHARE MODE 区别
| 锁类型 | 语法 | 作用 | 并发 |
|---|---|---|---|
| 排他锁 | FOR UPDATE |
读写都阻塞 | 悲观写 |
| 共享锁 | LOCK IN SHARE MODE |
读允许,写阻塞 | 悲观读 |
4. 超时控制
sql
-- 等 3 秒拿不到锁就报错(InnoDB 5.7+)
SELECT ... FOR UPDATE NOWAIT; -- 立即报错
SELECT ... FOR UPDATE SKIP LOCKED; -- 跳过已锁行,返回空结果
SELECT ... FOR UPDATE WAIT 3; -- 等待 3 秒(MySQL 8.0)
5. 常见坑
- 无索引 → 退化为表锁 或间隙锁 → 并发骤降
- 长事务 → 锁持有时间长 → 阻塞/死锁概率飙升
- 快照读 (
REPEATABLE READ)下,FOR UPDATE会升级成当前读 ,可能产生幻读 → 需要范围锁/间隙锁
一句话记忆
SELECT ... FOR UPDATE = "把查到的行当场加写锁,直到事务结束" ,用来实现悲观并发控制;务必走索引、尽量短事务、按需设置超时。