【问题标题】:CHAR semantics and ORA-01461CHAR 语义和 ORA-01461
【发布时间】:2011-07-10 23:06:12
【问题描述】:

我使用 Oracle 后端(OCI8 函数)维护一个 PHP 驱动的应用程序。该应用使用 Oracle 10g XE 开发并部署在客户拥有的任何版本上。

该应用程序处理单字节文本 (ISO-8859-15),在针对 Oracle XE 的西欧 版进行开发时,我从未遇到任何问题。但是,我最近安装了 Universal 版本,在插入带有非 ASCII 字符的大字符串时遇到了问题。此版本设置NLS_CHARACTERSET = AL32UTF8;因为我的应用程序使用WE8ISO8859P15 Oracle 默默地将我的输入数据从 ISO-8859-15 转换为 UTF-8(这很好)。但似乎某些大小检查出错了:包含 1500 个 字符的字符串(ISO-8889-15 中为 1500 字节,UTF-8 中为 4500 字节)似乎溢出了VARCHAR2(4000 CHAR) 列。

我已经创建了这个测试表:

CREATE TABLE FOO (
    FOO_ID NUMBER NOT NULL ENABLE,
    DATA_BYTE VARCHAR2(4000 BYTE),
    DATA_CHAR VARCHAR2(4000 CHAR),

    CONSTRAINT FOO_PK PRIMARY KEY (FOO_ID)
);

这个问题可以用这段代码重现:

<?php
$connection = oci_connect(DB_USER, DB_PASS, DB_CONN_STRING, 'WE8ISO8859P15');
if( !$connection ){
    $e = oci_error();
    die(htmlspecialchars($e['message']));
}

$id = 1;
$data = str_repeat('€', 1500);

$sql = 'INSERT INTO FOO (FOO_ID, DATA_CHAR) ' .
    'VALUES (:id, :data)';
$res = oci_parse($connection, $sql);
if(!$res){
    $e = oci_error();
    die(htmlspecialchars($e['message']));
}
if(!oci_bind_by_name($res, ':id', $id)){
    $e = oci_error();
    die(htmlspecialchars($e['message']));
}
if(!oci_bind_by_name($res, ':data', $data)){
    $e = oci_error();
    die(htmlspecialchars($e['message']));
}
if(!oci_execute($res, OCI_COMMIT_ON_SUCCESS)){
    $e = oci_error();
    die(htmlspecialchars($e['message']));
}

...触发:

警告:oci_execute(): ORA-01461: sólo puede enlazar un valor LONG para insertarlo en una columna 长

这与我尝试插入 4001 字符字符串时遇到的错误相同。如果我插入 xxx... 而不是 €€€ 则不会发生这种情况 如果我将脚本保存为 UTF-8 并像这样连接也不会发生这种情况:

<?php
$connection = oci_connect(DB_USER, DB_PASS, DB_CONN_STRING, 'AL32UTF8');

[更新:我的测试有缺陷。使用 UTF-8 并不能避免 ORA-01461]

如何解决这个问题? NLS_CHARACTERSET 数据库参数不是我控制的,将我的应用程序切换到 UTF-8 可能会导致其他问题(几乎我们所有的客户都有单字节数据库)。

【问题讨论】:

    标签: php oracle oci8


    【解决方案1】:

    这可能不是您可以解决的问题,除非您想使用 CLOB 而不是 VARCHAR2。

    在 Oracle 中,当你声明一个列时,默认是使用字节长度语义。例如,一个 VARCHAR2(100) 分配了 100 个字节的存储空间。如果您使用像 ISO 8859-1 这样的单字节字符集,每个字符都需要 1 个字节的存储空间,因此这也会为 100 个字符分配空间。但是,如果您使用的是像 UFT-8 这样的多字节字符集,则每个字符可能需要 1 到 4 个字节的存储空间。因此,根据数据的不同,一个 VARCHAR2(100) 可能只能存储 25 个字符的数据(英文字符通常需要 1 个字节,欧洲字符通常需要 2 个字节,亚洲字符通常需要 3 个字节)。

    您可以告诉 Oracle 使用字符长度语义,这通常是我从 ISO-8859-1 数据库迁移到 UTF-8 数据库时的建议。如果您声明列 VARCHAR2(100 CHAR),Oracle 将为 100 个字符分配空间,无论最终是 100 字节还是 400 字节。您还可以将 NLS_LENGTH_SEMANTICS 参数设置为 CHAR 以更改默认值(对于新 DDL),以便 VARCHAR2(100) 分配 100 个字符而不是 100 个字节的存储空间。

    不幸的是,Oracle VARCHAR2 的大小限制(在 SQL 引擎而不是 PL/SQL 引擎的上下文中)是 4000 字节。因此,即使您声明一个列 VARCHAR2(4000 CHAR),您仍将被限制为实际插入 4000 个字节的数据,这些数据可能少至 1000 个字符。例如,在使用 AL32UTF8 字符集的数据库中,我可以声明一列 VARCHAR2(4000 CHAR) 但插入需要 2 个字节存储的字符表明我无法真正插入 4000 个字符的数据

    SQL> create table foo (
      2    col1 varchar2(4000 char)
      3  );
    
    Table created.
    
    SQL> insert into foo values( rpad( 'abcde', 4000, unistr('\00f6') ) );
    
    1 row created.
    
    SQL> ed
    Wrote file afiedt.buf
    
      1* insert into foo values( rpad( 'abcde', 6000, unistr('\00f6') ) )
    SQL> /
    
    1 row created.
    
    SQL> select length(col1), lengthb(col1)
      2    from foo;
    
    LENGTH(COL1) LENGTHB(COL1)
    ------------ -------------
            2003          4000
            2003          4000
    

    如果您需要存储 4000 个字符的 UTF-8 数据,则需要一种可以处理 16000 个字节的数据类型,这需要转移到 CLOB。

    【讨论】:

    • 你说得对,我的 UTF-8 测试脚本中有一个错误:它也会触发 ORA-01461。 VARCHAR2(4000 CHAR) 似乎不能容纳超过 4000 个字节。我会研究是降低列大小还是切换到CLOB
    • 我找到了一些参考资料:“当您创建一个包含 VARCHAR2 列的表时,您为VARCHAR2 列。” - download.oracle.com/docs/cd/B19306_01/server.102/b14220/…
    • 您可以通过使用替代的固定字节字符集来最小化问题。例如 JA16SJIS 使用两个字节的日语字符,而 TH8TISASCII 是一个单字节的泰语字符集
    • @Gary - 唯一的问题是 Alvaro 使用 XE,这意味着仅有的两个字符集选项是 ISO 8859-1 和 UTF-8。通过正常安装,您是绝对正确的。当然,我不确定一旦安装了 XE 数据库,您是否真的可以更改它的字符集。 Oracle 不会鼓励这样做,但他们不支持任何 XE 安装...
    • @Gary Myers - 如果问题是使用JA16SJIS VARCHAR2 不能容纳超过 4,000 个字节(无论字符集如何),则最大容量将降低到 2,000 个字符。唯一的优点是我可以预测给定的字符串是否适合。这是重点还是我错过了什么?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-04-14
    • 1970-01-01
    • 2014-02-07
    • 1970-01-01
    • 1970-01-01
    • 2016-04-11
    • 1970-01-01
    相关资源
    最近更新 更多