Python 连接和操作 DB2 和 Oracle 数据库

使用 Python 就难免要应对到不同数据库连接的问题,Python 目前也没有 Java 使用 JDBC 瘦客户端驱动那么方便。本人在用 Python 连接 SQL Server 时经常还是会有些问题,此文只着力于如何用 Python 连接 DB2 和 Oracle 数据库。

从本文中我们将会学到

  1. Python 围绕着 ibm_db 来操作 DB2
  2. 以 Python DB-API 2.0 规范来操作 DB2
  3. cx_Oracle Python 库 + Oracle Instant Client 操作 Oracle
  4. 用 Oracle 的  SID 还是 Service Name 来连接数据库
  5. Python 中借助 JayDeBeApi 使用 JDBC 驱动来操作数据库(以 Oracle 为例)
  6. 从 JayDeBeApi 中我们了解到 JPype 有助于我们在 Python 中调用 Java 

Python 操作 DB2

连接 DB2 数据库要简单的多,只要安装 ibm-db 库

pip install ibm-db   # 或用是 ibm_db

假设数据库中创建有一个表  t1, 并有记录

立即查询

然后查询记录的代码如下

执行后输出的结果为

{'C1': 1, 0: 1, 'C2': 'aaa', 1: 'aaa'}
{'C1': 2, 0: 2, 'C2': 'bbb', 1: 'bbb'}

both 表示是结果行里既有列名也有索引号。

这和我们在 Python 中使用数据库的标准流程(conn->conn.cursor()->cursor.execute()->cursor.fetchXxx()) 不太一致,DB2 的操作以 ibm_db 模块为中心。ibm_db 的 conn 对象没什么有效的操作函数, 我们可以列出 ibm_db 的函数和属性

active
autocommit
bind_param
callproc
check_function_support
client_info
close
column_privileges
columns
commit
conn_error
conn_errormsg
conn_warn
connect
createdb
cursor_type
dropdb
exec_immediate
execute
execute_many

立即执行单条语句用 exec_immediate(), 如果要绑定参数来执行就得用到 prepare, bind_param 和 execute 函数

绑定参数查询

然后像上面那样处理结果

调用存储过程

创建一个示例存储过程

然后调用它

输出为

abc

返回值为 statement 和所有输入输出参数组成的列表,输入值我们并不用关心,所以用 *_ 去匹配它们。

调用存储过程时注意存储过程的名称,参数个数及类型的匹配,不然任何错误都是报

Exception: Describe Param Failed: [IBM][CLI Driver][DB2/NT64] SQL0440N No authorized routine named "TEST_PROC" of type "PROCEDURE" having compatible arguments was found. SQLSTATE=42884 SQLCODE=-440

根本无法明确是什么错误。

IBM DB2 的 Python 驱动是以二进制的行式提供的,而且非标准方式使用 DB2 数据库,几乎有方法的参数都是 (*args, **kwargs), 如何调用只好看 IBM 的 Application development in Python with ibm_db,或者也可以看源代码 https://github.com/ibmdb/python-ibmdb

标准方式使用 DB2

上面的非标准方式操作 DB2 很反常规,幸好这个驱动同时也提供了

ibm_db_dbi: Python driver for IBM DB2 and IBM Informix databases that complies to the DB-API 2.0 specification

输出为

(1, 'aaa')
(2, 'bbb')

最后别忘了用 conn.close() 关闭连接。

Python 操作 Oracle 数据库

Python 连接 Oracle 数据库就比较复杂一些了,除了安装  cx_Oracle 库

pip install cx-oracle

麻烦的事还在后头

现在试着用 cx_Oracle 来连接数据库

注意:cx_Oracle 连接时要用 service name, 而不是 SID, 下面将会介绍到如何得到 service name,以及通过 SID 构建 DSN 字符串。

错误来了

