暂无图片
暂无图片
1
暂无图片
暂无图片
暂无图片

Oracle数据库字符集问题

扫地僧的故事 2020-07-01
3146

作为一个既精通mysql又精通oracle的工程师,不能厚此薄彼,必须得雨露均沾才行呀,所以今天就写写oracle是怎么存汉字的,为什么我们在运维过程中经常会碰到乱码的问题,应该怎么解决呢?

同事说必须有美女才能坚持看完这种枯燥无味的技术文章


首先,得从字符集说起。一个字符不论英文,中文,泰文,日文等任何语言,他们在计算机中都是通过二进制的字节保存,这个二进制的编码就是字符编码(也称内码),字符集就是字符与内码的对应表。每个国家都有一个自己的字符集。使用最广泛的ASCII编码(不支持中文),我们国家使用GB2312,GBK等字符集,这些字符集几乎包含了所有汉字字符的内码。为了解决字符集之间的不通用性,推出了Unicode编码,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。我们常用的编码格式是utf-8。
Oracle作为一个国际化的数据库,支持多种语言和字符集,所以在运维过程中也不可避免的会碰到字符集转换导致的乱码问题等。

准备了2个例子,和大家分享下。看看客户端和服务端之间是怎么进行字符集转换,以及大家平时说的"garbage in, garbage out" 到底是什么情况下产生的?

一、客户端和数据库端字符集不一致,并不一定会导致数据库乱码!

假设客户端的字符集是GBK,Oracle数据库的字符集是UTF8,终端字符集是GB2312。为了容易理解,我先用ue编辑器查下,不同编码格式下,"中"字对应的字符编码。

在GBK编码格式下,"中"转换成16进制的编码是:d6,d0; 在UTF8编码格式下,"中"转换成16进制的编码是:e4,b8,ad。

客户端通过sqlplus工具连到数据库,并执行语句:"create table t (n1 varchar2(10));insert into t values ('中');"。这个时候客户端发送给数据库的指令是往表t中插入"D6,D0"。当数据库接收到这个内码后,会去获取客户端NLS_LANG的值来确定这串内码的编码格式。此时,Oracle已经知道了客户端传过来的编码格式是GBK,而我数据库本身的编码格式是UTF8,两者不一致,此时oracle很机智自己把GBK的"中"转换成UTF8的"中",然后存进了数据库。如果你对此有疑问,可以使用dump()函数,执行语句:select dump(16,n1) from t;得到的结果是:e4,b8,ad 。数据库中存的确实是UTF8的"中"字。

接着,客户端发起SQL "select n1 from t;",此时你觉得结果集会不会是乱码?其实同上面一样,oracle在接收到请求取出内码后,并没有直接返回给客户端,而是去获取客户端NLS_LANG的值,来判断是否需要转换编码格式,这里oracle返回给客户端的其实是"D6 D0",客户端从GBK的编码表中找到"D6 D0"对应"中"。

做个实验,简单的验证下:

从上图能看到,确实是以utf8的编码格式存入数据库。那么,返回给客户端编码格式真的是GBK吗?我用spool工具,将查询结果导出到文件。

使用vim编辑文件,以十六进制显示文件内容(命令模式下输入":%!xxd")。看到文件中,"中"存的是d6d0,说明这个文件的编码格式为GBK,与我们预期的一致。

在终端字符集为gbk的前提下,cat t_n1.txt, 中文显示正常。

修改终端字符集编码格式,改为utf8,再cat t_n1.txt ,中文显示出现了乱码。

所以,当我们遇到终端查看文件显示乱码的情况,一定要确保终端编码格式与文件编码格式一致。

看看美女,休息会吧。。
二、如果客户端和数据库端字符集一致,一定不会出现乱码吗?
假设现在客户端的字符集为utf8,数据库的字符集为utf8,终端的字符集为utf8 。为了方便区分文件编码格式,将刚才的t_n1.txt重命名为t_n1_gbk.txt,同时复制该文件,命名为t_n1_gbk.sql,编辑文件内容"truncate table t;insert into t values('中');"。使用iconv命令将gbk编码的t_n1_gbk.sql 转换成utf8编码的文件t_n1_utf8.sql。

使用sqlplus工具连到数据库服务器,分别执行t_n1_gbk.sql和t_n1_utf8.sql.

从上图中能看到,执行t_n1_gbk.sql,数据库中存的"中"字的内码为"D6D0"。所以,我们能得到一个结论:oracle数据库字符集虽然规定了存入数据库时的编码格式,但有时候也会存入错误的内码。将终端字符集改为gbk之后,中文显示正常。这就是大家所说的garbage in,garbage out。存入数据库的内码是d6d0,返回客户端的内码也是d6d0。
这时你可能会有疑问,上一个例子中,oracle在返回客户端结果时,会根据客户端的字符集自动做转换的,为什么这里utf8的客户端,返回的是gbk的内码呢?

其实是因为当客户端的字符集和数据库的字符集一致时,oracle默认不会做任何转换。

执行t_n1_utf8.sql,得到的结果就是我们想要的,中文显示正常,数据库中存的是utf8编码的"中" 。所以,最理想的是,确保数据库字符集,客户端字符集,终端字符集一致,这样就不可能会出现乱码的情况。
我觉得如果理解了上面这些内容,运维过程中遇到80%的乱码情况,都能轻松解决了。ogg同步配置中如何设置抽取端和应用端的字符集,应该也很容易就能理解了。



不知道你们有没有看明白我讲的,反正我觉得我是搞清楚了,哈哈哈~~

文章转载自扫地僧的故事,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论