签到成功

知道了

CNDBA社区CNDBA社区

有关Oracle字符集转换的一点讨论

2016-11-09 09:58 4847 0 原创
作者: arealman

最近,群里面见到很多关于ORACLE乱码的问题的讨论,我总结了一下普遍的几个问题,做了几个测试案例,再针对测试案例的结果解释一下原因。希望能做到知其然,知其所以然。。。

测试背景:操作系统默认字符集GBK,ORACLE服务端字符集AL32UTF8 执行工具是 sqlplushttp://www.cndba.cn/cndba/arealman/article/331http://www.cndba.cn/cndba/arealman/article/331

下面几个测试案例都是用"哈"字符来展示的,先说明这么一个事实,"哈"字符在GBK下的编码为"B9 FE",在UTF8下的编码为"E5 93 88".http://www.cndba.cn/cndba/arealman/article/331

Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation.  All rights reserved.
C:/Users/hongjiel>echo %NLS_LANG%
AMERICAN_AMERICA.US7ASCII
C:/Users/hongjiel>sqlplus rm_share_v2/rm_share_v2@oss1
SQL*Plus: Release 11.2.0.1.0 Production on Tue Nov 8 16:27:04 2016
Copyright (c) 1982, 2010, Oracle.  All rights reserved.
Connected to:
Oracle Database 10g Enterprise Edition Release 10.2.0.3.0 - 64bit Production
With the Partitioning, OLAP and Data Mining options

SQL> select '哈' from dual;
'??'
--------------------------------
??
SQL> select dump('哈',16) from dual;
DUMP('??',16)
--------------------------------------------------------------------------------
Typ=96 Len=6: ef,bf,bd,ef,bf,bd
SQL> quit

Disconnected from Oracle Database 10g Enterprise Edition Release 10.2.0.3.0 - 64bit Production
With the Partitioning, OLAP and Data Mining options (with complications)
C:/Users/hongjiel>echo %NLS_LANG%
AMERICAN_AMERICA.US7ASCII
C:/Users/hongjiel>set NLS_LANG=AMERICAN_AMERICA.AL32UTF8
C:/Users/hongjiel>sqlplus rm_share_v2/rm_share_v2@oss1
SQL*Plus: Release 11.2.0.1.0 Production on Tue Nov 8 16:41:18 2016
Copyright (c) 1982, 2010, Oracle.  All rights reserved.
Connected to:
Oracle Database 10g Enterprise Edition Release 10.2.0.3.0 - 64bit Production
With the Partitioning, OLAP and Data Mining options
SQL> select dump('哈',16) from dual;
DUMP('哈',16)
--------------------------------------------------------------------------------
Typ=96 Len=2: b9,fe
SQL> quit
Disconnected from Oracle Database 10g Enterprise Edition Release 10.2.0.3.0 - 64bit Production
With the Partitioning, OLAP and Data Mining options
C:/Users/hongjiel>set NLS_LANG=AMERICAN_AMERICA.UTF8
C:/Users/hongjiel>sqlplus rm_share_v2/rm_share_v2@oss1
SQL*Plus: Release 11.2.0.1.0 Production on Tue Nov 8 16:42:27 2016
Copyright (c) 1982, 2010, Oracle.  All rights reserved.
Connected to:
Oracle Database 10g Enterprise Edition Release 10.2.0.3.0 - 64bit Production
With the Partitioning, OLAP and Data Mining options
SQL> select dump('哈',16) from dual;
DUMP('锟斤拷',16)
--------------------------------------------------------------------------------
Typ=96 Len=6: ef,bf,bd,ef,bf,bd
SQL> select '哈' from dual;

'锟斤拷'
--------------------------------------------------------------------------------
锟斤拷

上面共列举了三个案例,分别将nls_lang设置为US7ASCII,AL32UTF8,UTF8


http://www.cndba.cn/cndba/arealman/article/331

