【问题标题】:cx_Oracle Setting large value for CLOB var fails with cx_Oracle.DatabaseError: ORA-03146: Invalid buffer length for TTC fieldcx_Oracle 为 CLOB var 设置大值失败并出现 cx_Oracle.DatabaseError: ORA-03146: TTC 字段的缓冲区长度无效
【发布时间】:2017-11-14 01:29:43
【问题描述】:

我正在尝试将一个非常大的值 (>536MB) 设置到 cx_oracle CLOB 变量中,但它似乎在 512MB 标记附近失败。

conn = cx_Oracle.connect('foo/bar@baz')
cur = conn.cursor()
clob = cur.var(cx_Oracle.CLOB)

# The following is successful
clob.setvalue(0, 'A' * 1024 * 1024 * 511)

# The following fails
clob.setvalue(0, 'A' * 1024 * 1024 * 512)

第二个命令(或任何大于此的值)失败:

cx_Oracle.DatabaseError: ORA-03146: Invalid buffer length for TTC field

在对第二个命令进行了 3 次调用后,会话本身似乎已断开连接(而第一个命令不会发生这种情况)

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
cx_Oracle.OperationalError: ORA-03135: connection lost contact
Process ID: 17567
Session ID: 137 Serial number: 9226

我已经在两个环境中进行了测试,结果相同:本地环境中的 Python 连接到本地 Oracle;和 python 连接到不同的远程 Oracle 环境的远程 unix 环境。它们都在完全相同的1024 * 1024 * 512 行失败,这让我相信问题可能出在 cx_Oracle 上。

数据库版本:Oracle 12CR1

Oracle 客户端版本:12.1.0.2.0

cx_Oracle 版本:在 5.1.3 和 6.0.3 上测试,结果相同

Python 版本:2.7.13

一切都是 64 位的。


我的用例是我需要调用 PLSQL 过程将 CLOB 插入数据库。我们的项目遵循特定的 ERP 规则集,我们不允许直接进行 DML。我有类似于以下的代码:

data = get_user_provided_data()

conn = cx_Oracle.connect('foo/bar@baz')
cur = conn.cursor()
clob = cur.var(cx_Oracle.CLOB)
clob.setvalue(0, data)

cur.callproc('xxfoo_bar_pkg.insert_one', 
               keywordParameters={
                   'p_data_i': clob, 
               })

【问题讨论】:

  • 你可能在这里遇到了一个错误,因为数据库连接不应该直接死掉,即ORA-03135: connection lost contact。您能否也确认您的 Oracle 客户端版本(由 Python 驱动程序加载的库。
  • 这将在github.com/oracle/python-cx_Oracle/issues/112 中跟进。一般来说,如果您通过 SQL 插入 LOB 列,请避免使用临时 LOB,以免使用临时表空间。他们也没有这个问题。
  • @gvenzl 我使用的是 12.1.0.2.0 Oracle 客户端版本
  • @ChristopherJones 我已经更新了问题的底部以包含我正在使用的代码示例。我们正在使用 PLSQL 过程来执行实际的插入(我们不允许从 python 直接创建 DML)。调用cur.callproc 时有没有办法避免临时 LOB?

标签: python oracle cx-oracle


【解决方案1】:

感谢 Anthony Tuininga 在github repo 的回复。

这显然是一个错误,但以下是一个有效的解决方法,其中涉及更改 PLSQL 以使用 CLOB 的 OUT 参数,并使用 Oracle 的 empty_clob 实例化该值。这会将LOB locator 返回给python,这有点像指向LOB 值的指针。您可以写入 lob 定位器,然后进行提交,这会将值写入数据库。我不清楚这对网络的影响,但性能似乎类似于

DDL:

CREATE TABLE xxtest_cx_oracle (
    data CLOB
);


CREATE OR REPLACE PACKAGE xxtest_cx_oracle_pkg
IS
    -- Normal way; limited to ~ 511MB
    PROCEDURE insert_one(
        p_data_i IN CLOB
    );

    -- Workaround; supports > 511MB
    PROCEDURE insert_one_workaround(
        p_data_o OUT CLOB
    );
END;

CREATE OR REPLACE PACKAGE BODY xxtest_cx_oracle_pkg
IS 
    -- Normal way; limited to ~ 511MB
    PROCEDURE insert_one(
        p_data_i IN CLOB
    )
    IS
    BEGIN
        INSERT INTO xxtest_cx_oracle (
            data
        ) VALUES (
            p_data_i
        );
    END;

    -- Workaround; supports > 511MB
    PROCEDURE insert_one_workaround(
        p_data_o OUT CLOB
    )
    IS
    BEGIN
        INSERT INTO xxtest_cx_oracle (
            data
        ) VALUES (
            empty_clob()
        ) RETURNING data INTO p_data_o;
    END;
END;

Python:

import cx_Oracle
conn = cx_Oracle.connect('FOO/bar@BAZ')
cur = conn.cursor()

# Old way, using normal IN param; only supports ~511MB
data = cur.var(cx_Oracle.CLOB)
data.setvalue(0, 'A' * 1024 * 1024 * 511)
cur.callproc('xxtest_cx_oracle_pkg.insert_one', (data,))
conn.commit()

# New way using empty_clob and OUT param; supports > 511MB
data = cur.var(cx_Oracle.CLOB)
cur.callproc('xxtest_cx_oracle_pkg.insert_one_workaround', (data))
lob = data.getvalue()
lob.write('A' * 1024 * 1024 * 512)
conn.commit()

我注意到的一件事是尝试关闭 conn 会失败:

>>> cursor.close()
>>> conn.close()
Traceback (most recent call last):
   File "<stdin>", line 1, in <module>
cx_Oracle.DatabaseError: DPI-1054: connection cannot be closed when open statements or LOBs exist

但您可以只使用del lob 或让conn 超出范围来解决此问题。

【讨论】:

    猜你喜欢
    • 2019-02-03
    • 2014-12-31
    • 2021-10-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-05
    • 1970-01-01
    • 2010-09-06
    相关资源
    最近更新 更多