PostgreSQL 9.5:SELECT … SKIP LOCKED(含 FOR UPDATE/SHARE)源码级深度解析与9.5→18演进

本文系统解析 PostgreSQL 9.5 引入的 SKIP LOCKED 能力(常与行级锁 SELECT … FOR UPDATE/SHARE 联用),从语法、行为到源码路径与执行流程,帮助读者建立“知其然更知其所以然”的系统性认知;并概览后续版本的演进。包含:概述、背景、名词解释、语法与行为、源码映射、并发与锁管理、性能与配置、兼容性与迁移、Mermaid 流程图(配色优化)、参考资料与“速记口”总结。

简介与项目背景

在任务队列/并行消费者等场景中,经典做法是“选出一批待处理行并加锁”,但在高并发下,行级锁冲突会导致阻塞、队头等待放大。SKIP LOCKED 允许在选取并加锁行时跳过已被其他事务锁住的行,避免阻塞并提升吞吐与整体时延稳定性。

典型应用:多进程/多线程从同一表拉取“未处理任务”,使用 SELECT … FOR UPDATE SKIP LOCKED 块并发消费,已被其它消费者锁住的行会被跳过,避免互相等待。

名词解释

  • Row-level lock(行级锁):通过 SELECT … FOR UPDATE/SHARE 等语法对元组加锁,保证并发修改/读取的正确性。
  • SKIP LOCKED:当目标行被其它事务持有行级锁时,选择跳过该行,而不是等待锁释放。
  • NOWAIT:遇到锁冲突立即报错(不同于 SKIP LOCKED 的“跳过”)。
  • Blocking/Non-blocking:阻塞式等待锁 vs 非阻塞式跳过或报错。

语法与行为

  • 语法要点(9.5 起支持,常用搭配如下):
    • SELECT … FROM tbl WHERE … FOR UPDATE SKIP LOCKED
    • SELECT … FROM tbl WHERE … FOR SHARE SKIP LOCKED
  • 行为语义:
    • FOR UPDATE/SHARE:对目标行加行级锁(UPDATE/SHARE 强度不同)。
    • SKIP LOCKED:如果该行当前被其他事务持锁,则本次选择中直接跳过该行(不阻塞、不报错)。
  • 与 NOWAIT 的差异:
    • NOWAIT:遇锁即报错,适用于不希望等待也不希望跳过的场景。
    • SKIP LOCKED:遇锁即跳过,适用于“尽快获取可处理行”的队列/并发消费场景。

示例 SQL(更多示例):sql/select_skip_locked_examples.sql

源码映射与实现要点

SKIP LOCKED 的实现涉及解析器、锁管理与执行器三大层面:

说明:本文以目录/文件路径级别给出源码位置,函数级调用链可在上述路径内进一步检索,如元组锁获取、冲突检查与“跳过”分支处理。

并发与锁管理关系

  • 目标:在“获取一批可处理行”的过程中最大化非阻塞性。
  • 行级锁获取策略:
    • FOR UPDATE/SHARE 在扫描器尝试锁定元组时,若发现元组已被持锁:
      • NOWAIT:抛错返回。
      • SKIP LOCKED:跳过该元组,继续扫描下一候选行。
  • 可见性与快照:
    • 在 MVCC 下,扫描基于快照判断可见行;锁获取是附加操作。对已锁住但可见的行,SKIP LOCKED 将“可见但不可立即锁定”的行跳过,避免等待。
  • 饥饿与公平性:
    • SKIP LOCKED 提升吞吐,但可能导致部分热门行在高并发竞争下“长时间被跳过”。实际业务需配合调度策略(批次、优先级、重试机制)缓解。

性能与配置建议

  • 索引选择与过滤:
    • 队列获取场景通常结合状态列与索引(如 status、priority、created_at),降低扫描成本。
  • 批量与分片:
    • 可分批次进行 LIMIT 并配合分片/分区,减少同一热点范围的锁竞争。
  • 监控:
    • 关注长尾行被跳过次数与处理延迟,必要时做“无锁阶段重扫”或迁移到其他分片。
  • 与 NOWAIT 的取舍:
    • NOWAIT 更适合“希望在应用层处理冲突”的场景;SKIP LOCKED 更适合“希望尽快拿到可处理集合”的场景。

兼容性与迁移

  • 从阻塞式 FOR UPDATE 迁移:
    • 在不希望等待锁的场景,将原有 FOR UPDATE 替换为 FOR UPDATE SKIP LOCKED;评估业务语义是否允许跳过(例如任务必须按严格顺序处理的场景不适合)。
  • 错误处理与重试:
    • 与 NOWAIT 相比,SKIP LOCKED 不产生错误;应用层需考虑“跳过行”的再处理策略(重试、回收机制)。

Mermaid:并发取数与加锁流程(配色与样式优化)

为缓解视觉疲劳与提升可读性,统一采用柔和配色与节点样式:

开始扫描候选行

尝试 FOR UPDATE/SHARE 加锁

该行是否被他事务持锁?

成功加锁并返回该行

子句为 SKIP LOCKED?

跳过该行继续扫描

子句为 NOWAIT?

报错返回

等待锁释放(阻塞)

生成结果集或继续批量

从 9.5 到 18 的相关演进概览(与 SKIP LOCKED 关联的方向)

  • 9.5:引入 SKIP LOCKED(与 FOR UPDATE/SHARE 联用),在并发消费与队列场景显著提升吞吐与稳定性。
  • 9.6:并行查询与执行器优化增强;在大数据量扫描下受益(配合索引与过滤)。
  • 10:声明式分区引入;在分区环境中将热点任务分布到多个分区可进一步降低竞争。
  • 11~12:优化器与索引层改进,间接改善扫描与加锁的整体成本。
  • 13~14:B-Tree 与 I/O 优化(如页面管理/去重),降低写放大与冲突热点。
  • 15~18:稳定性与可维护性增强;SKIP LOCKED 作为成熟能力保持稳定,配合更丰富的调度/分区策略。

注:上述为与 SKIP LOCKED 关联的宏观方向;具体版本细节以各版本 Release Notes 与文档为准。

参考资料与权威链接

速记口(面试/设计复盘快速记忆)

  • 关键词:SKIP LOCKED、FOR UPDATE/SHARE、行级锁、非阻塞、队列并发。
  • 语法骨架:SELECT … FOR UPDATE|SHARE SKIP LOCKED;NOWAIT 为“遇锁报错”。
  • 源码定位:解析src/backend/parser/gram.y → 锁管理src/backend/storage/lmgr/ → Heap访问src/backend/access/heap/ → 执行器src/backend/executor/
  • 并发语义:遇锁跳过/不等待,提升吞吐但可能产生“热门行长时间被跳过”,需配合调度策略。
  • 性能要点:索引与过滤、批量/分片、监控跳过次数与处理延迟。
  • 迁移提示:从阻塞式 FOR UPDATE 迁移时确认业务允跳过;必要时与 NOWAIT/重试策略结合。

附注:本文通过路径级引用标注源码位置以保持跨版本稳定性与可溯源性;如需函数级细节,可在上述目录中检索具体实现(行锁获取、冲突判断、跳过分支与执行器遍历逻辑)。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