我使用Oracle后端(OCI8函数)维护一个PHP驱动的应用程序.该应用程序是使用Oracle 10g XE开发的,并部署在客户拥有的任何版本上.
该应用程序处理单字节文本(ISO-8859-15),我在开发针对西欧版Oracle XE 时从未遇到任何问题.但是,我最近安装了通用版,我在插入带有非ASCII字符的大字符串时遇到了问题.这个版本设置NLS_CHARACTERSET = AL32UTF8; 因为我的应用程序使用WE8ISO8859P15Oracle静默地将我的输入数据从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)
);
Run Code Online (Sandbox Code Playgroud)
使用此代码可以重现此问题:
<?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']));
}
Run Code Online (Sandbox Code Playgroud)
......触发:
警告:oci_execute():ORA-01461:sólopuedeenlazar un valor LONG para insertarlo en una columna LONG
这是我尝试插入4001字符串时得到的相同错误.如果我插入xxx...而不是,则不会发生€€€ 如果我将脚本保存为UTF-8并且连接如下,则不会发生这种情况:
<?php
$connection = oci_connect(DB_USER, DB_PASS, DB_CONN_STRING, 'AL32UTF8');
Run Code Online (Sandbox Code Playgroud)
[ 更新:我的测试存在缺陷.使用UTF-8无法避免ORA-01461]
我该如何覆盖这个问题?NLS_CHARACTERSET数据库参数不是我控制的并将我的应用程序切换到UTF-8可能会导致其他问题(几乎所有客户都有单字节数据库).
Jus*_*ave 10
除非您想使用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
Run Code Online (Sandbox Code Playgroud)
如果需要存储4000个字符的UTF-8数据,则需要一个可处理16000字节的数据类型,这样就必须转移到CLOB.
| 归档时间: |
|
| 查看次数: |
7391 次 |
| 最近记录: |