PostgreSQL SKIP LOCKED 如何避免阻塞等待的批量更新

发布时间 - 2026-01-28 00:00:00    点击率:
SKIP LOCKED 只在 SELECT ... FOR UPDATE 或 FOR NO KEY UPDATE 的子查询中生效,需用 WITH 或子查询包裹并显式加锁,外层 UPDATE 不加锁;避免无索引过滤、缺失 ORDER BY 导致重复争抢,且无法跳过未提交的隐式锁行。

SKIP LOCKED 在 UPDATE 语句中怎么写才生效

SKIP LOCKED 只在 SELECT ... FOR UPDATESELECT ... FOR NO KEY UPDATE 的子查询中起作用,不能直接写在 UPDATE 顶层。常见错误是以为可以这样写:UPDATE t SET status = 'done' WHERE id IN (SELECT id FROM t WHERE status = 'pending' LIMIT 10 FOR UPDATE SKIP LOCKED) —— 这其实没问题,但关键在于:外层 UPDATE 本身不加锁,真正加锁和跳过的是子查询里的 SELECT

实操建议:

  • 必须用子查询包裹,且子查询需显式带 FOR UPDATEFOR NO KEY UPDATE + SKIP LOCKED
  • 避免在子查询里用非索引字段过滤,否则可能全表扫描并锁大量无关行
  • 如果业务允许“非严格 FIFO”,优先用 FOR NO KEY UPDATE(锁粒度更小,不阻塞其他 UPDATE 同一行的非键列操作)

为什么加了 SKIP LOCKED 还会卡住

典型现象是并发执行时某次查询返回空结果,或延迟明显升高。根本原因不是 SKIP LOCKED 失效,而是它只跳过“已被其他事务锁定的行”,不跳过“正在被其他事务修改但尚未提交、且未显式加锁”的行 —— 比如另一个事务刚执行了 UPDATE t SET status = 'processing' WHERE id = 123 但还没 COMMIT,此时你的 SELECT ... FOR UPDATE SKIP LOCKED 仍会等待该行的行级锁释放。

排查与应对:

  • pg_stat_activity 查看是否有长事务或未提交的 UPDATE 卡在中间
  • 确保所有涉及同一批数据的写操作都统一走带 SKIP LOCKED 的路径,避免混用普通 UPDATE
  • 设置合理的 lock_timeout(如 SET lock_timeout = '500ms'),让等待失败可被捕获而非无限挂起

批量更新时 LIMIT 和 ORDER BY 的取舍

LIMIT 控制每次取多少行很自然,但若没配 ORDER BY,PostgreSQL 不保证顺序,不同事务可能反复抢同一组 ID,导致某些行长期得不到处理。而加 ORDER BY(尤其非索引列)又可能拖慢查询、引发排序临时文件。

推荐做法:

  • 优先在过滤条件字段上建索引(如 status + created_at),再用 ORDER BY created_at LIMIT 10,这样能走索引扫描
  • 避免 ORDER BY random() 或函数表达式,它们无法利用索引且强制排序
  • 如果业务允许“近似公平”,可用 WHERE id > $last_id ORDER BY id LIMIT 10 做游标分页,比全局排序更轻量

并发安全的完整模板示例

以下是一个生产环境验证过的最小可行模式,用于从待处理队列表中安全取一批任务:

WITH candidates AS (
  SELECT id FROM tasks
  WHERE status = 'pending'
  ORDER BY priority DESC, id
  LIMIT 20
  FOR NO KEY UPDATE SKIP LOCKED
)
UPDATE tasks
SET status = 'processing', worker_id = 'w-abc123', updated_at = now()
WHERE id IN (SELECT id FROM candidates)
RETURNING id, payload;

注意点:

  • FOR NO KEY UPDATEFOR UPDATE 更宽松,适合仅改状态类字段
  • RETURNING 必须有,否则应用拿不到实际被更新的行,无法继续后续逻辑
  • 不要依赖 UPDATE ... RETURNING 的返回顺序做业务判断——它不保证和子查询 ORDER BY 一致