案例一,执行"select '哈' from dual;"返回的执行结果是"??",执行"select dump('哈',16) from dual;"计算该字符的编码值,返回结果为"ef,bf,bd,ef,bf,bd". 本案例的nls_lang = US7ASCII, '哈'字符按照操作系统的编码输入编码B9 FE, 因为nls_lang与服务端字符集不一样,因此要做编码转换 US7ASCII ==> AL32UTF8. 因为SU7ASCII为单字节编码,因此会认为B9 FE 编码为两个字符,而这两个字符都不是有效的ASCII字符,可认为是乱码,因此转换为AL32UTF8下的默认乱码字符"ef,bf,bd,ef,bf,bd"。这一步骤解释了为什么执行"select dump('哈',16) from dual;"返回的编码值为"ef,bf,bd,ef,bf,bd"; 当SQL执行后将结果返回给客户端,在客户端也还是要再做一次字符编码转换AL32UTF8 ==> US7ASCII, "ef,bf,bd,ef,bf,bd"编码是AL32UTF8的有效字符,两个三字节编码字符��。但是该字符不是US7ASCII的有效字符,无法在编码对照表中找到转换关系的,转换为US7ASCII下的默认乱码字符"3F 3F",也就是字符串"??".http://www.cndba.cn/cndba/arealman/article/331


http://www.cndba.cn/cndba/arealman/article/331

案例二, nls_lang = AL32UTF8, '哈'字符按照操作系统的编码输入编码B9 FE,因为nls_lang与服务端编码一致,因此客户端不需要做编码转换,直接把字符按原先的编码送到服务器端。服务器端执行完之后返回给客户端的字符,在客户端同理也不用做编码转换,因此sqlplus下显示的字符编码为B9 FE,sqlplus按照操作系统默认字符集解释"B9 FE",结果为 '哈'字。

--PS:想象一下,如果该动作是insert 而不是 select, 则保存到数据库的编码就是 "B9 FE",服务端编码为AL32UTF8,也就是说AL32UTF8字符集的库,实际存储的是GBK字符编码,可认为是乱码了。http://www.cndba.cn/cndba/arealman/article/331


http://www.cndba.cn/cndba/arealman/article/331

案例三, nls_lang = UTF8, '哈'字符按照操作系统的编码输入编码"B9 FE",因为nls_lang与服务端编码不一致(尽管utf8是al32utf8的子集),所以客户端需要做编码转换。 utf8字符集的编码方案为可变长字节编码,但"B9 FE"不符合双字节字符的编码规则(UTF8编码规则我在其他文章中有做说明,或者百度),可认为是两个乱码,因此转换为目标字符集AL32UTF8下的默认乱码字符"ef,bf,bd,ef,bf,bd"。 客户端将该编码的字符发送到服务端执行后返回,在客户端同理也需要编码转换的,转换方案为 AL32UTF8 ==> UTF8. "ef,bf,bd,ef,bf,bd" 编码在AL32UTF8下是有效的编码,两个三字节编码字符��,转换后的编码也是"ef,bf,bd,ef,bf,bd"。 因此sqlplus下显示的字符编码为 "ef,bf,bd,ef,bf,bd",按照操作系统默认字符集GBK来解释这些编码,则为锟(ef,bf)斤(bd,ef)拷(bf,bd).


http://www.cndba.cn/cndba/arealman/article/331

经常也有看到有人的疑问,为什么应用程序或者sqlplus下查询是乱码的,而plsql dev工具显示的却是正常的呢?  

这里也来解释一下为什么会有这种现象。 plsql dev 作为一个比sqlplus 集成度更高的工具,在字符集转换的问题上,比sqlplus 等多做了一重预处理了。我们用A表示操作系统默认字符集,B表示NLS_LANG,C表示服务端字符集。 对于 A <== 1 ==> B <== 2 ==> C 这2个环节, sqlplus 仅仅是在 环节2)做一次字符集转换.而 plsql 在环节1也会做一次处理。

重新测试一次上面的案例二,除了将sqlplus工具换为PLSQL Dev之外,其他的设置是一样的。在PLSQL中执行:
SQL> select dump('哈',16) from dual;
 
DUMP('哈',16)
--------------------------------------------------------------------------------
Typ=96 Len=3: e5,93,88
 
SQL>

可以看出,得到与上面的案例二不同的结果,编码"B9 FE"转换为UTF8的"e5,93,88"了。

http://www.cndba.cn/cndba/arealman/article/331


版权声明:本文为博主原创文章,未经博主允许不得转载。

字符 编码 乱码

用户评论
* 以下用户言论只代表其个人观点,不代表CNDBA社区的观点或立场
arealman

arealman

关注
  • 10
    原创
  • 0
    翻译
  • 0
    转载
  • 5
    评论
  • 访问:53852次
  • 积分:51
  • 等级:注册会员
  • 排名:第42名
精华文章
    最新问题
    查看更多+
    热门文章
      热门用户
      推荐用户
        Copyright © 2016 All Rights Reserved. Powered by CNDBA · 皖ICP备2022006297号-1·

        QQ交流群

        注册联系QQ