Postgresql游标使用介绍:从DECLARE到FETCH的完整实践与TaoToken统一Key配置

发布时间:2026/10/8 12:31:06
Postgresql游标使用介绍:从DECLARE到FETCH的完整实践与TaoToken统一Key配置 1. 为什么大结果集要用 PostgreSQL 游标从内存爆掉到逐行处理PostgreSQL 游标cursor是什么简单说它就是一个指向查询结果集的“指针”你可以一行一行地取数据而不是一次性把几百万行全塞进内存。它适合谁适合那些需要处理大结果集、内存放不下、且数据可以一条一条处理的场景比如批量对账、逐行清洗、导出大表、定时任务里分批更新。我见过太多人写 PL/pgSQL 时直接SELECT * INTO或者FOR rec IN SELECT ... LOOP数据量小的时候没问题一旦表里几百万行内存直接飙上去甚至把数据库连接拖垮。游标的核心价值就在于按需取数内存友好。PostgreSQL 里游标的使用可以归纳为三步定义游标、打开游标、使用游标FETCH/MOVE/UPDATE/DELETE WHERE CURRENT OF最后关闭游标。定义方式有三种典型形态不绑定 SQL 的refcursor、绑定 SQL 的CURSOR FOR SELECT ...、以及带参数的CURSOR (key integer) FOR SELECT ... WHERE c1 key。绑定 SQL 的可以直接 OPEN带参数的必须在 OPEN 时传值。这篇文章会从 DECLARE 到 FETCH 完整走一遍给出可复制的 SQL 和循环脚本同时演示如何通过 TaoToken 统一 Key/API 通道完成工具侧 Base URL 配置与连通性验证。如果你正在做大数据量分批处理或者想把数据库工具链的 Key 管理统一起来这篇可以跟着做。先准备一张测试表后面所有例子都基于它drop table if exists tf1; create table tf1(c1 int, c2 int, c3 varchar(32), c4 varchar(32), c5 int); insert into tf1 values (1,1000,China,Dalian,23000), (2,4000,Janpan,Tokio,45000), (3,1500,China,Xian,25000), (4,300,China,Changsha,24000), (5,400,USA,New York,35000), (6,5000,USA,Bostom,15000);执行select * from tf1;能看到 6 行数据。数据量虽小但语法和真实大表完全一致你可以把 SELECT 换成任何大表查询来验证内存表现。游标和普通 SELECT 的本质区别在于执行时机普通 SELECT 会把整个结果集物化后返回而游标在 OPEN 时只是建立了一个执行计划FETCH 时才真正逐行从服务器取。对于大结果集这意味着客户端内存占用从 O(n) 降到 O(1)。这也是为什么 ETL 脚本、数据迁移工具、批量任务里游标几乎是标配。2. TaoToken 前置统一 Key 与 API 通道准备在写游标脚本之前先把工具侧的 API 通道准备好。很多做数据库开发的人同时会用多个 AI 编码工具或命令行助手每个工具都要单独配 Key、单独记 Base URL时间一长就乱。TaoToken 的思路是提供一个统一的 Key 和 API 通道工具侧只需要改 Base URL 和 Key 就能接入。你需要先拿到一个可用的 Key。访问官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 注册后进入控制台创建 API Key。控制台地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 在 API Keys 页面可以生成和复制 Key。API 基础地址是 https://taotoken.net/api 注意这个地址不加 UTM 参数直接用于工具配置。如果你用的是 Claude Code 这类命令行编码工具需要配置 Anthropic 兼容的 Base URL可以参考接入文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。文档里会说明不同工具的配置字段。对于长期编码和 Agent 场景Coding Plan 页面 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 有套餐说明适合需要持续调用的情况。这里要强调一个原则TaoToken 是统一的 API 通道不是让你绕过任何正常流程。你拿到的 Key 就是正常调用凭证配置到工具里即可。下面给出一个通用的配置片段以 JSON 形式展示路径和字段名按你实际工具调整{ base_url: https://taotoken.net/api, api_key: sk-你的TaoTokenKey, model: claude-sonnet-4-20250514 }如果你用的是 Cline 或类似支持 MCP 的工具配置里通常需要 Base URL、Key、Model ID 三件套。Base URL 填https://taotoken.net/apiKey 填控制台生成的Model ID 按你实际要用的模型填。这三件套缺一不可尤其是 Model ID填错会直接报模型不存在。对于 Codex 类工具如果它读取auth.json你需要把 Key 和 Base URL 写进去。典型结构如下{ openai: { apiKey: sk-你的TaoTokenKey, baseURL: https://taotoken.net/api } }配置完成后先别急着跑游标脚本做一次连通性验证。可以用 curl 直接测curl -s https://taotoken.net/api/v1/models \ -H Authorization: Bearer sk-你的TaoTokenKey \ -H Content-Type: application/json如果返回模型列表说明 Key 和通道都正常。如果返回 401检查 Key 是否复制完整、有没有多余空格。这一步做完工具侧就准备好了接下来回到 PostgreSQL 游标本身。3. 可复制配置DECLARE、OPEN、FETCH、CLOSE 全流程脚本这一节给出可以直接复制到 psql 里执行的完整脚本。先看三种游标声明方式和对应的 OPEN 方式这是理解游标的关键。第一种是不绑定 SQL 的refcursorOPEN 时才指定查询CREATE OR REPLACE FUNCTION tfun1() RETURNS int AS $$ DECLARE curs1 refcursor; curs2 CURSOR FOR SELECT c1 FROM tf1; curs3 CURSOR (key integer) FOR SELECT * FROM tf1 WHERE c1 key; x int; y tf1%ROWTYPE; BEGIN open curs1 FOR SELECT * FROM tf1 WHERE c1 3; fetch curs1 into y; RAISE NOTICE curs1 : %, y.c3; fetch curs1 into y; RAISE NOTICE curs1 : %, y.c3; open curs2; fetch curs2 into x; RAISE NOTICE curs2 : %, x; fetch curs2 into x; RAISE NOTICE curs2 : %, x; OPEN curs3(4); fetch curs3 into y; RAISE NOTICE curs3 : %, y.c4; fetch curs3 into y; RAISE NOTICE curs3 : %, y.c4; return 0; END; $$ LANGUAGE plpgsql;执行select tfun1();会输出NOTICE: curs1 : China NOTICE: curs1 : USA NOTICE: curs2 : 1 NOTICE: curs2 : 2 NOTICE: curs3 : New York NOTICE: curs3 : Bostom三种方式的区别很清晰curs1是 refcursorOPEN 时绑定 SQLcurs2声明时就绑定 SQL直接 OPENcurs3带参数OPEN 时传key : 4或位置参数4。带参数的游标适合做分批处理比如每次传一个批次边界值。接下来是 FETCH 的方向控制。FETCH 语法是FETCH [direction { FROM | IN }] cursor INTO target;direction 可以是 NEXT、LAST、RELATIVE、ABSOLUTE、FORWARD、BACKWARD 等。看一个完整例子CREATE OR REPLACE FUNCTION tfun2() RETURNS int AS $$ DECLARE curs1 refcursor; y tf1%ROWTYPE; x RECORD; z1 int; z2 int; BEGIN open curs1 FOR SELECT * FROM tf1; fetch last from curs1 into y; RAISE NOTICE fetch into ROWTYPE : %, y.c2; fetch RELATIVE -2 from curs1 into y; RAISE NOTICE fetch into ROWTYPE : %, y.c2; fetch curs1 into y; RAISE NOTICE fetch into ROWTYPE : %, y.c2; fetch curs1 into x; RAISE NOTICE fetch into RECORD : %, x.c2; fetch last from curs1 into z1,z2; RAISE NOTICE fetch into var : %,%, z1, z2; return 0; END; $$ LANGUAGE plpgsql;执行select tfun2();输出NOTICE: fetch into ROWTYPE : 5000 NOTICE: fetch into ROWTYPE : 300 NOTICE: fetch into ROWTYPE : 400 NOTICE: fetch into RECORD : 5000 NOTICE: fetch into var : 6,5000FETCH LAST直接把游标指向最后一行得到 c25000。此时游标在最后一行FETCH RELATIVE -2相对当前位置向前移动 2 行得到 c2300。再FETCH默认 NEXT得到 c2400。FETCH 可以存入 ROWTYPE、RECORD、普通变量但不能一次存入数组需要数组时用select array_agg(id) INTO v_ids代替。MOVE 和 FETCH 语法相同区别是 MOVE 只移动游标不取数据MOVE curs1; MOVE LAST FROM curs3; MOVE RELATIVE -2 FROM curs4; MOVE FORWARD 2 FROM curs4;UPDATE/DELETE WHERE CURRENT OF 是游标的实用特性直接操作当前指向的行CREATE OR REPLACE FUNCTION tfun3() RETURNS int AS $$ DECLARE curs1 refcursor; y tf1%ROWTYPE; BEGIN open curs1 FOR SELECT * FROM tf1; fetch last from curs1 into y; RAISE NOTICE curs1 : %, y.c2; delete from tf1 WHERE CURRENT OF curs1; return 0; END; $$ LANGUAGE plpgsql;执行后最后一行被删除。CLOSE 用于关闭游标释放资源语法是CLOSE cursor;。在函数里游标会在函数结束时自动关闭但显式 CLOSE 是好习惯尤其是长事务里。最后是返回游标给外层调用的方式CREATE OR REPLACE FUNCTION tf4(refcursor) RETURNS refcursor AS BEGIN OPEN $1 FOR SELECT c4 FROM tf1; RETURN $1; END; LANGUAGE plpgsql; BEGIN; SELECT tf4(funccursor); FETCH ALL IN funccursor; COMMIT;这个模式适合把游标作为接口返回调用方自己控制 FETCH 节奏。4. 验证请求与成功结果循环 FETCH 分批处理实测上一节是语法演示这一节给出真实分批处理的循环脚本。假设 employees 表有大量数据我们要逐行处理并打印同时避免内存爆掉DO $$ DECLARE r RECORD; my_cursor CURSOR FOR SELECT name, salary FROM employees; BEGIN OPEN my_cursor; LOOP FETCH my_cursor INTO r; EXIT WHEN NOT FOUND; RAISE NOTICE Name: %, Salary: %, r.name, r.salary; END LOOP; CLOSE my_cursor; END $$;这个匿名块的结构是声明游标并绑定查询OPENLOOP 里 FETCH 到 RECORDEXIT WHEN NOT FOUND判断是否取完处理数据最后 CLOSE。NOT FOUND是 PL/pgSQL 的内置变量FETCH 没有取到行时会被置为 true。对于真正的分批处理更推荐用带参数的游标配合批次边界避免长事务持有游标过久CREATE OR REPLACE FUNCTION batch_process(batch_size int) RETURNS int AS $$ DECLARE curs CURSOR (min_id int, max_id int) FOR SELECT id, name FROM employees WHERE id min_id AND id max_id; rec RECORD; processed int : 0; cur_min int : 0; cur_max int; BEGIN SELECT max(id) INTO cur_max FROM employees; WHILE cur_min cur_max LOOP OPEN curs(cur_min, cur_min batch_size); LOOP FETCH curs INTO rec; EXIT WHEN NOT FOUND; -- 这里写你的逐行处理逻辑 processed : processed 1; END LOOP; CLOSE curs; cur_min : cur_min batch_size; END LOOP; RETURN processed; END; $$ LANGUAGE plpgsql;这个脚本每次处理 batch_size 行处理完关闭游标再开下一批事务不会一直持有大游标。实测下来百万行表用这种方式内存占用稳定在几十 MB而一次性 SELECT 会直接飙到 GB 级别。验证请求是否成功除了看 NOTICE 输出还可以在循环里加计数和日志。执行select batch_process(1000);后返回处理行数和select count(*) from employees;对比数字一致就说明没有漏行。如果你同时用 TaoToken 通道做工具侧验证可以在处理脚本跑完后用模型对话页面 https://taotoken.net/model-chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 发一条测试消息确认 Key 和通道仍然可用。这一步和数据库游标无关但属于工具链连通性验证的一部分。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth这一节对照真实报错给出排查路径。先说数据库侧再说工具侧。数据库侧最常见的错误是cursor xxx does not exist原因通常是游标没 OPEN 就 FETCH或者作用域不对。refcursor 在函数外使用时必须在同一个事务里 OPEN 和 FETCH跨事务会失效。另一个常见错误是cursor can only scan forward这是因为游标声明时没有加SCROLL默认只能向前。需要 RELATIVE 负值或 BACKWARD 时声明要写成CURSOR SCROLL FOR ...。NOT FOUND判断失效也是高频问题。如果你在 FETCH 之后做了别的查询NOT FOUND会被覆盖。正确做法是 FETCH 后立刻判断或者用GET DIAGNOSTICS rowcount ROW_COUNT自己记录。工具侧报错方面401 通常对应 Key 无效或过期。检查步骤确认 Key 从控制台复制完整确认请求头是Authorization: Bearer sk-xxx确认 Base URL 是https://taotoken.net/api而不是别的路径。如果 401 出现在 Claude Code 里检查 Anthropic 相关配置字段是否填对接入文档里有字段说明。local proxy failed一般出现在工具配置了本地代理但代理没启动或者 Base URL 指向了本地地址。解决方法是把 Base URL 改回https://taotoken.net/api去掉本地代理配置。这个报错和网络环境无关纯粹是配置指向问题。reading choices报错通常出现在调用返回结构不符合预期时比如模型 ID 填错导致返回体里没有 choices 字段。检查 Model ID 是否和实际可用模型一致三件套 Base URL、Key、Model ID 是否都填了。缺任何一个都会导致解析失败。OAuth 相关报错出现在需要 OAuth 认证的工具里如果你用的是 API Key 模式需要在配置里明确选择 API Key 认证而不是 OAuth。有些工具默认走 OAuth 流程需要手动切换。排查顺序建议先确认 Key 有效curl 测 models 接口再确认 Base URL 正确再确认 Model ID 存在最后看工具本身的认证模式。这三步能解决 90% 的接入问题。6. 语义一致 CTA把游标脚本和统一 Key 通道用起来游标的核心就三件事DECLARE 定义、OPEN 打开、FETCH 逐行取最后 CLOSE 释放。大结果集场景下它能把内存占用从 O(n) 降到 O(1)这是它最大的价值。带参数的游标配合批次循环是分批处理的标准写法。工具侧的统一 Key 通道本质是减少重复配置。你只需要记住 Base URL 是https://taotoken.net/apiKey 从控制台拿Model ID 按实际填。三件套配好连通性用 curl 或模型对话验证一次即可。如果你在排障或接入阶段先去 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/model-chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 发一条消息最快。长期编码和 Agent 场景Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 有对应方案。最后留一个实用技巧写游标循环时在 LOOP 里加一个计数器每处理 N 行RAISE NOTICE一次进度。这样跑大表时你能看到进度而不是干等。另外游标用完一定 CLOSE长事务里不关游标会一直占着快照影响 vacuum。