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

8次阅读

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

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

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; 

text=ZqhQzanResources