- 环境准备:安装必要的 Python 库。
- 数据库连接:使用
cx_Oracle库建立与 Oracle 数据库的连接。 - 创建存储过程:为了演示,我们先在 Oracle 数据库中创建一个简单的存储过程。
- 调用存储过程:使用 Python 的
callproc()函数来执行存储过程。 - 处理不同类型的参数:演示如何处理输入参数、输出参数和输入/输出参数。
- 处理游标(Ref Cursor):这是从存储过程返回结果集最标准、最强大的方式。
- 最佳实践和错误处理:编写健壮、可维护的代码。
环境准备
你需要安装 cx_Oracle 库,它提供了 Python 与 Oracle 数据库的接口。

pip install cx_Oracle
重要提示:cx_Oracle 需要 Oracle 客户端库,如果你的机器上没有安装 Oracle 客户端(如 Instant Client),你需要先下载并配置它。
- Oracle Instant Client:这是一个轻量级的客户端,
cx_Oracle需要它来与数据库通信。- 下载地址:Oracle Instant Client Downloads
- 下载适合你操作系统的版本(如 Windows x64, Linux x64 等)。
- 解压下载的文件,并将解压后的
bin目录添加到系统的PATH环境变量中,这是让 Python 能找到 Oracle 库的关键一步。
数据库连接
调用存储过程的第一步是建立数据库连接,你需要提供用户名、密码和连接字符串(DSN)。
import cx_Oracle
# --- 连接信息 ---
# 替换为你的实际数据库信息
username = "your_username"
password = "your_password"
dsn = "hostname:port/service_name" # "localhost:1521/XE"
# --- 建立连接 ---
try:
# 创建连接对象
connection = cx_Oracle.connect(user=username, password=password, dsn=dsn)
# 创建游标对象,用于执行 SQL 语句
cursor = connection.cursor()
print("成功连接到 Oracle 数据库!")
except cx_Oracle.DatabaseError as e:
error, = e.args
print(f"数据库连接错误: {error.code} - {error.message}")
# 在实际应用中,你可能需要在这里重新抛出异常或进行其他错误处理
exit()
# ... 在这里执行你的操作 ...
# --- 操作完成后关闭连接 ---
# 使用 'with' 语句可以更优雅地管理连接和游标
cursor.close()
connection.close()
创建示例存储过程
为了演示,我们在 Oracle 数据库中创建一个简单的存储过程,这个存储过程接收一个员工的 ID,并返回该员工的姓名和薪水。
-- 在 SQL*Plus, SQL Developer 或其他 Oracle 客户端中执行
CREATE OR REPLACE PROCEDURE get_employee_details (
p_emp_id IN NUMBER,
p_emp_name OUT VARCHAR2,
p_salary OUT NUMBER
)
IS
BEGIN
SELECT first_name, salary
INTO p_emp_name, p_salary
FROM employees
WHERE employee_id = p_emp_id;
-- 如果找不到员工,则抛出异常
IF SQL%NOTFOUND THEN
RAISE_APPLICATION_ERROR(-20001, '员工未找到');
END IF;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE_APPLICATION_ERROR(-20001, '员工未找到');
END get_employee_details;
/
调用存储过程(基本示例)
我们用 Python 来调用上面创建的 get_employee_details 存储过程,这个存储过程有一个输入参数和两个输出参数。

cx_Oracle 使用 cursor.callproc(procname, [arg1, arg2, ...]) 来调用存储过程,输出参数的值会通过传入的变量列表返回。
import cx_Oracle
# --- 连接信息 (假设已经连接成功) ---
username = "your_username"
password = "your_password"
dsn = "hostname:port/service_name"
try:
connection = cx_Oracle.connect(user=username, password=password, dsn=dsn)
cursor = connection.cursor()
# 要查询的员工ID
employee_id_to_find = 101
# 准备输出参数的变量
# 这些变量需要在调用前初始化,特别是对于字符串,最好初始化为 None
emp_name = None
emp_salary = 0
# 调用存储过程
# callproc 会返回一个元组,包含所有输出参数的值
# 我们传入的变量列表中的变量也会被修改
print(f"正在查询员工 ID: {employee_id_to_find}...")
cursor.callproc("get_employee_details",
[employee_id_to_find, emp_name, emp_salary])
# 从传入的变量列表中获取结果
print(f"查询成功!")
print(f"员工姓名: {emp_name}")
print(f"员工薪水: {emp_salary}")
except cx_Oracle.DatabaseError as e:
error, = e.args
print(f"调用存储过程时发生错误: {error.code} - {error.message}")
finally:
# 确保游标和连接被关闭
if 'cursor' in locals():
cursor.close()
if 'connection' in locals():
connection.close()
print("数据库连接已关闭。")
处理不同类型的参数
cx_Oracle 会自动处理大部分数据类型的转换,对于 IN 参数,直接传入值即可,对于 OUT 和 IN OUT 参数,你需要传入一个变量,该变量在调用后会被赋值。
IN参数:直接传递值。OUT参数:传递一个变量(如None或0),存储过程会修改它。IN OUT参数:传递一个变量,存储过程会读取它的初始值,并修改它。
示例:一个包含 IN, OUT, IN OUT 参数的存储过程
CREATE OR REPLACE PROCEDURE process_value (
p_in_val IN NUMBER,
p_in_out_val IN OUT NUMBER,
p_out_val OUT VARCHAR2
)
IS
BEGIN
-- IN OUT 参数:增加 10
p_in_out_val := p_in_out_val + 10;
-- OUT 参数:根据 IN 参数生成字符串
p_out_val := '输入值是: ' || TO_CHAR(p_in_val);
END;
/
Python 调用

