SELECT FOR UPDATE 是数据库事务中实现行级排他锁的关键语句,用于防止并发修改导致数据不一致,需在事务内使用,锁住索引匹配的行,避免全表扫描和过大锁范围,并支持 NOWAIT、SKIP LOCKED 等进阶控制。

SELECT FOR UPDATE 是在数据库事务中实现行级锁的关键语句,主要用于防止并发修改导致的数据不一致。它会在查询到的行上加写锁(排他锁),其他事务无法对这些行执行 UPDATE、DELETE 或再次 SELECT FOR UPDATE,直到当前事务提交或回滚。
什么时候该用 SELECT FOR UPDATE
适用于“读取-修改-写入”典型场景,比如:
- 库存扣减:先查剩余库存,判断是否足够,再更新库存量
- 订单生成:检查用户余额或优惠券状态,再扣款或标记使用
- 抢购/秒杀:确保同一商品不会被超卖
注意:仅在事务内(BEGIN / START TRANSACTION)使用才有效;自动提交模式下会立即提交,锁也随即释放,起不到保护作用。
基础用法与注意事项
语法结构简单,但细节决定成败:
- 必须搭配事务使用:START TRANSACTION; ... SELECT ... FOR UPDATE; ... COMMIT;
- 锁的是索引行:若 WHERE 条件未命中索引(如全表扫描),可能升级为表锁,严重降低并发性能
- 避免锁范围过大:尽量用主键或唯一索引定位单行;用范围条件(如
WHERE status = 'pending')会锁定所有匹配行 - 锁等待超时默认是 50 秒(MySQL),可通过
innodb_lock_wait_timeout调整
一个典型库存扣减示例(MySQL)
假设有一张商品表 products(id, name, stock),要安全扣减 ID=100 的商品库存 1 件:
START TRANSACTION;-- 加锁查询,确保后续更新基于最新且被锁定的值 SELECT stock FROM products WHERE id = 100 FOR UPDATE;
-- 应用层判断:if stock >= 1 → 执行更新 UPDATE products SET stock = stock - 1 WHERE id = 100;
COMMIT;
如果另一事务同时执行相同 SELECT FOR UPDATE,它会阻塞等待,直到前一个事务结束。若超时则报错 Lock wait timeout exceeded,应用需捕获并重试或提示失败。
进阶技巧:跳过锁等待或限制锁定范围
避免长时间阻塞,可主动控制行为:
-
SELECT ... FOR UPDATE NOWAIT(Oracle/PostgreSQL 支持):不等待,直接报错 -
SELECT ... FOR UPDATE SKIP LOCKED(MySQL 8.0+/PostgreSQL):跳过已被锁的行,常用于任务队列分发 - 结合 LIMIT 使用(如
SELECT ... FOR UPDATE LIMIT 1):只锁满足条件的第一行,适合选一个可用资源
例如任务队列中取一个待处理任务,多个工作进程并发执行时,用 SKIP LOCKED 可避免争抢同一行:
SELECT * FROM tasks WHERE status = 'pending' ORDER BY created_at LIMIT 1 FOR UPDATE SKIP LOCKED;










