AL32UTF8/UTF8(Unicode)数据库字符集的使用
Why does utl_encode.base64_encode produce different results in PLSQL and SQL?(UTL_ENCODE is used when you have binary data that you want to transfer across the network. If you just stream this in it's
A.1) 我需要使用 Nchar,Nvarchar2 或者 Nclob么?
人们经常认为像 NCHAR,NVARCHAR2 或者 NCLOB(NLS_NCHAR_CHARACTERSET/国家字符集数据类型)等数据类型需要在具有 UNICODE 支持的 Oracle 中使用。
这是不正确的。
答: AL32UTF8 或者 UTF8 NLS_CHARACTERSET 数据库就可以,推荐AL32UTF8
Oracle 强烈建议使用 AL32UTF8 作为 NLS_CHARACTERSET 除非有应用层/供应商所带来的限制,例如低于版本 12 的 Oracle Applications。
如果有旧的 8i 或者更低的客户端使用了 UTF8 而不是 AL32UTF8 作为 NLS_CHARACTERSET,参见 Note 237593.1 Problems connecting to AL32UTF8 databases from older versions (8i and lower)。
NLS_NCHAR_CHARACTERSET("国家字符集")定义了 NCHAR,NVARCHAR2 和 NCLOB 列的编码并且在 9i 及以上版本已经是 Unicode了(参见 Note 276914.1 The National Character Set in Oracle 9i 10g and 11g)。
NLS_CHARACTERSET("字符集")定义了"普通的" CHAR,VARCHAR2,LONG 和 CLOB列编码,这些也可以被用于存放 Unicode。在这样的情况下需要 AL32UTF8 或者 UTF8 NLS_CHARACTERSET 数据库。
查看 NLS_CHARACTERSET 的值可以用如下查询:
SQL> select value from NLS_DATABASE_PARAMETERS where parameter='NLS_CHARACTERSET';
VALUE
----------------------------------------
AL32UTF8
当使用 AL32UTF8 作为 NLS_CHARACTERSET 时,所有"普通的" CHAR,VARCHAR2,LONG 和 CLOB 数据类型是"Unicode"的并且您可以存储世界上任何语言到 CHAR,VARCHAR2,LONG 和 CLOB 数据类型中。
您不可能将 AL16UTF16 作为 NLS_CHARACTERSET,AL16UTF16 只能被用于 NLS_NCHAR_CHARACTERSET,参见 Note:276914.1 The National Character Set in Oracle 9i 10g and 11g。
人们经常采取简单的使用 NCHAR,NVARCHAR2 或者 NCLOB 来将一个应用"Unicode"化并认为这样做比改变 NLS_CHARACTERSET 要减少很多工作,然而 N- 类型在(其他厂商)编程语言和应用层通常只有很少的支持。使用 N- 类型需要客户端应用/程序的明确支持。
这就是 Oracle 通常推荐不要使用 N-types 而是使用 AL32UTF8 (或者 UTF8) 的 NLS_CHARACTERSET 结合 CHAR,VARCHAR2,LONG 和 CLOB 数据类型的原因。
NVARCHAR2 datatype uses 3 bytes per characte
Why does utl_encode.base64_encode produce different results in PLSQL and SQL?
(UTL_ENCODE is used when you have binary data that you want to transfer across the network. If you just stream this in it's raw format some protocols may interpret the binary data as control characters or character combinations and may act on this. To get around this, the binary data is encoded into characters using UTL_ENCODE.)
For example, from PL/SQL:
CREATE OR REPLACE FUNCTION TestEncodeBase64 (email nvarchar2) RETURN nvarchar2 IS
result nvarchar2(256);
BEGIN
SELECT UTL_RAW.CAST_TO_VARCHAR2(UTL_ENCODE.BASE64_ENCODE(UTL_RAW.CAST_TO_RAW(email)))
INTO result
FROM dual;
RETURN result;
END TestEncodeBase64;
/
SQL> select TestEncodeBase64 ('TEST') from dual;
TESTENCODEBASE64('TEST')
-----------------------------------------
AFQARQBTAFQ=
From SQL:
SELECT UTL_RAW.CAST_TO_VARCHAR2(UTL_ENCODE.BASE64_ENCODE(UTL_RAW.CAST_TO_RAW('TEST'))) as test
FROM dual;
TEST
-----------------
VEVTVA==
SOLUTION
The reason there is a difference is because the PLSQL user defined function uses NVARCHAR2 whereas SQL defaults to VARCHAR2. The NVARCHAR2 datatype uses 3 bytes per character so therefore 'TEST' is 12 bytes in length. The VARCHAR2 datatype uses 2 bytes per character so 'TEST' is 8 bytes in length.
When this character string is converted to RAW data this is where the difference occurs as the two strings are different lengths. SQL uses the default VARCHAR2 datatype and when you change the function to use VARCHAR2 the result of SQL and PLSQL is the same.
There should be no issue here as when the strings are decoded back again you will not see any difference.
A.2) 我的操作系统需要支持 Unicode 或者我需要在 OS 里安装字符集么?
对于一个 Unicode 数据库,Oracle 不需要运行这个数据库的 OS 具有"Unicode 支持"因为 Oracle AL32UTF8 的实现不依赖于 OS。
例如,完全可以在没有安装任何 UTF-8 语言环境的 Unix 的系统上运行/使用 AL32UTF8 数据库。不过建议您配置操作系统使用 UTF-8,这样您可以将此环境作为 UTF-8*客户端*使用。
同样也没有必要为 Oracle database/client 软件本身"安装Unicode"或者类似的东西,在 Oracle 某版本中所有已知的字符集,包括 Unicode 字符集,是被始终安装的。您根本无法选择不安装它们。
请注意这里是关于使用 Oracle 定义的(使用 AL32UTF8 作为 NLS_CHARACTERSET 或 NLS_LANG),如果你想在 Unix 系统上使用例如 sqlplus 作为一个 UTF-8 客户端,那么你可能需要 OS 操作系统的 UTF-8 支持来使该应用正常工作。
答:数据库不需要,但是在数据库所在服务器上运行sqlplus 查询时需要,所以如果乱码去本地windows查询吧
A.4) Oracle AL32UTF8/UTF8 database 支持/定义了/识别我们插入的语言或者字符么?
简短的回答,当使用 AL32UTF8 时,答案是肯定的。
对于一些语言,如 HKCSC2004,UTF8 可能不是很理想(参见 B.5)。
如果您想 100% 确保,请检查 Oracle release 的 Unicode 版本并查看 http://www.unicode.org 或者 Note 1051824.6 What languages are supported in an Unicode (UTF8/AL32UTF8) database?
相比 AL32UTF8 数据库来说,客户端环境能否支持该语言才是更大的问题
B) 服务器端影响
B.1) 以 AL32UTF8 存储数据(insert 时的 ORA-01401/ORA-12899 错误)
AL32UTF8 是一个变宽度的字符集,表示为 1 个字符的代码可以是 1,2,3 或 4 个字节长。这与 WE8ISO8859P1 或 WE8MSWIN1252 字符集中 1 字符始终是 1 个字节有很大的区别。
US7ASCII 字符集(A-Z,a-Z,0-1 和 ./?,*# 等等)在 AL32UTF8 中总是 1 字节,因此,对于大多数西欧语言的影响是相当有限的,因为在大多数西方语言中,在 8 bit 字符集中,整个数据集只有"特殊"字符会使用更多的字节,(比起 A-Z 来说)他们不经常使用。
当转换西里尔文或阿拉伯文系统到 AL32UTF8 时,由于所有的西里尔或阿拉伯数据将会花费更多的字节来存储,对整个数据集的影响将更大。
请注意,任何 US7ASCII(A-Z,a-Z,0-1 和 ./?,*# ..)以外的字符将采取更多的“字节”来存储相同的字符,所以在列级可能产生很大的影响。
列需要足够大来存放附加的字节,在默认情况下,列的大小是以字节定义而不是字符。
默认情况下,"create table (<colname> VARCHAR2 (300));" 指该列能存放300字节。
这意味着您能存放这样的300字节:比如 300个 1字节的字符例如"a" ,或者 100个 3字节的字符例如€。
如果您尝试存放 101个 3字节的字符您将看到 ORA-01401/ORA-12899 因为这 101个字符的字节长度超过了 300。
从 9i 开始,以您想存放的字符数来定义列的长度而不管字符集是可能的。
关于这是如何工作的,有哪些限制和当前已知的问题,参见 Note 144808.1 Examples and limits of BYTE and CHAR semantics usage (NLS_LENGTH_SEMANTICS)。
更多关于 AL32UTF8 编码是如何工作的,参见 Note 69518.1 Storing and Checking Character Codepoints in a UTF8/AL32UTF8 (Unicode) database。
注意: UTF8 可以是 1,2,3 或者 6字节/字符,所有 4字节的 AL32UTF8 字符在 UTF8 中将会被存储成 2个 3字节。
那些在 UTF8 中占 6字节、在 AL32UTF8 中占 4字节的"real life"字符是有限的并且只会在使用一些中文字符时存在,参见 Note 69518.1 和 note 787371.1。
B.2) 如果使用 AL32UTF8 我的数据库会增长多少?
最大的扩展将会发生在 CLOB 类型上(或者 XMLtype – 该类型后台使用 CLOB),如果源数据库是 8 bit 字符集(WE8ISO8859P1,WE8MSWIN1252 等),那么转换后的 Clob 列会在磁盘大小上变成原来的两倍。
参见 Note 257772.1 CLOBs and NCLOBs character set storage in Oracle Release 8i, 9i, 10g and 11g。
关于扩展评估,请参照下面的条目:
- 如果使用 csscan,Csscan.txt 输出文件会在 Expansion 标题下列出扩展评估,参见 note 444701.1 Csscan output explained。
- 如果使用 DMU 工具,那么在 Database Properties: Scanning tab 栏目里点击 Estimate Tablespace Extension 来检查扩展评估。
我们建议在转换到 AL32UTF8 时总是使用 Csscan 或者 DMU 工具;并且在使用 csscan 或者 DMU 工具扫描源库前不要导出/导入数据,参见A.5)。
对于西欧数据库来说,非CLOB 类型的扩展通常是几个百分比,因为大多数字符实际上是 US7ASCII 字符。
对于存放其他语言群体,像阿拉伯语,西里尔语等的数据库,将从整体上有比西欧数据库更高的数据扩展量。
B.3) 字符的码点(Codepoints)可能在 AL32UTF8 中发生改变
有一种普遍的误解,认为一个字符总是相同的代码,例如英镑符号£通常被称为"code 163"字符。
这是不正确的,只有在某个特定的字符集中,才能说一个字符是一个特定的代码(!)如果你不知道使用的是什么字符集,那么该代码本身没有任何含义。
这种差别看起来很小,实际上不是这样。
例如英镑符号£在 WE8ISO8859P1 和 WE8MSWIN1252 字符集中的确是"code 163"(十六进制A3),但在 AL32UTF8 中英镑符号£是代码 49827(十六进制C2 A3)。
在 AL32UTF8 数据库中,使用 chr(163) 得到的 163 代码是非法字符,因为 163 代码在 UTF8 根本不存在,英镑符号£在 UTF8/AL32UTF8 系统中是 chr(49827)。
因此,当使用类似 CHR() 的函数时要小心,一个字符的代码依赖于数据库的字符集!
使用 Unistr('\codepoint>') 要远远好于使用 CHR(). Unistr() (9i 的一个新功能)在每一个可识别该字符的字符集中均能正常工作。例如当字符集从 WE8MSWIN1252 变到 AL32UTF8 时,不需要改变欧元符号的 Unistr 的值。
关于如何在 AL32UTF8 字符集中检查/查找某一个字符和使用Unistr 的更多信息,参见 Note 69518.1 Storing and Checking Character Codepoints in an UTF8/AL32UTF8 (Unicode) database。
只有 US7ASCII(A-Z,a-z,0-9) 字符在 AL32UTF8 中和 US7ASCII,WE8ISO8859P1,AR8MSWIN1256 等中具有相同的码点(codepoints)。这意味着使用 chr() 应当避免高于 128 的值。
B.12) 确保您没有在字符数据类型(CHAR,VARCHAR2,LONG,CLOB)中存放"binary" (比如 pdf,doc,docx,jpeg,png 等文件)或者加密数据(比如密码)
如果 binary 数据(比如 PDF,doc,docx,jpeg,png 等文件)或者加密数据(比如哈希/加密过的密码)作为 CHAR,VARCHAR2,LONG 或者 CLOB 数据类型被存储/处理时,尤其是在使用 AL32UTF8 数据库时(即使没有使用 exp/imp),丢失数据是正常的,或者会出现一些比如 ORA-29275 或者 ORA-600 [kole_t2u], [34] 的错误。
对于存放 binary 数据(比如 PDF,doc,docx,jpeg,png 等文件)或者加密数据(比如哈希/加密过的密码),唯一支持的数据类型是 LONG RAW 或者 BLOB。
如果您想在 CHAR,VARCHAR2,LONG 或者 CLOB 数据类型中存放 binary 数据(比如 PDF,doc,docx,jpeg,png 等文件)或者加密数据(比如哈希/加密过的密码),那么它们必须在应用层被转化成"字符集安全"的形式,比如 base64 编码。
Note 1297507.1 Problems with (Importing) Encrypted Data After Character Set Change Using Other NLS_CHARACTERSET Database or Upgrading the (client) Oracle Version
Note 1307346.1 DBMS_LOB Loading and Extracting Binary File To Oracle Database
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)