cx_Oracle使用cx_Oracle.Database错误设置CLOB var的大值失败:ORA-03146:TTC字段的缓冲区长度无效

时间:2017-11-14 01:29:43

标签: python oracle cx-oracle

我试图将一个非常大的值(> 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

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

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;和远程unix环境中的python连接到不同的远程Oracle环境。它们都在完全相同的1024 * 1024 * 512行失败,这使我相信问题可能出在cx_Oracle上。

数据库版本:Oracle 12CR1

Oracle客户端版本:12.1.0.2.0

cx_Oracle version:在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, 
               })

1 个答案:

答案 0 :(得分:0)

感谢Anthony Tuininga对github repo的回应。

这显然是一个错误,但以下是一个有效的解决方法,它涉及更改PLSQL以使用OUT param作为CLOB,并使用Oracle的empty_clob来实例化该值。这会将LOB locator返回给python,这有点像指向LOB值的指针。您可以写入lob定位器,然后是commit,它会将值写入DB。我不清楚网络对此的影响,但性能似乎与

类似

的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超出范围来解决此问题。