ARTICLE DETAIL

资讯详情

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

python3.4连接oracle数据库:cx_Oracle 环境配置与连通性验证

python3.4连接oracle数据库:cx_Oracle 环境配置与连通性验证 1. Python 3.4 连 Oracle 踩坑现场cx_Oracle 版本对不上到底报什么错如果你手上有一台老机器跑着 Python 3.4 64 位数据库那边是 Oracle 11g 64 位任务就是写个脚本把某张表的前几条数据捞出来看看链路通不通——那你大概率会撞上 cx_Oracle 的版本地狱。这个组合不算新但恰恰因为年代久网上能搜到的资料要么是 Python 2.7 的要么是 cx_Oracle 8.x 配 Oracle 19c 的直接照抄必然翻车。先说清楚这套东西是什么、能干什么、适合谁。cx_Oracle 是 Python 访问 Oracle 数据库的官方推荐驱动它本身是 Python 扩展模块但底层并不自己实现 Oracle 网络协议而是通过调用 Oracle 客户端里的 OCIOracle Call Interface动态库来完成通信。这意味着两件事第一你的机器上必须有一套 Oracle 客户端完整客户端或 Instant Client 都行第二cx_Oracle 的版本、Python 的版本、Oracle 客户端的版本、数据库的版本这四者之间存在硬性的匹配关系错一个就连不上。适合读这篇的人很明确维护遗留系统的运维、需要从老 Oracle 库导数据做分析的数据同学、以及被派去给老项目写个临时脚本的开发者。Python 3.4 是 2014 年发布的早已停止维护但生产环境里就是有这种机器你没法说换就换。所以这篇不讲怎么升级 Python只讲在现有约束下怎么把链路打通。我先把最容易出问题的点摆出来。cx_Oracle 5.2.1 是最后一个提供 Windows 预编译 exe 安装包、并且明确支持 Python 3.4 的版本之一。它的安装包命名规则是cx_Oracle-驱动版本-Oracle客户端版本.win-amd64-pyPython版本.exe比如cx_Oracle-5.2.1-11g.win-amd64-py3.4.exe。这个名字里每个字段都是约束5.2.1 是驱动版本11g 表示它编译时链接的是 11g 的 OCIwin-amd64 是 64 位 Windowspy3.4 是 Python 3.4。四个字段必须和你本机环境完全一致否则要么装不上要么装上了 import 就报DLL load failed。很多人卡在第一步就是因为下载了cx_Oracle-5.2.1-11g.win-amd64-py3.4.exe却发现装完 import 报错。原因通常不是安装包错了而是 Oracle 客户端没装或者装了但 oci.dll 不在 Python 能找到的路径里。cx_Oracle 在 import 时会去几个固定位置找 oci.dll系统 PATH、Oracle 客户端安装目录、以及 Python 的 site-packages 目录。最省事的做法就是把 oci.dll 直接拷到Python34\Lib\site-packages下面这样不用配环境变量也能找到。还有一个隐蔽的坑Oracle 客户端必须是 64 位的。如果你机器上装的是 32 位 Oracle 客户端而 Python 是 64 位那 oci.dll 是 32 位的64 位 Python 进程根本加载不了报错信息还是那句含糊的DLL load failed: %1 is not a valid Win32 application。这个报错里的 Win32 application 会误导人以为要装 32 位 Python其实恰恰相反是客户端位数不对。判断方法很简单看 Oracle 客户端安装目录或者用file命令、看 oci.dll 属性里的位数。数据库版本 11g 和客户端版本 11g 是对应的这个组合没问题。cx_Oracle 5.2.1 连 11g 数据库是官方支持的。连接串的写法在这个版本里也比较传统用户名/密码主机:端口/服务名或者用户名/密码主机/服务名都能用excerpt 里用的是LS/LS192.168.1.234/orcl省略了端口默认走 1521服务名是 orcl。这种写法在 11g 上没问题但要注意服务名和 SID 的区别如果对方给的是 SID 而不是服务名得用主机:端口:SID的格式冒号不是斜杠。把环境这一层理清楚之后后面的配置和验证就是按部就班的事了。下一节先讲怎么把 TaoToken 这类模型服务接进来辅助排查——毕竟老环境里报错信息不直观有个能对话的模型帮你解读报错会快很多。2. 用 TaoToken 辅助排查 cx_Oracle 环境问题API Key 与接入准备老环境排查最痛苦的地方在于报错信息太短。DLL load failed就六个单词不告诉你缺哪个 dll、路径对不对、位数匹配不匹配。这时候如果有个能理解上下文的模型帮你分析效率会高不少。TaoToken 是一个模型 API 聚合服务你可以把它理解成一个统一的入口用同一套 API Key 和 Base URL 去调用不同的模型不用为每个模型单独注册和配环境。对于排查 cx_Oracle 这种偏门问题它的价值在于你可以把报错原文、你的环境信息、你试过的操作一起丢给模型让它帮你缩小范围。先说清楚接入需要什么。你需要三样东西Base URL、API Key、Model ID。Base URL 是https://taotoken.net/api注意这个地址不带任何查询参数是纯 API 端点。API Key 需要你去控制台创建地址是https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content登录后在 API Keys 页面新建一个复制出来保存好它只显示一次。Model ID 取决于你想用哪个模型在模型列表里能看到比如常见的对话模型有对应的 ID 字符串。这里要强调一个原则TaoToken 是模型服务入口不是数据库中间件也不是代理工具。它不碰你的 Oracle 连接不参与 cx_Oracle 的通信它只负责在你遇到报错时提供对话式的排查建议。你的数据库连接始终是 Python 进程直连 Oracle 客户端再到数据库这条链路和 TaoToken 无关。把这两件事分清楚就不会有用 TaoToken 连数据库这种误解。具体怎么用最直接的方式是打开模型对话页面https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content在网页里直接和模型对话。你可以这样提问把import cx_Oracle的完整报错贴进去再补一句我的环境是 Windows 64 位Python 3.4 64 位装了 Oracle 11g 64 位客户端oci.dll 已拷到 site-packages为什么还报 DLL load failed。模型会帮你列出可能的原因比如 PATH 里有另一个 32 位 oci.dll 抢先被加载、或者拷贝的 oci.dll 版本和客户端其他 dll 不配套。如果你更习惯在命令行里工作也可以用 API 的方式调用。下面是一个用 Python 请求 TaoToken 对话接口的最小示例注意这是独立的排查脚本和你连 Oracle 的脚本分开跑import json import urllib.request API_URL https://taotoken.net/api/v1/chat/completions API_KEY 你的_API_Key MODEL_ID 你的_Model_ID payload { model: MODEL_ID, messages: [ {role: user, content: import cx_Oracle 报 DLL load failed环境是 Win64 Python3.4 64位 Oracle 11g 64位客户端oci.dll 已放 site-packages怎么排查} ] } req urllib.request.Request( API_URL, datajson.dumps(payload).encode(utf-8), headers{ Content-Type: application/json, Authorization: Bearer API_KEY }, methodPOST ) with urllib.request.urlopen(req, timeout60) as resp: result json.loads(resp.read().decode(utf-8)) print(result[choices][0][message][content])这段代码用的是 Python 标准库 urllib因为 Python 3.4 环境里不一定有 requests用标准库最稳。注意choices这个字段如果返回结构里没有它说明请求本身失败了先检查 API Key 和 Model ID 是否正确。这个排查脚本和你的 Oracle 连接脚本是两回事别混在一起。对于需要长期做数据迁移、写多个脚本的场景可以考虑 Coding Plan地址是https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content它更适合持续性的编码任务。但如果你只是偶尔排查一下环境问题用模型对话页面就够了不用一上来就上套餐。接入文档在https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content里面有完整的接口说明和参数列表。建议先把文档扫一遍特别是错误码部分这样遇到 401 之类的响应你能自己判断是 Key 问题还是权限问题。3. 可复制的 cx_Oracle 环境配置安装包、oci.dll 与连接串写法这一节是整篇的核心所有配置都给你可复制的片段。按顺序来一步都别跳。第一步确认 Python 版本和位数。在命令行执行python --version python -c import struct; print(struct.calcsize(P) * 8)第一条输出Python 3.4.x第二条输出64说明是 64 位 Python。如果输出 32那后面所有 64 位的包都不能用得换 32 位的 cx_Oracle 和 32 位 Oracle 客户端。这一步必须先做不然后面全是白费。第二步确认 Oracle 客户端。你需要一套 64 位的 Oracle 11g 客户端。完整客户端安装后会有一个bin目录里面包含oci.dll。Instant Client 也可以解压后同样有oci.dll。确认位数的方法是看oci.dll的文件属性或者在命令行用 dumpbin 工具查看。更简单的办法是看安装目录名Oracle 通常会在目录里体现位数但最可靠的还是直接看 dll 本身。第三步下载 cx_Oracle 安装包。文件名必须是cx_Oracle-5.2.1-11g.win-amd64-py3.4.exe。这个包在 PyPI 的历史版本里能找到注意不要下成py2.7或者win32的。下载后双击安装它会自动装到你的 Python 3.4 的 site-packages 目录。安装完成后在命令行验证python -c import cx_Oracle; print(cx_Oracle.version)如果这一步就报DLL load failed别急继续第四步。如果输出了5.2.1说明驱动本身加载成功了但还不代表能连数据库因为 oci.dll 可能还没被找到。第四步处理 oci.dll。把 Oracle 客户端bin目录下的oci.dll拷贝到Python34\Lib\site-packages下面。注意是拷贝不是移动原目录的 oci.dll 要保留因为客户端其他组件可能还要用。拷完之后再跑一次上面的 import 验证。如果还是报错检查是不是 PATH 环境变量里有一个 32 位的 Oracle 目录排在前面导致系统优先加载了错误的 oci.dll。用where oci.dll命令可以看系统实际会加载哪个。这里有个细节cx_Oracle 5.2.1 在 import 时会尝试加载 oci.dll加载顺序大致是 site-packages、PATH、以及一些默认路径。把 oci.dll 放 site-packages 是最可控的做法因为它优先级高不会被 PATH 里的其他版本干扰。第五步写连接脚本。excerpt 里给的是一个最小示例我把它整理成更完整的版本加上异常处理和资源释放import cx_Oracle # 连接串格式用户名/密码主机:端口/服务名 # 端口省略时默认 1521 DSN LS/LS192.168.1.234/orcl conn None cursor None try: conn cx_Oracle.connect(DSN) print(连接成功数据库版本, conn.version) cursor conn.cursor() cursor.execute(select 1 from ck10_cfmx where rownum 10) row cursor.fetchone() if row: print(查询结果, row[0]) else: print(查询无结果) except cx_Oracle.DatabaseError as e: error, e.args print(数据库错误码, error.code) print(错误信息, error.message) finally: if cursor: cursor.close() if conn: conn.close()连接串的写法有几种变体用表格对照一下写法含义适用场景user/passhost/orcl省略端口默认 1521orcl 是服务名11g 常用服务名和 SID 同名时也能用user/passhost:1521/orcl显式指定端口和服务名端口非默认时user/passhost:1521:SID冒号分隔最后是 SID对方给的是 SID 而非服务名user/pass//host:1521/orcl标准 URL 形式更规范兼容性更好如果你不确定对方给的是服务名还是 SID优先试服务名写法连不上再换 SID 写法。报错通常是ORA-12514: TNS:listener does not currently know of service requested这个就是服务名不对换成 SID 试试。还有一个容易忽略的点密码里如果有特殊字符比如、/直接拼在连接串里会解析错误。这种情况下要用cx_Oracle.makedsn()配合connect()分开传参import cx_Oracle dsn cx_Oracle.makedsn(192.168.1.234, 1521, service_nameorcl) conn cx_Oracle.connect(LS, LS, dsn)这样密码和连接信息分开特殊字符不会干扰解析。Python 3.4 环境下makedsn的参数名是service_name不是sid用错了会报参数错误。配置到这一步环境层面就齐了。下一节做三步验证确认链路真的通了。4. 三步验证链路导入模块、建立连接、执行查询验证要分三步走每步单独确认不要跳步。跳步的后果是出了问题不知道是哪一层的事。第一步验证模块导入。这一步只确认 cx_Oracle 和 oci.dll 能加载不碰网络python -c import cx_Oracle; print(cx_Oracle version:, cx_Oracle.version); print(client version:, cx_Oracle.clientversion())预期输出类似cx_Oracle version: 5.2.1 client version: (11, 2, 0, 4, 0)clientversion()返回的是 Oracle 客户端的版本元组能打印出来说明 oci.dll 已经被成功加载并且可以调用。如果这一步报DLL load failed回到上一节检查 oci.dll 的位数和路径。如果报AttributeError: module has no attribute clientversion说明你装的 cx_Oracle 版本太老5.2.1 是有的检查是不是装成了别的版本。第二步验证建立连接。这一步只连不查import cx_Oracle try: conn cx_Oracle.connect(LS/LS192.168.1.234/orcl) print(连接成功) print(数据库版本, conn.version) conn.close() except cx_Oracle.DatabaseError as e: error, e.args print(连接失败错误码, error.code) print(错误信息, error.message)预期输出连接成功和数据库版本号。这一步常见的报错有几种对照着看ORA-12541: TNS:no listener说明主机通了但 1521 端口没有监听检查数据库服务是否启动、监听是否配置。ORA-12514: TNS:listener does not currently know of service requested说明监听在但服务名不对换成 SID 写法再试。ORA-01017: invalid username/password; logon denied说明账号密码错注意大小写Oracle 默认密码区分大小写。ORA-12170: TNS:Connect timeout occurred说明网络不通检查防火墙和主机地址。如果报的是cx_Oracle.DatabaseError但错误码是 0 或者没有错误码那可能是 oci.dll 加载了但版本不匹配回到第一步确认 clientversion 是否正常。第三步验证执行查询。这一步才真正跑 SQLimport cx_Oracle conn cx_Oracle.connect(LS/LS192.168.1.234/orcl) cursor conn.cursor() cursor.execute(select 1 from ck10_cfmx where rownum 10) rows cursor.fetchall() for row in rows: print(row) cursor.close() conn.close()预期输出是若干行数据。如果表里没数据fetchall()返回空列表不报错。如果表名写错报ORA-00942: table or view does not exist。如果权限不够报ORA-00942或者ORA-01031: insufficient privileges。三步都通过之后把三步合并成一个脚本加上异常处理和日志就可以作为日常使用的模板了。注意fetchall()在数据量大时会一次性把所有结果读进内存Python 3.4 环境下内存管理不如新版本如果表很大改用fetchmany(1000)分批读while True: rows cursor.fetchmany(1000) if not rows: break for row in rows: print(row)这样内存占用可控。另外查询完记得关 cursor 和 connection老环境里连接泄漏会导致数据库端会话堆积时间长了连不上。验证通过后如果你想把这段脚本纳入版本管理或者做更复杂的 ETL可以用 Coding Plan 来辅助生成和优化代码地址在https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content。但前提是三步验证已经过了链路本身没问题否则模型也帮不了你。5. 常见报错排查401、DLL load failed、ORA-12514 逐个拆这一节把实际会撞到的报错集中拆一遍。每个报错给出原文、原因、解决动作。报错一ImportError: DLL load failed: %1 is not a valid Win32 application这是最高频的。原文里 Win32 application 极具误导性很多人以为要装 32 位 Python其实恰恰相反。这个报错的意思是Python 进程尝试加载一个 dll但那个 dll 的位数和进程不匹配。64 位 Python 加载 32 位 dll 会报这个反过来也会。排查动作确认 Python 是 64 位用第 3 节的 struct 命令确认 Oracle 客户端是 64 位看 oci.dll 属性确认 cx_Oracle 安装包是win-amd64而不是win32。三者必须都是 64 位。如果 PATH 里有多个 Oracle 目录用where oci.dll看实际加载的是哪个把 32 位的那个从 PATH 里移除或者把正确的 oci.dll 拷到 site-packages 抢占优先级。报错二ImportError: DLL load failed: 找不到指定的模块注意这个和上一个不同上一个说不是有效的 Win32 应用这个说找不到指定的模块。后者通常是 oci.dll 本身找到了但它依赖的其他 dll 缺失。Oracle 客户端不是单个 dlloci.dll 依赖同目录下的一堆 dll比如oraociei11.dll、orannzsbb11.dll等。排查动作不要只拷 oci.dll把 Oracle 客户端bin目录下所有 dll 一起拷到 site-packages或者更规范的做法是把整个bin目录加到 PATH 最前面。如果只拷了 oci.dll它加载时会去找同目录的依赖找不到就报这个错。报错三cx_Oracle.DatabaseError: ORA-12514: TNS:listener does not currently know of service requested连接串里的服务名不对。Oracle 11g 里服务名和 SID 是两个概念监听器注册的是服务名但如果你用 SID 写法去连监听器可能不认识。排查动作先确认对方给的是服务名还是 SID。服务名写法是host:port/service_nameSID 写法是host:port:SID。如果服务名连不上换 SID 试。还可以在数据库服务器上用lsnrctl status看监听器注册了哪些服务名。报错四cx_Oracle.DatabaseError: ORA-01017: invalid username/password; logon denied账号密码错。Oracle 默认密码区分大小写LS和ls是两个不同的账号。排查动作确认用户名密码注意连接串里的斜杠和 符号。如果密码里有特殊字符用makedsn分开传参。另外确认账号没有被锁定ORA-28000: the account is locked是锁定需要 DBA 解锁。报错五TaoToken 接口返回 401这个和 Oracle 无关是模型服务侧的认证失败。原文通常是{error: {message: Invalid API key, type: invalid_request_error}}或者类似的。排查动作检查 API Key 是否复制完整有没有多余空格。检查请求头里Authorization是不是Bearer加 KeyBearer 后面有一个空格。检查 Key 是否被删除或过期去控制台https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content重新生成一个。如果用的是环境变量传 Key确认环境变量在当前 shell 里生效了。报错六cx_Oracle.DatabaseError: ORA-12170: TNS:Connect timeout occurred网络层不通。可能是防火墙挡了 1521 端口可能是主机地址写错可能是数据库服务器没启动。排查动作先用telnet 192.168.1.234 1521测端口通不通。如果不通找网络或 DBA 确认。如果通但 cx_Oracle 还是超时检查连接串里的主机地址是不是解析到了错误的 IP。报错七AttributeError: module object has no attribute connect这个通常是把脚本命名成了cx_Oracle.py导致 import 时导入的是自己的脚本而不是真正的模块。排查动作改脚本名不要和模块名重名。检查当前目录下有没有cx_Oracle.py或cx_Oracle.pyc。报错八UnicodeDecodeError或中文乱码Oracle 客户端和数据库的字符集不匹配。Python 3.4 默认用 UTF-8但 Oracle 客户端可能用 GBK 或别的。排查动作在连接后设置conn.outputtypehandler或者用cursor.var(cx_Oracle.STRING)显式指定编码。更彻底的办法是设置环境变量NLS_LANG比如NLS_LANGSIMPLIFIED CHINESE_CHINA.AL32UTF8让客户端用 UTF-8 通信。把这些报错对照着排查大部分环境问题都能定位。如果遇到没列出来的把完整报错贴到模型对话页面https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content让模型帮你分析比搜索引擎快。6. 把链路固化下来脚本模板与后续接入建议三步验证通过之后别急着把临时脚本删掉。把它整理成一个可复用的模板下次换台机器或者换个库改几个参数就能用。下面这个模板把连接、查询、异常处理、资源释放都包进去了Python 3.4 直接能跑# -*- coding: utf-8 -*- import cx_Oracle import sys # 配置区改这里就行 DB_USER LS DB_PASS LS DB_HOST 192.168.1.234 DB_PORT 1521 DB_SERVICE orcl SQL select 1 from ck10_cfmx where rownum 10 def get_connection(): dsn cx_Oracle.makedsn(DB_HOST, DB_PORT, service_nameDB_SERVICE) return cx_Oracle.connect(DB_USER, DB_PASS, dsn) def run_query(sql): conn None cursor None try: conn get_connection() cursor conn.cursor() cursor.execute(sql) columns [d[0] for d in cursor.description] print( | .join(columns)) while True: rows cursor.fetchmany(1000) if not rows: break for row in rows: print( | .join(str(v) for v in row)) except cx_Oracle.DatabaseError as e: error, e.args print(数据库错误 [%s]: %s % (error.code, error.message), filesys.stderr) raise finally: if cursor: cursor.close() if conn: conn.close() if __name__ __main__: run_query(SQL)这个模板有几个设计点值得说。用makedsn而不是拼字符串避免密码特殊字符问题。用fetchmany分批读避免大表撑爆内存。finally里关资源保证异常时也不泄漏连接。cursor.description拿列名输出带表头方便看结果。如果你后续要做更复杂的操作比如批量插入、调用存储过程、处理 LOB 字段可以在模型对话页面里让模型基于这个模板扩展。把模板贴进去说清楚要加什么功能模型会给你改好的版本。这比从零写快得多也不容易漏掉资源释放。对于需要长期维护多个 Oracle 脚本的场景Coding Plan 会更合适地址在https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content。它适合持续性的编码任务不用每次单独调用。但如果你只是偶尔写个脚本导数据用模型对话页面按次调用就够了。最后提醒几个老环境的实用技巧。第一Python 3.4 的print虽然是函数但filesys.stderr这种用法是支持的别被网上 Python 2 的资料带偏。第二cx_Oracle 5.2.1 的makedsn参数名是service_name不是sid用错了报TypeError。第三如果数据库端有连接数限制脚本跑完一定要关连接老环境里连接泄漏恢复起来很麻烦。第四把NLS_LANG环境变量设好能省掉很多中文乱码的排查时间。链路通了之后这套东西就可以嵌到你的数据流程里了。不管是定时导数据、做报表、还是给老系统做数据同步核心都是这个模板加你的业务 SQL。环境配置这一关过了后面就是写 SQL 的事了。
返回列表