2011-03-08 10 views
6

Prowadzę aplikację opartą na PHP z zapleczem Oracle (funkcje OCI8). Aplikacja została opracowana przy użyciu Oracle 10g XE i wdrożona w dowolnej wersji posiadanej przez klienta.Semantyka CHAR i ORA-01461

Aplikacja obsługuje tekst jednobajtowy (ISO-8859-15) i nigdy nie miałem problemu z opracowaniem wersji z Europy Zachodniej Oracle XE. Jednak ostatnio zainstalowałem Universal wydanie i mam problemy podczas wstawiania dużych ciągów znaków z znakami spoza ASCII. Ta wersja ustawia NLS_CHARACTERSET = AL32UTF8; ponieważ moja aplikacja używa WE8ISO8859P15 Oracle po cichu konwertuje moje dane wejściowe z ISO-8859-15 na UTF-8 (co jest w porządku). Wygląda jednak na to, że pewne sprawdzenia rozmiarów się nie udają: ciąg znaków o długości 1500 znaków () (1500 bajtów w ISO-8889-15, 4500 bajtów w UTF-8) wydaje się przepełniać kolumnę VARCHAR2(4000 CHAR).

Utworzyłem tę tabelę testową:

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) 
); 

Problemem może być powielana z tym kodem:

<?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'])); 
} 

... co wyzwala:

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

Jest to ten sam błąd, który pojawia się przy próbie wstawienia ciągu znaków 4001. To nie zdarza się jeśli wstawić xxx... zamiast €€€ i nie stanie, jeśli mogę zapisać skrypt jako UTF-8 i podłączyć w następujący sposób:

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

[Aktualizacja: Moja próba była błędna . Używanie UTF-8 nie omija ORA-01461]

Jak mogę zmienić ten problem? Parametr bazy danych NLS_CHARACTERSET nie jest czymś, co kontroluję , a przełączanie mojej aplikacji na UTF-8 prawdopodobnie spowoduje inne problemy (prawie wszyscy nasi klienci mają bazy danych z jednym bajtem).

Odpowiedz

9

Prawdopodobnie nie jest to coś, nad czym możesz pracować, chyba że chcesz użyć CLOB zamiast VARCHAR2.

W Oracle, po zadeklarowaniu kolumny, domyślnie używa się semantyki długości bajtów. Tak więc VARCHAR2 (100) na przykład przydziela 100 bajtów pamięci. Jeśli używasz jednobajtowego zestawu znaków, takiego jak ISO 8859-1, każdy znak wymaga 1 bajta pamięci, więc przydziela również miejsce na 100 znaków. Ale jeśli używasz wielobajtowego zestawu znaków, takiego jak UFT-8, każda postać może wymagać od 1 do 4 bajtów pamięci. Zależnie od danych, VARCHAR2 (100) może przechowywać tylko 25 znaków danych (angielskie znaki zazwyczaj wymagają 1 bajtu, europejskie znaki zazwyczaj wymagają 2 bajtów, a azjatyckie znaki zwykle wymagają 3 bajtów).

Możesz powiedzieć Oracle, aby używał semantyki długości znaków, co zwykle sugeruję przy przechodzeniu z bazy danych ISO-8859-1 do bazy danych UTF-8. Jeśli zadeklarujesz kolumnę VARCHAR2 (100 CHAR), Oracle przydzieli miejsce na 100 znaków, niezależnie od tego, czy będzie to 100 bajtów, czy 400 bajtów.Możesz też ustawić parametr NLS_LENGTH_SEMANTICS na CHAR, aby zmienić domyślny (dla nowego DDL), aby VARCHAR2 (100) przydzielił 100 znaków pamięci zamiast 100 bajtów.

Niestety dla Ciebie limit rozmiaru Oracle VARCHAR2 (w kontekście silnika SQL, a nie silnika PL/SQL) wynosi 4000 bajtów. Więc nawet jeśli zadeklarujesz kolumnę VARCHAR2 (4000 CHAR), nadal będziesz ograniczał się do wstawienia 4000 bajtów danych, które mogą mieć zaledwie 1000 znaków. Na przykład, w bazie danych przy użyciu zestawu znaków AL32UTF8 mogę zadeklarować kolumny VARCHAR2 (4000 znaków), ale wstawienie znaku, który wymaga 2 bajtów pamięci pokazuje, że tak naprawdę nie można wstawić 4000 znaków danych

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 

Jeśli potrzebujesz 4000 znaków danych UTF-8, potrzebujesz danych, które mogłyby obsłużyć 16000 bajtów, co wymagałoby przeniesienia do CLOB.

+0

Masz rację, wystąpił błąd w skrypcie testowym UTF-8: powoduje on również uruchomienie ORA-01461. Wydaje się, że 'VARCHAR2 (4000 CHAR)' nie może pomieścić więcej niż 4000 * bajtów *. Zbadam, czy obniżyć rozmiar kolumny, czy przełączyć na "CLOB". –

+0

Znalazłem odniesienie: "Gdy tworzysz tabelę z kolumną VARCHAR2, określasz maksymalną długość łańcucha (w bajtach lub znakach) od 1 do 4000 ** bajtów ** dla kolumny VARCHAR2." - http://download.oracle.com/docs/cd/B19306_01/server.102/b14220/datatype.htm#sthref3780 –

+1

Być może uda ci się zminimalizować problem, używając alternatywnego stałego zestawu znaków. Na przykład JA16SJIS używa dwóch bajtów dla znaków japońskich, a TH8TISASCII to jednobajtowy tajski zestaw znaków –