PostgreSQL游标滚动与敏感度设置

来源:个人站长网作者:松松建站头衔:草根站长
导读:本期聚焦于松松建站创作的《PostgreSQL游标滚动与敏感度设置》,敬请观看详情。当一条 SELECT 语句返回的结果集大到无法一次性装进内存时,游标就成了 PostgreSQL 提供的标准解决方案。它允许应用程序分批取回数据,而不是等待整个结果集生成完毕。不过在声明游标时,有两个容易被忽视的属性会直接影响使用方式:滚动性(SCROLL)和敏感度(INSENSITIVE)。前者决定了游标能否反向遍历,后者决定了游标能否感知底层数据的

当一条 SELECT 语句返回的结果集大到无法一次性装进内存时,游标就成了 PostgreSQL 提供的标准解决方案。它允许应用程序分批取回数据,而不是等待整个结果集生成完毕。不过在声明游标时,有两个容易被忽视的属性会直接影响使用方式:滚动性(SCROLL)和敏感度(INSENSITIVE)。前者决定了游标能否反向遍历,后者决定了游标能否感知底层数据的并发修改。本文将结合示例详细讲解这两个属性的用法和陷阱。

PostgreSQL游标滚动与敏感度设置

游标滚动性:SCROLL 与 NO SCROLL 的区别

滚动性控制的是游标在结果集中的移动方向。声明为 SCROLL 的游标支持 FETCH PRIOR、FETCH FIRST、FETCH LAST、FETCH RELATIVE 以及 FETCH ABSOLUTE 等全方位的移动操作;而 NO SCROLL 游标只能从头到尾单向前进,也就是仅支持 FETCH NEXT。这个看似简单的区别背后,其实涉及 PostgreSQL 存储结果集的方式。

需要特别注意的是,通过 SQL 层面 DECLARE ... CURSOR 声明的普通查询游标,默认就是 NO SCROLL 的。如果尝试在一个默认游标上执行 FETCH BACKWARD,PostgreSQL 会直接报错,提示游标不能反向取数据。原因在于 PostgreSQL 对简单查询采用了流式执行策略——结果行是边执行边产生的,执行过的行如果没有显式缓存,就无法回退重新读取。要在 SQL 层获得滚动能力,必须在声明时显式写上 SCROLL 关键字,此时 PostgreSQL 会物化一份临时结果集,代价是额外的内存或临时文件开销。

-- 声明一个可滚动的游标
DECLARE cur_order SCROLL CURSOR FOR
    SELECT order_id, amount FROM orders ORDER BY order_id;

-- 取最后一行
FETCH LAST FROM cur_order;

-- 向前回退两行
FETCH RELATIVE -2 FROM cur_order;

-- 直接跳转到第 3 行
FETCH ABSOLUTE 3 FROM cur_order;

CLOSE cur_order;

在 PL/pgSQL 存储过程中,情况正好相反:过程语言里的游标默认是可滚动的(相当于 SCROLL),如果明确知道只需要单向遍历,建议显式声明为 NO SCROLL,避免数据库为回退能力预留物化缓冲,从而降低内存占用。下面是一个 PL/pgSQL 中的声明示例:

DO $$
DECLARE
    cur NO SCROLL CURSOR FOR SELECT id FROM t_limit_test;
    rec RECORD;
BEGIN
    OPEN cur;
    LOOP
        FETCH cur INTO rec;
        EXIT WHEN NOT FOUND;
        RAISE NOTICE '当前行 id = %', rec.id;
    END LOOP;
    CLOSE cur;
END $$;

敏感度设置:INSENSITIVE 与 SENSITIVE 的行为差异

敏感度描述的是游标对底层数据并发变化的可见性。按照 SQL 标准,INSENSITIVE 游标看到的是声明时刻的数据快照,事务内其他语句对源表的修改不会反映到游标结果中;SENSITIVE 游标则相反,能够感知这些变化。PostgreSQL 在文档中声明其游标默认且仅支持 INSENSITIVE 语义——即使在声明时写上 SENSITIVE 或 INSENSITIVE 关键字,实际行为仍然是 INSENSITIVE,SENSITIVE 会被静默接受而不报错。

这背后的原因是 PostgreSQL 的一致性读机制。游标读取数据时依赖 MVCC 多版本快照,整个游标生命周期内使用同一个快照,因此自然隔离了后续修改。举例来说,先声明一个游标,然后在同一事务中 UPDATE 源表并提交部分逻辑,再通过游标 FETCH,取到的依然是声明时的旧数据。这一点从其他数据库(如 SQL Server 的动态游标)迁移过来的开发者尤其要留意,行为预期可能完全不同。

BEGIN;

DECLARE cur_ins INSENSITIVE CURSOR FOR
    SELECT name FROM users WHERE active = true;

-- 即使这里修改了 users 表,游标读到的仍是声明时的数据
UPDATE users SET active = false WHERE id = 1;

FETCH ALL FROM cur_ins;  -- 返回结果仍包含 id = 1 的记录

COMMIT;

如果业务上确实需要感知最新数据,可行的替代方案有两个:一是修改完成后重新声明游标并重新打开;二是放弃游标,改用普通查询直接读取当前快照。试图依赖游标的敏感性来实现实时读取,在 PostgreSQL 中是行不通的。

实际使用中的注意事项与性能建议

首先是滚动性与性能的权衡。SCROLL 游标需要物化结果集,当原始查询返回千万行数据时,物化开销可能非常大,甚至撑爆临时文件空间。生产环境中,只有确实需要随机跳转或回退遍历时才使用 SCROLL,普通的分批拉取场景(例如 ETL 导出)用 NO SCROLL 就够了。

其次是事务边界问题。不带 WITH HOLD 选项的游标必须在一个事务块内使用,事务提交或回滚后游标自动销毁。如果希望游标跨事务存活,需要使用 WITH HOLD,但此时 PostgreSQL 会强制在事务提交时物化整个结果集,同样会带来不小的存储开销。另外,WITH HOLD 游标必须是只读的,不能配合 FOR UPDATE 使用。

最后给出一个综合示例,展示跨事务的滚动游标用法:

BEGIN;
DECLARE cur_hold SCROLL CURSOR WITH HOLD FOR
    SELECT id, title FROM articles ORDER BY created_at DESC;

FETCH NEXT 10 FROM cur_hold;  -- 先取前 10 条
COMMIT;  -- 游标仍然存活

BEGIN;
FETCH PRIOR FROM cur_hold;    -- 回退一行
CLOSE cur_hold;
COMMIT;

总结一下:PostgreSQL 的游标默认不可滚动且不敏感,SQL 层游标需要显式声明 SCROLL 才能反向移动,而 PL/pgSQL 游标默认可滚动、建议按需声明 NO SCROLL;敏感度方面 PostgreSQL 只实现 INSENSITIVE 语义,需要最新数据时应重新打开游标。理解这两个属性,能帮助你在报表分页、批量导出、流式处理等场景中写出更可靠高效的代码。

修改时间:2026-09-12 07:00:29

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/20260912/55175.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。