国内海外服务器测评及优惠
Linux服务器运维救灾服务

如何用 NOWAIT / SKIP LOCKED 避免阻塞等待

NOWAIT 使锁请求不等待而立即报错(错误码55P03),需应用层捕获并兜底;SKIP LOCKED 则跳过已锁行继续查询,适用于并发队列消费。

PostgreSQL 中 NOWAIT 怎么用,就报错

加了 NOWAIT 却抛出 could not obtain lock on row,不是“不等待”吗?没错,它确实不等,但也不隐式失败——它会立刻报错,由你决定怎么兜底。常见于 SELECT ... FOR UPDATEFOR SHARE 场景。

典型写法:

SELECT * FROM orders WHERE id = 123 FOR UPDATE NOWAIT;

如果该行正被其他事务锁定,这条语句不会挂起,而是直接返回错误。你需要在应用层捕获这个特定异常(PostgreSQL 错误码 55P03),再走降级逻辑,比如重试、跳过、或改查缓存。

  • NOWAIT 只作用于当前语句申请的锁,不影响事务中后续语句
  • 不能和 SKIP LOCKED 同时用——二者语义冲突,PostgreSQL 会拒绝解析
  • 某些 ORM(如 Djan 的 select_for_update(nowait=True))底层就是翻译成这个语法,但要注意异常类型是否被正确映射

SKIP LOCKED 真实适用场景:队列消费与分片取数

它不是“跳过整张表”,而是跳过**当前扫描路径中已被其他事务加锁的行**,继续往后找可用行。最典型的是实现无竞争的 job 队列:

UPDATE tasks SET status = 'processing', worker_id = 'w1' 
WHERE id IN (
  SELECT id FROM tasks 
  WHERE status = 'pending' 
  ORDER BY created_at 
  LIMIT 10 
  FOR UPDATE SKIP LOCKED
);

多个 worker 并发执行这段 SQL,不会互相阻塞,各自拿到互斥的 10 条任务。关键点:

一站式AI应用开发和部署工具

  • SKIP LOCKED 只在 SELECT ... FOR UPDATE/SHARE 中有效,不能单独用于 UPDATEDELETE
  • 必须配合 ORDER BY + LIMIT 使用才有意义,否则可能跳过大量数据却只取到零星几条
  • 在可重复读(RR)隔离级别下仍安全:它跳过的行对当前事务不可见,不会导致幻读问题

MySQL 的 SKIP LOCKED 和 PostgreSQL 有啥不一样

MySQL 8.0.1 才支持 SKIP LOCKED,行为基本一致,但有两个实际差异点:

  • MySQL 不支持 NOWAIT(直到 8.0.29 才部分支持,且仅限 SELECT ... FOR UPDATE,不支持 FOR SHARE
  • MySQL 的 SKIP LOCKED 在二级索引覆盖扫描时可能跳过本应锁定的行(因锁粒度和 MVCC 实现差异),而 PostgreSQL 基于 tuple-level lock,更稳定
  • MySQL 执行带 SKIP LOCKED 的语句时,EXPLAIN 显示的 Extra 字段会出现 Using where; Skip locked,可用来确认是否生效

别以为加了就能高枕无忧:几个容易忽略的坑

SKIP LOCKEDNOWAIT 解决的是锁等待问题,但掩盖不了设计缺陷:

  • 没加合适索引时,FOR UPDATE SKIP LOCKED 可能触发全表扫描+逐行加锁,性能断崖下跌——务必确保 WHERE 条件走索引
  • 在长事务中反复用 SKIP LOCKED 拿数据,可能因 MVCC 版本链过长导致查询变慢,甚至 OOM
  • NOWAIT 报错后不做重试或退避,直接返回失败,用户体验可能比短暂等待还差
  • 有些数据库代理(如 ProxySQL、pgBouncer 连接池 in transaction pooling 模式)会吞掉 NOWAIT 异常或干扰锁行为,务必在真实部署环境验证

真正难的从来不是语法,而是判断哪条数据值得锁、锁多久、谁来负责清理残留状态。

赞(0) 打赏
未经允许不得转载:linuxcto运维 » 如何用 NOWAIT / SKIP LOCKED 避免阻塞等待

觉得文章有用就打赏一下文章作者

非常感谢你的打赏,我们将继续提供更多优质内容,让我们一起创建更加美好的网络世界!

支付宝扫一扫

微信扫一扫