cx_Oracle.DatabaseError: DPI-1047: Cannot locate a 64-bit Oracle Client library: "dlopen(libclntsh.dylib, 1): image not found". See https://cx-oracle.readthedocs.io/en/latest/user_guide/installation.html for help

因为这不是一个瘦客户端,需要安装 Oracle 的本地库来支援该驱动,即 Oracle Instant Client。还可用预先安装了 Oracle Instant Client 的 Docker 镜像Dockerfile

下面以在 Mac OS 上安装 Oracle Instant Client 为例,可选择安装 Oracle Instant Client Downloads for macOS(Intel x86) 里的 Basic Light Package(ZIP)(大小 35M)。然后解压某个目录下,比如

unzip instantclient-basiclite-macos.x64-19.8.0.0.0dbru.zip -d ~/Developers

现在只要连接 Oracle 数据库前加上一行代码

因此完整的操作 Oracle 数据库的代码是

输出

('X',)

除了调用 cx_Oracle.init_oracle_client 来指定 Oracle Instant Client 的位置外,还能用环境变量 LD_LIBRARY_PATH 的方式

export LD_LIBRARY_PATH=/Users/yanbin/Developers/instantclient_19_8:$LD_LIBRARY_PATH

这样的话,就不需要 xc_Oracle.init_oracle_client() 这个函数调用了。

如何通过 SID 进行连接

前面提到 cx_Oracle 是用 service name 进行连接的,如查把 service name  换作 SID 是不能连接成功的,比如写成

报错

cx_Oracle.DatabaseError: ORA-12514: TNS:listener does not currently know of service requested in connect descriptor

这儿有两种方式

通过 SID 构造 DSN 字符串

此时的 dsn_str 是

(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.86.33)(PORT=1521))(CONNECT_DATA=(SID=ee)))

或者在 tnsnames.ora 中找到 service name, 也可用 SID 连接数据库后,通过 SQL 语句 select * from global_name 找到 service name。

JayDeBeApi 桥接 JDBC 到 Python

最后,有人尝试用 JayDeBeApi 在 Python 中使用 JDBC 数据库驱动来连接数据库,这样做似乎对 Oracle 来讲可以省去安装 Oracle Instant Client 软件包,但带来的负担更重了,还需要安装 Java 虚拟机,并且运行的时候还得启动 JVM,用 Python 一个很重要的原因就是要摆脱 Java。

比如操作 DB2

因为实际上扮演连接操作数据库的角色是 Java 的 JDBC, 所以 url 是一个 JDBC 连接字符串,JayDeBeApi 用 url 结合 driver_args 中的参数去调用 Java 的 Driver.getConnection(url, *driver_args) 方法。JayDeBeApi 所实现的是在 Java 与 Python 之间进行类型的转换,以调用相应的 JDBC 函数。

JayDeBeApi 依赖于 JVM,它借助于 JPype1 试图从 JAVA_HOME 或 PATH 环境变量中找到 java 执行程序启动 JVM。如果没有 JAVA_HOME 或 PATH 中找不到 java,则最后因系统而异采用不同的查找定位 JAVA_HOME 的办法,见 _jvmfinder.py,所以这会造成启动 JVM 非常的慢。

找到的 JVM 的路径可以通过 JPype1 代码打印出来

pip install jpype1

如果定位到 JAVA_HOME, 输出类似如下的路径

/Library/Java/JavaVirtualMachines/jdk1.8.0_281.jdk/Contents/Home/jre/lib/jli/libjli.dylib

否则报告

jpype._jvmfinder.JVMNotFoundException: No JVM shared library file (libjvm.so) found. Try setting up the JAVA_HOME environment variable properly.

基于 JayDeBeApi 的实现原理,它几乎可以应用于所有 JDBC 驱动支持的数据库。

参考:

  1. Python downloads and related resources
  2. ibm_db API documentation

本文链接 https://yanbin.blog/python-connect-db2-oracle-databases/, 来自 隔叶黄莺 Yanbin Blog

[版权声明] Creative Commons License 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。

Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments