☰
PostgreSQL FETCH 游标实战:用 TaoToken 统一 Key 跑通分页查询配置
2026/9/28 19:45:28 网站建设 项目流程

1. 为什么要在 PostgreSQL 里用 FETCH 游标做分页

如果你写过LIMIT 100000, 20这种深分页 SQL,大概见过它越翻越慢:数据库得先扫过前面十万行再丢掉,只为了给你最后 20 行。数据量一上来,查询时间从几十毫秒涨到几秒,接口直接超时。PostgreSQL 的游标配合FETCH就是来解决这类问题的——它把一次大查询拆成多次小批量读取,每次只抓固定行数,不用反复重扫。

FETCH是 PostgreSQL 里从游标中检索行的命令,语法骨架是FETCH [direction { FROM | IN }] cursorname。direction 可以是NEXT、PRIOR、FIRST、LAST、ABSOLUTE count、RELATIVE count、FORWARD count、BACKWARD count、ALL等。游标本身有一个位置指针,创建完之后停在第一行之前,每抓一次就往后挪,抓完所有行就停在最后一行之后。这个机制天然适合「分批拉取 + 断点续读」的场景,比如导出百万级订单、后台任务逐批处理、报表分页。

这篇面向需要在 SQL 层做大数据量分批读取的开发者,给出可复制的游标声明与FETCH语句骨架,同时把 TaoToken 统一 Key 的settings.json配置片段一起放进来,让你在验证游标分页的同时,也能用同一套 Key 跑通模型侧的调试请求。适合谁:正在被深分页拖慢的 PostgreSQL 使用者、需要写批处理脚本的后端同学、以及想用统一通道管理多个模型 Key 的开发者。

2. TaoToken 前置:统一 Key 与 API 通道准备

TaoToken 在这里的角色是「统一 Key 管理 + API 通道」。你不需要在每台机器、每个脚本里散落不同的模型 Key,而是把 Key 集中放在一个settings.json里,通过统一的 API 地址调用。官网入口是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 基址是 https://taotoken.net/api (这个不加 UTM)。

先把 Key 拿到手:进入控制台创建 API Key,地址是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite 。创建完复制那串 Key,后面写进配置文件。如果你只是想先验证模型对话能不能通,可以用模型对话页 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite 直接试;如果是长期编码或 Agent 场景,走 Coding Plan https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite 更划算;接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,API Keys 管理页在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 。

注意:TaoToken 是统一的 API 通道与 Key 管理服务,不是数据库代理。PostgreSQL 的游标操作仍然在你的数据库里执行,TaoToken 负责的是模型侧请求的统一出口。两者配合的方式是:你用同一套 Key 配置去调试「生成 SQL 的模型请求」和「执行 SQL 的数据库连接」,减少 Key 散落带来的管理成本。

3. 可复制配置:settings.json 与游标 SQL 骨架

3.1 settings.json 配置片段

把下面这段写进你的settings.json,Key 换成控制台里创建的那串。base_url指向 TaoToken 的 API 地址,api_key用统一 Key,模型名按你实际要用的填。

{ "taotoken": { "base_url": "https://taotoken.net/api", "api_key": "sk-你的统一Key", "default_model": "claude-sonnet-4-20250514", "timeout_seconds": 60 }, "postgres": { "host": "127.0.0.1", "port": 5432, "dbname": "demo", "user": "postgres", "password": "your_password" } }

如果你用的是 Claude Code 这类工具,Anthropic 兼容入口在 https://taotoken.net/claude-code-anthropic?utm_source=taotoken_aicg_blog_end&utm_content=claude-code-anthropic&utm_campaign=rewrite ,把base_url指过去即可。这样模型请求和数据库连接都从同一份配置读,改 Key 只改一处。

3.2 游标声明与 FETCH 语句骨架

游标必须在事务里用,因为DECLARE CURSOR默认只在当前事务有效。下面是最小可运行骨架,先建一张测试表灌点数据。

