CHAR семантика и ORA-01461 - PullRequest
7 голосов
/ 08 марта 2011

Я поддерживаю PHP-приложение с бэкэндом Oracle (функции OCI8).Приложение разработано с использованием Oracle 10g XE и развернуто на любой версии, которой владеет клиент.

Приложение обрабатывает однобайтовый текст (ISO-8859-15), и у меня никогда не было проблем при разработке с Западноевропейская редакция Oracle XE.Тем не менее, я недавно установил Universal , и у меня возникают проблемы при вставке больших строк с не-ASCII символами.Эта версия устанавливает NLS_CHARACTERSET = AL32UTF8;так как мое приложение использует WE8ISO8859P15 Oracle молча преобразует мои входные данные из ISO-8859-15 в UTF-8 (что нормально).Но кажется, что некоторые проверки размера идут не так: строка с 1500 символами (1500 байт в ISO-8889-15, 4500 байт в UTF-8), кажется, переполняет столбец 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: все равно длинно, долго, долго, долго, долго

Это та же ошибка, что и я, когда я пытаюсь вставить строку символа 4001.Этого не произойдет, если я вставлю xxx... вместо €€€ , и этого не произойдет, если я сохраню свой сценарий как UTF-8 и подключусь так:

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

[ Обновление: Мой тест был ошибочным.Использование UTF-8 не исключает ORA-01461]

Как я могу решить эту проблему?Параметр базы данных NLS_CHARACTERSET - это не то, чем я управляю , и переключение моего приложения на UTF-8 может вызвать другие проблемы (почти у всех наших клиентов есть однобайтовые базы данных).

1 Ответ

10 голосов
/ 08 марта 2011

Это, вероятно, не то, что вы можете обойти, если не хотите использовать 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.

...