2011-03-08 12 views
6

Oracleバックエンド(OCI8機能)を使用してPHP駆動型アプリケーションを保守します。このアプリケーションはOracle 10g XEで開発され、顧客が所有するバージョンに展開されます。CHARセマンティクスとORA-01461

アプリケーションでは、シングルバイトテキスト(ISO-8859-15)が処理されており、西ヨーロッパの版のOracle XEに対して開発中に問題は発生しませんでした。しかし、私は最近ユニバーサル版をインストールしました。非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:ソロ・プードゥ・エヴァンジャ・アン・ヴァール ロング・パラ・インサート・アンド・コラム LONG

4001文字列を挿入しようとしたときと同じエラーです。私が代わりに€€€xxx...を挿入する場合には発生しません、それは私がUTF-8として私のスクリプトを保存した場合に起こり、そのように接続していない:

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

[更新:私のテストには欠陥でした。 UTF-8を使用しても回避できないORA-01461

この問題をどのように解決できますか? NLS_CHARACTERSETデータベースパラメータは私が制御しているものではありません私のアプリケーションをUTF-8に切り替えると他の問題が発生する可能性があります(ほぼすべてのお客様がシングルバイトデータベースを持っています)。

答えて

9

VARCHAR2ではなくCLOBを使用しない限り、これはおそらく回避できないものです。

列で宣言すると、デフォルトでバイト長セマンティクスが使用されます。たとえば、VARCHAR2(100)は100バイトの記憶域を割り当てます。 ISO 8859-1のようなシングルバイト文字セットを使用している場合は、すべての文字に1バイトの記憶領域が必要です。したがって、これも100文字のスペースを割り当てます。しかし、UFT-8のようなマルチバイト文字セットを使用している場合は、各文字に1〜4バイトの記憶域が必要です。したがって、データに応じて、VARCHAR2(100)は25文字のデータしか格納できません(英語文字は一般に1バイト必要、ヨーロッパ文字は一般に2バイト、アジア文字は通常3バイト必要です)。

通常、ISO-8859-1データベースからUTF-8データベースに移動するときに示唆している文字長セマンティクスを使用するようにOracleに指示できます。 VARCHAR2(100 CHAR)列を宣言すると、100バイトまたは400バイトになるかどうかにかかわらず、Oracleは100文字のスペースを割り当てます。また、NLS_LENGTH_SEMANTICSパラメータをCHARに設定して、VARCHAR2(100)が100バイトではなく100文字の記憶域を割り当てるようにデフォルト(新しいDDL用)を変更することもできます。

残念ながら、(PL/SQLエンジンではなくSQLエンジンのコンテキストでの)Oracle VARCHAR2のサイズの制限は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データを格納する必要がある場合は、CLOBに移動する必要がある16000バイトを処理できるデータ型が必要です。

+0

私のUTF-8テスト・スクリプトでエラーが発生しました。これはORA-01461もトリガーします。 'VARCHAR2(4000 CHAR)'は4000バイト以上を保持できないようです。私は列のサイズを下げるか、 'CLOB'に切り替えるかについて調べます。 –

+0

「VARCHAR2列の表を作成するときは、VARCHAR2列の最大文字列長(バイトまたは文字数)を1〜4000バイト**に指定します。 - http://download.oracle.com/docs/cd/B19306_01/server.102/b14220/datatype.htm#sthref3780 –

+1

代替固定バイト文字セットを使用すると、問題を最小限に抑えることができます。たとえば、JA16SJISは日本語文字に2バイトを使用し、TH8TISASCIIは1バイトのタイ語文字セット –

関連する問題