为什么我们用ORACLE处理中文总不能如意,为什么我们在直接下SQL处理中文,会造成字符丢失, 著名的"单引号失踪"案到底是怎么回事?
本文将一一为您解答:
[@more@]篇首鸣谢:牛人Willie(山歌)
前段时间研究ORACLE对数据的存储的时候, 曾经有了解过一些关于ORACLE字符集的东西, 但是苦于有个问题一只萦绕于心, 所以没有什么东西产出。
这个问题就是我们客户端与服务器端的字符集都设成UTF8的时候, 当我们在SQL Navigator中直接下SQL操作某些中文字符串的时候, 会莫名奇妙的出现错误,例如
SELECT ‘济南’ FROM DUAL |
就会出错, 提示说少了一个单引号:
0:14:32 ORA-01756: quoted string not properly terminated |
但是我们用下面这种不符合语法的SQL,却可以:
SELECT ‘济南’’ FROM DUAL |
虽然有知道,是因为客户端字符集与服务器端字符集一致, 所以将上述SQL传入ORACLE的时候, 系统不会为其进行字符集转换,而ORACLE在将传入的ZHS16GBK编码以UTF8的方式来读取的时候,发生错误,将单引号给”吞掉”了。
但是, 知其然而不知其所以然, 总是觉得很难受, 找了很多资料, 都没有得到正解。都快积郁成疾了。 最终, Team内牛人出山了, 用一个”哥德巴赫”猜想, 一解俺的困惑。
我们知道UTF8在存储的时候, 是以如下方式进行编码的:
当要表示的内容是 7位 的时候就用一个字节:0*******
第一个0为标志位,剩下的空间正好可以表示ASCII 0-127 的内容。
当要表示的内容在 8 到 11 位的时候就用两个字节:110***** 10******
第一个字节的110和第二个字节的10为标志位。
当要表示的内容在 12 到 16 位的时候就用三个字节:1110***** 10****** 10****** 和上面一样,第一个字节的1110和第二、三个字节的10都是标志位,剩下的空间正好可以表示汉字, 因为我们的汉字就是用2个字节16位来存储的。以此类推:
四个字节:11110**** 10****** 10****** 10****** 五个字节:111110*** 10****** 10****** 10****** 10****** 六个字节:1111110** 10****** 10****** 10****** 10****** 10******
不过目前ORACLE 9I中常看到的UTF8字符集之支持最大3个字节的UTF8编码, 所以严格来说,ORACLE 9I中的UTF8并没有完全实现UTF8, 大概是因为目前3个字节就已经够用了, 完全可以用来存储当今已经存在的各种标准字符集。
我们的猜想如下:服务器端字符集是UTF8的时候, 基于以上规则, ORACLE引擎会以如下的方式来解析传入的字节流:
每次一个字节读入,
check到第一位为”0”,则以ASCII码方式解析这一个字节。
前2位为”10”, 非法,忽略当前字节。因为按照上面的规则, 不可能有以”10”开头的。
前3位为”110”, 读入2个字节。
前3位为”1110”, 读入3个字节。
前4位为”1111”, 非法, 忽略当前字节。因为9I的UTF8不支持这种。
所有当其解析”‘济南’”的时候, 会以如下方式解析:
我们知道单引号的ASCII码是27, “济南”的GBK编码是 BCC3 C4CF, 所以以上字符串的字节流便是:
00011011 10111100 11000011 11000100 11001111 00011011
第一个字节, 以”0”开头,解析为”’”
第二个字节, 以”10”开头, 忽略
第三个字节, 以”110”开头,读入2个字节
第五个字节, 以”110”开头,读入2个字节
注意,它将最后一个单引号当作与前面一个字节一起当作一个字符读入了,所以其SQL引擎找不到最后那个单引号了, 所以认为语法有错误。
其它各种中文出错的情况也都可模拟出来。至于如何解决, 就看各位自己的了, 知道了缘由, 办法自然就有了。
特别鸣谢: 文中的牛人Willie(山歌)
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/767125/viewspace-969030/,如需转载,请注明出处,否则将追究法律责任。
转载于:http://blog.itpub.net/767125/viewspace-969030/