-- 建测试表并灌 1000 行 CREATE TABLE IF NOT EXISTS orders ( id serial PRIMARY KEY, user_id int, amount numeric(10,2), created_at timestamptz DEFAULT now() ); INSERT INTO orders (user_id, amount) SELECT (random()*100)::int, (random()*500)::numeric(10,2) FROM generate_series(1, 1000);

然后开事务、声明游标、分批 FETCH:

BEGIN; -- 声明一个可滚动的游标,按 id 排序 DECLARE order_cursor SCROLL CURSOR FOR SELECT id, user_id, amount, created_at FROM orders ORDER BY id; -- 抓前 100 行 FETCH FORWARD 100 FROM order_cursor; -- 再抓下一批 100 行 FETCH FORWARD 100 FROM order_cursor; -- 回退到第一行 FETCH FIRST FROM order_cursor; -- 抓最后一行 FETCH LAST FROM order_cursor; -- 关闭游标并提交 CLOSE order_cursor; COMMIT;

SCROLL选项是关键:如果你想用FETCH PRIOR、FETCH BACKWARD或带负数的FETCH FORWARD,游标必须声明为SCROLL。不声明的话,简单查询 PostgreSQL 有时也允许反向抓取,但别依赖这个行为。如果声明了NO SCROLL,反向抓取会直接报错。

3.3 分页循环的写法

实际批处理里,你通常用一个循环反复FETCH FORWARD N,直到返回行数为 0。在 psql 里可以这样观察:

BEGIN; DECLARE c SCROLL CURSOR FOR SELECT id FROM orders ORDER BY id; FETCH FORWARD 200 FROM c; -- 返回 200 行 FETCH FORWARD 200 FROM c; -- 返回 200 行 FETCH FORWARD 200 FROM c; -- 返回 200 行 FETCH FORWARD 200 FROM c; -- 返回 200 行 FETCH FORWARD 200 FROM c; -- 返回 200 行 FETCH FORWARD 200 FROM c; -- 返回 0 行,说明抓完了 CLOSE c; COMMIT;

每次FETCH成功会返回一个命令标签FETCH count,count 是抓取的行数,可能是零。在 psql 里命令标签不直接显示,它用抓取的行数替代了。

4. 验证请求与成功结果

4.1 用 psql 验证游标分页是否生效

先确认游标位置行为符合预期。执行下面这段,观察每次 FETCH 返回的行数和内容:

BEGIN; DECLARE v_cursor SCROLL CURSOR FOR SELECT id, amount FROM orders ORDER BY id; FETCH FIRST FROM v_cursor; -- 第 1 行 FETCH NEXT FROM v_cursor; -- 第 2 行 FETCH ABSOLUTE 500 FROM v_cursor; -- 第 500 行 FETCH RELATIVE -1 FROM v_cursor; -- 第 499 行 FETCH BACKWARD 3 FROM v_cursor; -- 往回抓 3 行 CLOSE v_cursor; COMMIT;

成功的结果是:FETCH FIRST返回 1 行,FETCH NEXT返回 1 行,FETCH ABSOLUTE 500返回第 500 行,FETCH RELATIVE -1返回第 499 行,FETCH BACKWARD 3返回 3 行。如果FETCH ABSOLUTE 500返回空,说明游标没声明SCROLL或者数据不够 500 行。

4.2 用 TaoToken 统一 Key 跑一次模型侧验证

游标 SQL 验证通过后,用同一份settings.json里的 Key 发一个模型请求,确认通道是通的。下面用 curl 演示,Key 从配置里读:

curl -s https://taotoken.net/api/v1/messages \ -H "Content-Type: application/json" \ -H "x-api-key: sk-你的统一Key" \ -H "anthropic-version: 2023-06-01" \ -d '{ "model": "claude-sonnet-4-20250514", "max_tokens": 256, "messages": [ {"role": "user", "content": "用一句话说明 PostgreSQL FETCH 游标分页相比 LIMIT OFFSET 的优势"} ] }'

成功时你会拿到一个 JSON 响应,content数组里有模型返回的文本。这一步的意义是:你的统一 Key 既能驱动模型请求,又能配合数据库连接做 SQL 调试,不用在两套 Key 之间来回切换。

