ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

用Python_OracleDB 的调用Oracle存储过程返回Cursor的方法:TaoToken 统一 Key 通道下的可复制配置与验证

用Python_OracleDB 的调用Oracle存储过程返回Cursor的方法:TaoToken 统一 Key 通道下的可复制配置与验证 1. Python 调用 Oracle 存储过程返回 Cursor 的真实场景与坑点如果你正在用 Python 连 Oracle并且存储过程里返回的是SYS_REFCURSOR那你大概率会遇到一个很典型的问题普通cursor.execute()执行完拿不到结果集或者拿到的对象不是可迭代的行而是一个绑定变量句柄。这个场景在报表查询、批量数据导出、老系统对接里非常常见尤其是 Oracle 存储过程把查询结果通过OUT参数以游标形式吐出来的时候。我先把结论说清楚Python 的oracledb驱动也就是原来cx_Oracle的新名字调用返回 Cursor 的存储过程核心动作只有三步——用cursor.var()声明一个游标类型的绑定变量、把它作为OUT参数传进begin ... end;匿名块、执行完之后直接对这个游标变量做for row in cursor_var迭代。听起来简单但真正卡人的地方在于连接池怎么配、绑定变量类型怎么声明、字段映射怎么对齐、异常怎么捕获。这几个点任何一个没处理好你看到的报错就是ORA-01036、DPI-1010、ORA-06550这类让人头大的信息。这篇内容聚焦的就是这条完整链路从连接串配置到存储过程调用再到游标读取和异常处理。我会给出可以直接复制的配置片段也会用一个最小的存储过程来验证 Cursor 返回是否正常。如果你本地已经装好了 Oracle 客户端和 Python 环境跟着走一遍就能复现。另外说明一下本文里涉及模型调用通道的部分统一走 TaoToken 的 Key 通道来做演示目的是让配置片段保持一致的鉴权方式方便你在同一套环境里既跑数据库调用、又跑模型辅助排查。数据库本身还是连你自己的 Oracle 实例TaoToken 只负责模型侧的统一入口两者不混。先明确几个前置条件避免你走到一半发现环境不对Python 3.9 及以上推荐 3.11。安装oracledbpip install oracledb。注意不要再用cx_Oracle新项目直接用oracledb它是官方维护的。Oracle 客户端库Instant Client要能被找到或者用 thin 模式。oracledb默认 thin 模式不需要 Instant Client但如果你要用某些高级特性比如特定的字符集、外部认证还是得配lib_dir。有一个可用的 Oracle 实例里面有建存储过程的权限。我试过在 thin 模式下直接连 Oracle 19c 和 21c基本没问题。但如果你连的是比较老的 11gthin 模式可能不支持那就得装 Instant Client 并设置ORACLE_CLIENT_LIB_DIR或者初始化时传lib_dir。存储过程这边假设我们有一个测试用的过程签名大概是这样CREATE OR REPLACE PROCEDURE test1( param1 IN VARCHAR2, param2 IN VARCHAR2, p_cursor OUT SYS_REFCURSOR ) AS BEGIN OPEN p_cursor FOR SELECT a1 AS col_a, a2 AS col_b FROM dual UNION ALL SELECT b1, b2 FROM dual; END; /这个过程的逻辑很简单接收两个入参打开一个游标返回两行数据。用它来验证 Cursor 返回是最小可复现的。你可以在 SQL Developer 或者 sqlplus 里先跑一遍确认过程本身没问题再去 Python 里调。很多人第一次调的时候会把OUT SYS_REFCURSOR当成普通返回值处理写成result cursor.callproc(...)结果发现拿不到行。这就是没理解 Oracle 的游标是绑定变量机制。正确做法是用cursor.var(oracledb.CURSOR)声明一个游标变量传进去执行后迭代它。还有一个高频坑连接池的dsn写法。192.168.3.169:1521/orclpdb1这种是 EZConnect 格式host:port/service_name。如果你用的是 SID 而不是 service name得写成host:port/SID或者用完整的(DESCRIPTION...)。写错了就是ORA-12514或者DPY-6005。下面进入具体配置。我会先讲 TaoToken 的前置准备再给可复制的连接与调用代码然后是验证和排错。2. TaoToken 统一 Key 通道的前置准备与模型辅助排查这一节说清楚 TaoToken 在这套流程里扮演什么角色。你调 Oracle 存储过程本身不需要 TaoToken数据库连接是直连你的 Oracle 实例。但实际开发中你经常需要让模型帮你解释报错、生成 SQL、检查绑定变量写法这时候如果每个工具都单独配 Key会很乱。TaoToken 提供的是一个统一的 Key 通道把模型调用收敛到一个入口Base URL 和 Key 配一次多个工具复用。官网入口在这里https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。你可以先注册拿到 Key后面配置里会用到。拿到 Key 之后模型侧的 Base URL 统一用https://taotoken.net/api注意这个地址不带 UTM 参数是给程序调用的。Key 的格式一般是一串以sk-开头的字符串具体以你控制台里显示的为准。如果你只是想让模型帮你排查 Oracle 报错最直接的方式是打开模型对话页面把报错原文贴进去。模型对话入口https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 。这个页面适合快速问一句“这个 ORA-01036 是什么原因”不用写代码。如果你是要长期做编码、写 Agent、跑自动化脚本那更适合用 Coding Plan。Coding Plan 入口https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 。它的定位是给编码场景用的套餐配合 Claude Code、Cline 这类工具比较顺。Key 的管理在控制台https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 。你可以在这里创建、查看、吊销 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 里面有各语言的调用示例配环境变量、改 Base URL 的写法都能查到。如果你用的是 Claude Code它有自己的接入方式参考https://taotoken.net/claude-code-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaude_codeutm_campaignrewrite 。这个页面讲的是怎么把 Claude Code 接到统一通道上。这里要强调一点TaoToken 是模型调用的统一入口不是数据库代理也不是什么中转。你的 Oracle 连接串、账号密码、存储过程调用全都走你自己的网络和数据库跟 TaoToken 无关。TaoToken 只处理模型 API 的鉴权收敛。把这两件事分清楚后面配置就不会混。前置准备清单注册并拿到 TaoToken Key。确认模型 Base URL 是https://taotoken.net/api。本地 Python 环境装好oracledb。Oracle 实例可连测试存储过程已创建。如果要让模型辅助排查准备好模型对话或 Coding Plan 的入口。环境变量建议这样设避免 Key 硬编码在代码里export TAOTOKEN_API_KEYsk-你的key export TAOTOKEN_BASE_URLhttps://taotoken.net/apiWindows 下用set或者系统环境变量界面设置。设完之后Python 里用os.environ.get(TAOTOKEN_API_KEY)读取。模型侧的最小验证可以用 curl 快速确认 Key 通不通curl https://taotoken.net/api/v1/models \ -H Authorization: Bearer $TAOTOKEN_API_KEY返回模型列表就说明 Key 和 Base URL 都对。这一步不通后面模型辅助排查就别指望了。常见问题是 Key 复制时带了空格或者 Base URL 写成了带 UTM 的地址。记住程序调用用https://taotoken.net/api不带参数。数据库侧的前置确认oracledb版本python -c import oracledb; print(oracledb.__version__)输出类似2.5.0就正常。如果报ModuleNotFoundError回去pip install oracledb。到这里模型通道和数据库环境都准备好了。下一节进入可复制的配置片段包括连接池、绑定变量声明、存储过程调用。3. 可复制的连接池与存储过程调用配置片段这一节是全文的核心给出可以直接复制运行的代码。我会把连接池配置、游标变量声明、存储过程调用、结果读取拆开讲每一段都标清楚路径和参数含义。先看连接池配置。用oracledb.create_pool()创建池参数含义如下import oracledb pool oracledb.create_pool( userdamao, passwordwoaiwojia, dsn192.168.3.169:1521/orclpdb1, min1, max4, increment1, )user和password是你的 Oracle 账号。dsn是 EZConnect 格式host:port/service_name。min是池启动时的最小连接数max是最大连接数increment是每次扩容增加的连接数。这几个值按你实际并发调本地测试min1, max4足够。如果你要用 SQLAlchemy可以这样接from sqlalchemy import create_engine from sqlalchemy.pool import NullPool engine create_engine( oracleoracledb://, creatorpool.acquire, poolclassNullPool, )注意creatorpool.acquire是把池的获取连接方法交给 SQLAlchemypoolclassNullPool是避免 SQLAlchemy 再套一层池。这个组合在需要复用 oracledb 池的场景下比较常见。接下来是调用存储过程的关键部分。假设存储过程签名是test1(param1 IN VARCHAR2, param2 IN VARCHAR2, p_cursor OUT SYS_REFCURSOR)调用代码如下connect pool.acquire() cursor connect.cursor() # 声明游标类型的绑定变量 cursor_var cursor.var(oracledb.CURSOR) cursor.execute( begin test1(:param1, :param2, :p_cursor); end; , param1a1, param2a2, p_cursorcursor_var, ) # 迭代游标变量拿结果 for row in cursor_var: print(row , row) cursor.close() connect.close() pool.close()这里有几个点必须说清楚第一cursor.var(oracledb.CURSOR)声明的是游标类型绑定变量。oracledb.CURSOR是常量对应 Oracle 的SYS_REFCURSOR。不要写成oracledb.NUMBER或者oracledb.STRING类型不对会报DPI-1010或者ORA-06550。第二匿名块里用:p_cursor作为占位符执行时通过关键字参数p_cursorcursor_var传入。参数名要和占位符一致顺序无所谓因为是按名字绑定的。第三执行完之后直接for row in cursor_var迭代。cursor_var本身是可迭代的每次迭代返回一行行是 tuple 或者命名元组取决于你的rowfactory设置。如果你需要拿到字段名可以这样columns [d[0] for d in cursor_var.description] print(columns , columns) for row in cursor_var: print(dict(zip(columns, row)))cursor_var.description返回字段描述每个元素第一个是字段名。这样就能把行映射成字典方便后续处理。如果你用的是 SQLAlchemy 的engine.connect()调用方式类似但要注意连接是从 engine 拿的with engine.connect() as conn: raw conn.connection cur raw.cursor() cur_var cur.var(oracledb.CURSOR) cur.execute( begin test1(:p1, :p2, :pc); end;, p1a1, p2a2, pccur_var, ) for row in cur_var: print(row) cur.close()conn.connection拿到的是底层 oracledb 连接这样才能用cursor.var()。SQLAlchemy 的text()包装层不支持游标绑定变量所以必须下沉到原生连接。关于参数类型如果你的存储过程入参是数字Python 侧传 int 或 float 就行oracledb 会自动映射。如果是日期传datetime.datetime对象。如果是 CLOB/BLOB需要额外声明类型这里不展开。一个完整的可运行脚本把上面拼起来import oracledb def main(): pool oracledb.create_pool( userdamao, passwordwoaiwojia, dsn192.168.3.169:1521/orclpdb1, min1, max4, increment1, ) connect pool.acquire() try: cursor connect.cursor() cursor_var cursor.var(oracledb.CURSOR) cursor.execute( begin test1(:param1, :param2, :p_cursor); end; , param1a1, param2a2, p_cursorcursor_var, ) columns [d[0] for d in cursor_var.description] print(columns , columns) for row in cursor_var: print(row , row) cursor.close() finally: connect.close() pool.close() if __name__ __main__: main()这个脚本直接跑能打印出字段名和两行数据就说明整条链路通了。如果你在配置模型辅助排查比如用 Cline 或 Claude Code需要写全三件套Base URL、Key、Model ID。以 Cline 的 MCP 配置为例JSON 片段如下{ mcpServers: { taotoken: { command: npx, args: [-y, taotoken/mcp-server], env: { TAOTOKEN_BASE_URL: https://taotoken.net/api, TAOTOKEN_API_KEY: sk-你的key, TAOTOKEN_MODEL: claude-3-5-sonnet } } } }这里TAOTOKEN_BASE_URL、TAOTOKEN_API_KEY、TAOTOKEN_MODEL就是三件套缺一不可。Model ID 按你实际可用的填控制台里能查到。如果你用 Codex它的auth.json配置类似{ base_url: https://taotoken.net/api, api_key: sk-你的key, model: claude-3-5-sonnet }路径一般在~/.codex/auth.json或者项目根目录的.codex/auth.json按你的 Codex 版本为准。这些配置的作用是让模型工具能通过统一通道调用跟 Oracle 调用是两条独立的线。你排查 Oracle 报错时可以把报错贴给模型让它解释。配置片段到这里。下一节讲怎么验证请求成功、怎么确认 Cursor 返回正确。4. 验证请求与成功结果从执行到字段映射配置写完之后必须验证。验证分两层模型通道通不通数据库调用通不通。先快速过模型通道再重点讲数据库。模型通道验证前面 curl 已经给过。再补一个 Python 版本import os import requests resp requests.get( https://taotoken.net/api/v1/models, headers{Authorization: fBearer {os.environ[TAOTOKEN_API_KEY]}}, timeout10, ) print(resp.status_code) print(resp.json())返回 200 和模型列表说明 Key 和 Base URL 都对。如果 401检查 Key如果连接超时检查网络。数据库调用验证跑上一节的完整脚本。预期输出columns [COL_A, COL_B] row (a1, a2) row (b1, b2)字段名是大写因为 Oracle 默认把未加引号的标识符转大写。行是 tuple顺序和description一致。如果你看到这个输出说明 Cursor 返回、字段映射、游标迭代都正常。如果存储过程返回的字段有别名比如SELECT col_a AS myCol那description里就是myCol大小写按你写的来。这点在做字段映射时要注意别硬编码大写。验证字段映射可以加一段类型检查for d in cursor_var.description: print(name , d[0], type , d[1], size , d[3])d[1]是类型码d[3]是显示大小。这样你能确认每个字段的类型是否符合预期。比如VARCHAR2对应oracledb.DB_TYPE_VARCHARNUMBER对应oracledb.DB_TYPE_NUMBER。验证多行返回把存储过程改成返回 100 行看迭代是否完整CREATE OR REPLACE PROCEDURE test_many( p_cursor OUT SYS_REFCURSOR ) AS BEGIN OPEN p_cursor FOR SELECT LEVEL AS n, row_ || LEVEL AS label FROM dual CONNECT BY LEVEL 100; END; /Python 侧cursor_var cursor.var(oracledb.CURSOR) cursor.execute(begin test_many(:pc); end;, pccursor_var) count 0 for row in cursor_var: count 1 print(total rows , count)输出total rows 100就说明大结果集也能正常迭代。注意游标是流式读取的不会一次性把 100 行全加载到内存这对大结果集友好。验证异常处理故意传错参数类型try: cursor.execute( begin test1(:p1, :p2, :pc); end;, p1123, # 故意传数字存储过程期望 VARCHAR2 p2a2, pccursor_var, ) except oracledb.DatabaseError as e: error, e.args print(code , error.code) print(message , error.message)预期捕获到ORA-06502或者类型转换相关错误。这样你能确认异常捕获路径是通的。验证连接池复用连续调用多次看连接是否正常归还for i in range(5): conn pool.acquire() cur conn.cursor() cv cur.var(oracledb.CURSOR) cur.execute(begin test1(:p1, :p2, :pc); end;, p1a, p2b, pccv) rows list(cv) print(fround {i}, rows {len(rows)}) cur.close() conn.close()输出 5 轮每轮 2 行说明池的获取和归还正常。如果池耗尽会卡在pool.acquire()这时候检查max是不是太小或者有没有连接没关。验证成功之后把结果和预期对照。我一般会写一个简单的断言assert columns [COL_A, COL_B] assert len(rows) 2 assert rows[0] (a1, a2)断言通过整条链路就算验证完毕。这里再提一下模型辅助验证的用法。如果你在验证过程中遇到报错可以把报错和你的代码片段一起贴到模型对话里让它帮你定位。比如贴DPI-1010: invalid binding type模型会告诉你绑定变量类型声明错了。这种用法比你自己翻文档快。验证阶段常见的结果异常输出columns []说明游标没打开存储过程可能没执行到OPEN检查过程逻辑。输出行数不对检查存储过程的WHERE条件或者绑定变量是否传对。字段名乱码检查数据库字符集和客户端字符集是否一致。迭代时报DPI-1067游标已经被关闭或者被消费过检查是否重复迭代。下一节专门讲这些报错的排查。5. 本篇常见报错排查401、DPI-1010、ORA-01036 与游标读取异常这一节按真实报错来对照给出原因和修复动作。我把最常见的几类列出来你遇到哪个查哪个。401 Unauthorized模型通道报错原文{error: {message: Invalid API key, type: invalid_request_error}}原因TaoToken Key 不对或者请求头没带对。检查Authorization: Bearer sk-xxx格式Key 前后有没有空格。Base URL 必须是https://taotoken.net/api不要带 UTM 参数。如果 Key 刚创建等几秒再试。修复echo $TAOTOKEN_API_KEY curl https://taotoken.net/api/v1/models -H Authorization: Bearer $TAOTOKEN_API_KEYlocal proxy failed模型通道报错原文local proxy failed: dial tcp 127.0.0.1:7890: connect: connection refused原因本地配了代理但代理没启动或者代理端口不对。这个报错跟 TaoToken 无关是你本地网络配置的问题。检查环境变量HTTP_PROXY、HTTPS_PROXY或者工具自己的代理设置。修复关掉代理或者把代理指向正确的端口。如果你不需要代理直接 unsetunset HTTP_PROXY unset HTTPS_PROXYDPI-1010: invalid binding type报错原文oracledb.exceptions.DatabaseError: DPI-1010: invalid binding type原因绑定变量类型声明错了。最常见的是把游标变量声明成了oracledb.NUMBER或oracledb.STRING。存储过程的OUT SYS_REFCURSOR必须用cursor.var(oracledb.CURSOR)。修复# 错误 cursor_var cursor.var(oracledb.NUMBER) # 正确 cursor_var cursor.var(oracledb.CURSOR)ORA-01036: illegal variable name/number报错原文oracledb.exceptions.DatabaseError: ORA-01036: illegal variable name/number原因占位符和传入参数不匹配。比如匿名块里写了:p_cursor但执行时传的是cursorcursor_var名字对不上。或者占位符数量不对。修复占位符名字和执行参数名严格一致。# 错误 cursor.execute(begin test1(:p1, :p2, :pc); end;, param1a, param2b, cursorcursor_var) # 正确 cursor.execute(begin test1(:p1, :p2, :pc); end;, p1a, p2b, pccursor_var)ORA-06550: PLS-00306: wrong number or types of arguments报错原文oracledb.exceptions.DatabaseError: ORA-06550: line 1, column 7: PLS-00306: wrong number or types of arguments in call to TEST1原因调用存储过程时参数个数或类型不对。检查存储过程签名确认入参和出参数量、类型匹配。比如过程需要 3 个参数你只传了 2 个。修复对照DESC test1的输出逐个核对参数。DESC test1;ORA-12514: TNS:listener does not currently know of service报错原文oracledb.exceptions.DatabaseError: ORA-12514: TNS:listener does not currently know of service requested in connect descriptor原因dsn里的 service name 写错了。192.168.3.169:1521/orclpdb1里的orclpdb1必须是数据库实际注册的 service name。修复在数据库服务器上查SELECT name FROM v$services;用查到的 service name 替换。reading choices 相关报错模型通道报错原文Error reading choices: unexpected end of JSON input原因模型返回的响应不完整通常是网络中断或者超时。也可能是 Base URL 配错返回了非 JSON 内容。修复检查 Base URL 是否为https://taotoken.net/api加大超时时间重试。如果持续出现换个模型试试。OAuth 相关报错Claude Code 接入报错原文OAuth token expired or invalid原因Claude Code 的鉴权过期。如果你是通过统一通道接入检查配置里的 Key 是否还有效。修复重新生成 Key更新配置。Claude Code 的接入配置参考前面给的链接确认 Base URL、Key、Model ID 三件套都填对。游标读取异常DPI-1067报错原文oracledb.exceptions.InterfaceError: DPI-1067: the cursor has already been closed原因游标被重复迭代或者提前关闭。比如你先list(cursor_var)消费了一遍又for row in cursor_var再消费一遍第二次就报这个。修复游标只能消费一次。需要多次使用就先转成 listrows list(cursor_var) for row in rows: print(row) for row in rows: print(row) # 这样没问题游标读取异常结果为空但没报错现象for row in cursor_var一行都不输出也不报错。原因存储过程里的OPEN没执行到或者WHERE条件过滤掉了所有行。也可能是游标变量声明了但没传进过程。修复先在 SQL Developer 里直接跑存储过程确认有数据返回。再检查 Python 侧的绑定变量是否传对。连接池耗尽现象程序卡在pool.acquire()不返回。原因连接没归还池的max太小或者有连接泄漏。修复确保每次pool.acquire()之后都有connect.close()。用try/finally保证归还。临时加大max看是否缓解。connect pool.acquire() try: # 你的操作 pass finally: connect.close()字段名大小写不一致现象columns里是大写但你代码里按小写取取不到。原因Oracle 默认大写标识符。修复统一用大写或者建表/查询时用双引号指定大小写。映射时用dict(zip(columns, row))按实际字段名取。row_dict dict(zip(columns, row)) print(row_dict[COL_A])这些报错覆盖了大部分场景。遇到没列出来的把报错原文贴到模型对话里让它帮你分析。模型对话入口前面给过这里再放一次方便你复制https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 。排查的时候有个原则先确认是模型通道的问题还是数据库的问题。模型通道的报错通常是 401、超时、JSON 解析失败数据库的报错通常是 ORA-、DPI- 开头。分开定位效率高很多。6. 统一 Key 通道下的接入与验证入口走到这里整条链路应该已经跑通了。回顾一下关键动作连接池配置用oracledb.create_pool()游标变量用cursor.var(oracledb.CURSOR)声明存储过程用匿名块调用结果直接迭代游标变量。字段映射靠cursor_var.description异常捕获用oracledb.DatabaseError。如果你还需要把这套流程固化到项目里建议把连接池做成单例避免每次调用都重建。Key 和连接串走环境变量不要硬编码。存储过程调用封装成函数入参和出参明确。模型侧的统一通道Key 管理在控制台接入文档在文档页。需要新建 Key 就去 API Keys 页面。长期编码场景用 Coding Plan快速问报错用模型对话。这几个入口按你的实际需求选不用全用。最后给一个实用技巧把存储过程的调用和模型排查串起来。写一个脚本捕获到oracledb.DatabaseError时自动把报错和 SQL 片段发给模型让它返回可能的原因。这样排查效率会高很多。模型调用走统一通道Base URL 和 Key 配一次就行。如果你在接入过程中卡住优先检查三件套Base URL 是不是https://taotoken.net/apiKey 是不是有效Model ID 是不是可用。这三个对了模型通道就通。数据库侧检查连接串、绑定变量类型、存储过程签名。两边分开查问题很快能定位。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表