ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

PostgreSQL FETCH 游标实战:用 TaoToken 统一 Key 跑通分页查询配置

PostgreSQL FETCH 游标实战:用 TaoToken 统一 Key 跑通分页查询配置 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_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 基址是 https://taotoken.net/api 这个不加 UTM。先把 Key 拿到手进入控制台创建 API Key地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 。创建完复制那串 Key后面写进配置文件。如果你只是想先验证模型对话能不能通可以用模型对话页 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 直接试如果是长期编码或 Agent 场景走 Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 更划算接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite API Keys 管理页在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。注意TaoToken 是统一的 API 通道与 Key 管理服务不是数据库代理。PostgreSQL 的游标操作仍然在你的数据库里执行TaoToken 负责的是模型侧请求的统一出口。两者配合的方式是你用同一套 Key 配置去调试「生成 SQL 的模型请求」和「执行 SQL 的数据库连接」减少 Key 散落带来的管理成本。3. 可复制配置settings.json 与游标 SQL 骨架3.1 settings.json 配置片段把下面这段写进你的settings.jsonKey 换成控制台里创建的那串。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_sourcetaotoken_aicg_blog_endutm_contentclaude-code-anthropicutm_campaignrewrite 把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);然后开事务、声明游标、分批 FETCHBEGIN; -- 声明一个可滚动的游标按 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 countcount 是抓取的行数可能是零。在 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 blocksPostgreSQL 的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_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 检查 Key 状态接入细节看文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。想先验证模型对话是否正常用模型对话页 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 试一条长期编码或 Agent 场景走 Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。把FETCH FORWARD 100的批大小按你的内存和网络调通常 100 到 1000 之间比较稳太大反而失去分批的意义。
返回列表