4.3 分页结果校验

批处理场景里,光看 FETCH 返回行数不够,还要校验数据没漏没重。一个简单办法是记录每批的 id 区间:

BEGIN; DECLARE chk CURSOR FOR SELECT id FROM orders ORDER BY id; FETCH FORWARD 100 FROM chk; -- 记下最小 id 和最大 id FETCH FORWARD 100 FROM chk; -- 下一批的 id 应紧接上一批 CLOSE chk; COMMIT;

如果两批 id 区间有重叠或跳跃,检查ORDER BY是否稳定(有没有并列值导致顺序不确定)。游标分页依赖排序的确定性,排序字段有重复值时,不同批次可能抓到同一行或漏行。

5. 本篇常见错排查

5.1 报错:cursor can only scan forward

完整报错通常是ERROR: cursor can only scan forward或HINT: Declare it with SCROLL option to enable backward scan.。原因是你用了FETCH PRIOR、FETCH BACKWARD或负数FETCH FORWARD,但游标声明时没加SCROLL。解决:把DECLARE xxx CURSOR FOR ...改成DECLARE xxx SCROLL CURSOR FOR ...。如果游标已经声明为NO SCROLL,反向抓取一定失败,只能重新声明。

5.2 报错:cursor "xxx" does not exist

这个报错说明游标名拼错,或者游标已经CLOSE了,或者你跨了事务。游标默认只在声明它的事务内有效,事务一提交或回滚,游标就没了。如果你在事务 A 里DECLARE,在事务 B 里FETCH,必然报这个错。解决:把DECLARE和所有FETCH放在同一个BEGIN ... COMMIT里。

5.3 报错:DECLARE CURSOR can only be used in transaction blocks

PostgreSQL 的DECLARE CURSOR必须在事务块里执行。如果你在自动提交模式下直接跑DECLARE,就会看到这个提示。解决:前面加BEGIN;,后面加COMMIT;。用 psql 时注意\set AUTOCOMMIT off或者手动包事务。

5.4 FETCH 返回 0 行但数据明明存在

几种可能:游标已经抓到最后一行之后了,再FETCH NEXT自然返回空;或者FETCH ABSOLUTE count的 count 超出范围,游标被定位到第一行之前或最后一行之后;或者RELATIVE 0、FORWARD 0、BACKWARD 0在游标位于边界时返回空。解决:先FETCH FIRST把游标拉回开头,再继续抓。另外注意ABSOLUTE 0是定位到第一行之前,不是第一行。

5.5 性能问题:ABSOLUTE 抓取并不快

FETCH ABSOLUTE 500看起来像「直接跳到第 500 行」,但底层实现必须遍历前面 499 行才能到达。负数绝对抓取更糟:查询得一直读到结尾找到最后一行,再反向遍历。所以别用ABSOLUTE做大跳页,它不比相对移动快。真正高效的是顺序FETCH FORWARD N,这也是游标分页比LIMIT OFFSET快的原因——它不重扫。

5.6 游标里更新数据不被支持

PostgreSQL 不支持在游标中更新数据。如果你FETCH出来的行想直接UPDATE ... WHERE CURRENT OF cursor_name,这条路在 PostgreSQL 里走不通。解决:把 FETCH 出来的 id 收集起来,用单独的UPDATE ... WHERE id IN (...)批量更新。

6. 继续用 TaoToken 统一 Key 跑通你的分页链路

游标分页的验证链路到这里就完整了:DECLARE ... SCROLL CURSOR声明、FETCH FORWARD N分批抓取、CLOSE收尾,配合settings.json里的统一 Key 管理模型请求和数据库连接。如果你在接入过程中遇到 Key 配置或通道问题,去 API Keys 管理页 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 检查 Key 状态,接入细节看文档 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 。想先验证模型对话是否正常,用模型对话页 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite 试一条;长期编码或 Agent 场景走 Coding Plan https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite 。把FETCH FORWARD 100的批大小按你的内存和网络调,通常 100 到 1000 之间比较稳,太大反而失去分批的意义。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询