如何在事务中使用select for update_锁控制示例

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

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;
<p>-- 加锁查询,确保后续更新基于最新且被锁定的值
SELECT stock FROM products WHERE id = 100 FOR UPDATE;</p><p>-- 应用层判断:if stock >= 1 → 执行更新
UPDATE products SET stock = stock - 1 WHERE id = 100;</p><p>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;

相关推荐