字符集简单来说就是字符在计算机中的数字表示,学过计算机的人都知道ASCII编码,这就是一种字符集了。学过计算机的中国人都知道ASCII只有7位共可表示128种字符,用于英文字母及符号是够用了,但汉字显然远远不止128个,所以就有了用2字节16位编码的GB2312码,然后又不继扩充形成了GBK、GB18030等汉字编码。但是,对ASCII编码不满的绝不仅仅只有中国,日本、韩国甚至法国等欧洲国家只用ASCII编码也是不够的,所以世界上有很多种字符集,各国在对自已的文字进行编码时,顶多考虑与ASCII兼容,往往会与其他的编码冲突,也就是说,同一个二进制代码在不同的字符集表示的是不同的符号。然后、然后就有了unicode,但是由于历史原因,并不是所有的操作系统和软件都采用了Unicode,而是延用了以前的各种编码,并且为了向下兼容,即便程序内部采用的是Unicode,但是输入输出还是要转换成原有的编码。所以就有了本地字符集的概念,就是根据用户所处的国家区域设定一种或几种字符集。比方说在中国,使用的主要还是GB2312等字符集。在中国用中国的字符集、在美国用美国的字符集,这没什么关系,反正互不影响,都用得挺好。但Oracle以全球化的观点来看这个问题,认为一个数据库可能有多个国家的人在使用(比如跨国公司),将数据以本地化的方式展现给用户显示是最好不过的事,所以,Oracle就设置了服务端字符集、客户端字符集,用户就使用本地的字符集,然后转换成服务端的字符集保存在数据库中,取数据的时候再转换一下。这样,不同语言(字符集)的用户就可以使用同一个数据库。不过,很显然,采用这种做法的话,数据库字符集显然应该是各种可能用到的客户端字符集的超集。这应该才是确定数据库字符集的主要依据。

很显然,如果客户端字符集和数据库字符集一致,这种转换就不会发生,就好像在用一种本地软件,根本不存在字符集的概念一样。正因为如此,那怕数据库字符集使用的是US7ASCII也一样可以保存汉字,因为存进去的只是一串二进制数,取出来的也还是这一串二进制数,数据库根本不会管它代表的是什么符号,只要客户端懂,显示出来就是正确的。但是,在中国将数据库字符集设置为US7ASCII的人是不是没有考虑排序呀、货币符号呀、日期格式呀这些问题。

到目前为止,一切都还算正常,排序呀、日期格式呀这些问题都不算难处理。但是有了UTF后,麻烦就来了。UTF编码收录了各国文字和符号,统一编码嘛,这是好事呀,大家都用同一编码,不就和谐了么。事情不是这样的,在没有UTF之前,大家都用本地编码,对于程序而言,根本不都考虑编码的问题,如前所述,输入是什么输出还是什么,什么问题都没有。有了UTF编码之后,一些程序内部采用的是UTF编码,它就会把输入的东西转换成UTF编码再进行处理,比如说在中国,输入法输入汉字的时候用的是GBK编码,到了UTF程序里面,就转成了UTF编码了,然后显示、打印等输出时就用UTF编码,反正看上去汉字还是那个汉字,你根本发觉不了有什么变化。但如果是程序之间呢?本来是GBK编码的东西,转成UTF后又存到数据库里面去会怎么样呢?对于Oracle来说,如果数据库和客户端字符集都设置为US7ASCII,这时候Oracle客户端是不会做任何转换的,直接保存,但是保存的是UTF编码,然后取出来的时候,如果用本地程序(不考虑编码的程序)取,那么将会当做本地编码来处理,显然是乱码。那么将客户端字符集设成UTF字符集行不行呢?显然不行,因为这个时候Oracle就会把本来是UTF编码的东西当成是ASCII再做一次转换。从理论上来说,只要数据库和客户端字符集设置一致,在数据库客户端和服务端之间是不会发生转换的,这时候用UTF程序写进去的东西,取出来应该还是一样的,但是用本地编码的程序取出来就乱了,反之,用本地编码程序写进数据库的东西用UTF程序取出来也是乱的。

总之,再在可以把应用程序分成两类,一类是用本地编码的,一类是用UTF编码的,同一类程序写入数据库的数据可以用同类程序读取,用不同类的程序读取出来就是乱码。而现在比较新的一些开发工具比如.net、java内部都是采用UTF编码,而一些老的开发工具如powerbuilder就是用本地编码,如果这两类程序都要使用,并且数据库字符集还设置成了US7ASCII的话,那就麻烦了,目前唯一的办法就是在程序里面转码,每读入一个含有汉字的字符串都要转,够麻烦了吧。

慢!为什么数据库设置成us7ascii就会有问题,设置成别的比方说GB2312就不会有问题呢?这是因为,UTF是一个比较大的编码方案,GB2312里面的符号在UTF里都有,GB2312编码转成UTF编码不会有任何问题,只要有一张对应关系的表就行得通了,反之,GB2312里面的符号那怕表示成了UTF编码,只要有这张表,转回来也没有问题。但是,如果数据库字符集设置成了US7ASCII,用本地编码的程序读写都没问题,但是用UTF程序读的话,就会按ASCII转UTF的规则来转,本来是GB2312的东西当成ASCII来转换了,所以乱码问题就出来了。简单来讲,本地编码的程序不考虑字符集转换,只要Oracle客户端和服务端不发生转换,写进去什么样取出来也什么样,就不会有问题,而如果用UTF程序去读取数据库里面的东西,就会发生字符集转换,而本来是GB2312编码的东西,你偏说是ASCII,转换出来当然就错了。

补充一点,用JDBC驱动的话,更麻烦,因为JAVA内部是用UTF-16编码的,所以如果数据库字符集如果是ASCII,那么将会执行ASCII到UTF之间的转换,存的时候将UTF转换为ASCII,显然这个时候汉字存进去的话就已经转换错了,然后取出来的时候又由ASCII 转成UTF,存的时候就已经错了,显然这个转换也不可能换回来了。就算用JDBC的THIN和OCI都一样,用THIN驱动直接从数据库取,要转换,用OCI的话先通过Oracle客户端,如果客户端字符集和数据库字符集不一致,那到OCI这里可能就已经转换出错了,再换到JAVA里不可能更好,如果客户端字符集和数据库字符集一致,那还不是跟用Thin驱动同样的结果!

所以在中国将Oracle数据字符集设置成US7ASCII真的是一件再愚蠢不过的事情。ODAC也是Oracle提供的,情况好像跟JDBC一样,总是要发生UTF字符转换,既没有办法让它不转换,又不能通过更改客户端字符集让它进行正常的转换。其实Oracle没有错,设计数据库字符集和客户端字符集是能让产品全球化,使各地用各自的语言使用同一个产品、同一个数据库,JDBC、ODAC自动转换也没错,既然程序可以自动判断为什么还需要人为设置如何转换呢?错就错在本来用的是中文却偏偏要将数据库字符集设置为ASCII!