最易被忽略的是:SKIP LOCKED 解决不了长事务阻塞,也解决不了索引缺失导致的锁升级(比如锁整页甚至整个分区)。它只是并发调度的工具,不是万能锁优化器。


# 工具  # 为什么  # sql  # for  # select  # 并发  # postgresql  # 加锁  # 跳过  # 的是  # 只在  # 是一个  # 还没  # 还会  # 已被  # 分页  # 再用 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: uc浏览器二维码扫描入口_uc浏览器扫码功能使用地址  Laravel怎么配置S3云存储驱动_Laravel集成阿里云OSS或AWS S3存储桶【教程】  Laravel如何使用Eloquent ORM进行数据库操作?(CRUD示例)  微信公众帐号开发教程之图文消息全攻略  音响网站制作视频教程,隆霸音响官方网站?  如何用IIS7快速搭建并优化网站站点?  ChatGPT常用指令模板大全 新手快速上手的万能Prompt合集  如何在腾讯云服务器快速搭建个人网站?  Laravel N+1查询问题如何解决_Eloquent预加载(Eager Loading)优化数据库查询  香港服务器建站指南:外贸独立站搭建与跨境电商配置流程  Python高阶函数应用_函数作为参数说明【指导】  如何快速搭建个人网站并优化SEO?  LinuxCD持续部署教程_自动发布与回滚机制  Win11任务栏卡死怎么办 Windows11任务栏无反应解决方法【教程】  青岛网站建设如何选择本地服务器?  英语简历制作免费网站推荐,如何将简历翻译成英文?  如何自定义safari浏览器工具栏?个性化设置safari浏览器界面教程【技巧】  如何快速生成专业多端适配建站电话?  在线教育网站制作平台,山西立德教育官网?  Laravel怎么做数据加密_Laravel内置Crypt门面的加密与解密功能  Laravel的Blade指令怎么自定义_创建你自己的Laravel Blade Directives  Laravel如何获取当前登录用户信息_Laravel Auth门面使用与Session用户读取【技巧】  公司门户网站制作流程,华为官网怎么做?  大同网页,大同瑞慈医院官网?  如何在 Python 中将列表项按字母顺序编号(a.、b.、c. …)  在线制作视频的网站有哪些,电脑如何制作视频短片?  JavaScript常见的五种数组去重的方式  北京网站制作公司哪家好一点,北京租房网站有哪些?  Win11摄像头无法使用怎么办_Win11相机隐私权限开启教程【详解】  Java遍历集合的三种方式  Laravel如何创建自定义Facades?(详细步骤)  手机钓鱼网站怎么制作视频,怎样拦截钓鱼网站。怎么办?  轻松掌握MySQL函数中的last_insert_id()  如何在Windows环境下新建FTP站点并设置权限?  家族网站制作贴纸教程视频,用豆子做粘帖画怎么制作?  Python面向对象测试方法_mock解析【教程】  如何为不同团队 ID 动态生成多个独立按钮  北京网页设计制作网站有哪些,继续教育自动播放怎么设置?  Java类加载基本过程详细介绍  Laravel如何处理JSON字段的查询和更新_Laravel JSON列操作与查询技巧  香港服务器租用费用高吗?如何避免常见误区?  如何在万网利用已有域名快速建站?  网站制作软件有哪些,制图软件有哪些?  Laravel如何使用Telescope进行调试?(安装和使用教程)  JavaScript如何实现错误处理_try...catch如何捕获异常?  网站制作软件免费下载安装,有哪些免费下载的软件网站?  Laravel路由Route怎么设置_Laravel基础路由定义与参数传递规则【详解】  常州企业网站制作公司,全国继续教育网怎么登录?  黑客如何利用漏洞与弱口令入侵网站服务器?  佛山网站制作系统,佛山企业变更地址网上办理步骤?