import cx_Oracle
# ... (连接代码同上) ...
try:
cursor = connection.cursor()
in_val = 5
in_out_val = 100 # 初始值
out_val = None # 初始值
print(f"调用前: in_val={in_val}, in_out_val={in_out_val}, out_val={out_val}")
cursor.callproc("process_value", [in_val, in_out_val, out_val])
print(f"调用后: in_val={in_val}, in_out_val={in_out_val}, out_val={out_val}")
except cx_Oracle.DatabaseError as e:
# ... (错误处理同上) ...
pass
finally:
# ... (关闭连接同上) ...
pass
输出:
调用前: in_val=5, in_out_val=100, out_val=None
调用后: in_val=5, in_out_val=110, out_val=输入值是: 5
可以看到,in_val 没有改变,in_out_val 从 100 变成了 110,out_val 被赋予了新值。
处理游标(Ref Cursor) - 返回结果集
当存储过程需要返回一个多行的结果集时,最佳实践是使用 REF CURSOR(游标),这在 Oracle 中是标准做法。
创建一个 REF CURSOR 类型
-- 在包中定义类型是最佳实践
CREATE OR REPLACE PACKAGE emp_data_pkg AS
TYPE emp_cursor IS REF CURSOR;
END emp_data_pkg;
/
创建一个返回 REF CURSOR 的存储过程
CREATE OR REPLACE PROCEDURE get_employees_by_dept (
p_dept_id IN NUMBER,
p_emp_cursor OUT emp_data_pkg.emp_cursor
)
IS
BEGIN
OPEN p_emp_cursor FOR
SELECT employee_id, first_name, last_name, salary
FROM employees
WHERE department_id = p_dept_id
ORDER BY last_name;
END get_employees_by_dept;
/
在 Python 中调用并处理游标
在 Python 中,REF CURSOR 会被当作一个正常的游标对象返回。
import cx_Oracle
# ... (连接代码同上) ...
try:
cursor = connection.cursor()
department_id = 50 # 假设部门 50 存在
# 调用存储过程
# 注意:我们只传递了一个输入参数,输出参数 (p_emp_cursor) 会由 callproc 返回
result_cursor = cursor.callproc("get_employees_by_dept", [department_id])
# callproc 返回的第二个元素是 REF CURSOR 对象
ref_cursor = result_cursor[1]
print(f"部门 {department_id} 的员工列表:")
print("---------------------------------")
# 像操作普通游标一样遍历结果
for row in ref_cursor:
print(f"ID: {row[0]}, 姓名: {row[1]} {row[2]}, 薪水: {row[3]}")
# 关闭 REF CURSOR
ref_cursor.close()
except cx_Oracle.DatabaseError as e:
# ... (错误处理同上) ...
pass
finally:
if 'cursor' in locals():
cursor.close()
if 'connection' in locals():
connection.close()
print("\n数据库连接已关闭。")
最佳实践和错误处理
- 使用
with语句:cx_Oracle的Connection和Cursor对象都支持上下文管理器协议(with语句),可以自动关闭资源,即使发生异常。 - 参数化查询:虽然存储过程本身是预编译的,但在调用时传递参数也要注意,避免 SQL 注入风险(虽然风险比直接 SQL 小,但良好习惯是必须的)。
- 显式关闭游标:特别是
REF CURSOR,用完后一定要显式关闭,以释放数据库资源。 - 批量操作:如果需要调用存储过程处理大量数据,考虑使用
cursor.arraysize和cursor.executemany(如果存储过程支持批量输入)来提高性能。
使用 with 语句的改进版
import cx_Oracle
# --- 连接信息 ---
username = "your_username"
password = "your_password"
dsn = "hostname:port/service_name"
try:
# with 语句会自动处理连接的关闭
with cx_Oracle.connect(user=username, password=password, dsn=dsn) as connection:
# with 语句会自动处理游标的关闭
with connection.cursor() as cursor:
employee_id_to_find = 101
emp_name = None
emp_salary = 0
cursor.callproc("get_employee_details",
[employee_id_to_find, emp_name, emp_salary])
print(f"员工姓名: {emp_name}")
print(f"员工薪水: {emp_salary}")
except cx_Oracle.DatabaseError as e:
error, = e.args
print(f"发生数据库错误: {error.code} - {error.message}")
这个版本更简洁、更安全,是推荐的写法。
| 任务 | Python 代码 | 说明 |
|---|---|---|
| 安装库 | pip install cx_Oracle |
需要 Oracle Instant Client 支持。 |
| 建立连接 | cx_Oracle.connect(user, password, dsn) |
获取 Connection 对象。 |
| 创建游标 | connection.cursor() |
获取 Cursor 对象,用于执行。 |
| 调用无返回值过程 | cursor.callproc("proc_name", [arg1, ...]) |
直接调用。 |
| 调用带 OUT 参数过程 | cursor.callproc("proc_name", [in_val, out_var]) |
out_var 在调用后会被赋值。 |
| 调用返回游标过程 | result = cursor.callproc("proc_name", [in_val]); ref_cursor = result[1] |
ref_cursor 是一个可迭代的游标对象,需手动关闭。 |
| 关闭资源 | cursor.close(); connection.close() |
或使用 with 语句自动管理。 |
希望这份详细的指南能帮助你顺利地在 Python 中调用 Oracle 存储过程!
