Oracle-字符集之中文乱码分析

Oracle字符集基本原理

在搞懂Oracle字符集基本原理之前,一定要先分清以下三个概念:
1.Oracle数据库服务器字符集:即Oracle以哪种字符编码存储字符,可以通过以下语句查出数据库字符集的设置。

select * from v$nls_parameters where parameter='NLS_CHARACTERSET';

在这里插入图片描述
我本地数据是GBK,这里演示是AL32UTF8 ,下面是结果都是数据库字符集为AL32UTF8

2.客户端操作系统字符集:即客户端操作系统以哪种字符编码存储字符。
如果是Windows,可以使用 chcp 命令
在这里插入图片描述
如果是Linux,可以使用 echo $LANG
在这里插入图片描述
3. 客户端NLS_LANG参数:该参数用于向Oracle指示客户端操作系统的字符集。
Windows:计算机\HKEY_LOCAL_MACHINE\SOFTWARE\Oracle\KEY_OraDb11g_home1中的NLS_LANG
在这里插入图片描述
Linux,使用命令 echo $NLS_LANG
在这里插入图片描述
有了以上3个基本概念之后,我来阐述一下Oracle字符集转换的基本原则:
1.设置客户端的NLS_LANG为客户端操作系统的字符集
2.如果数据库字符集等于NLS_LANG,数据库和客户端传输字符时不作任何转换
3.如果它们俩不等,则需要在不同字符集间转换,只有客户端操作系统字符集是数据库字符集子集的基础上才能正确转换,否则会出现乱码。

几种常见情况分析
下面先看一个例子,再透过现象看本质,我们会针对这个例子进行分析。
该例子如下:
场景:数据库字符集 AL32UTF8操作系统window字符集 GBK
分别打开 cmd,设这里在同一个操作系统启动两个session,session1的NLS_LANG设为和数据库字符集一样(即AL32UTF8):
在这里插入图片描述
插入中文数据

--session1
insert into test values(1,'中国');
--session2
insert into test values(2,'中国');

在这里插入图片描述
执行查询 select * from test;
在session 1上执行查询:
Session_1>select * from test;
ID NAME


     1 中国
     2 涓   浗

在session 2上执行查询:
Session_2>select * from test;
ID NAME


     1 ???
     2 中国

上面例子看起来很诡异,session1和2都能正常显示自己插入的字符串,又都不能正常显示对方插入的字符串。为了弄清楚,我们首先得知道数据库里对这两个字符串是怎么存储的。我们可以使用dump函数获得字符在数据库的编码:

SQL> select id,dump(NAME,1016) from test;
ID DUMP(NAME,1016)
-- ------------------------------------------------------------
 1 Typ=1 Len=4 CharacterSet=AL32UTF8: d6,d0,b9,fa
 2 Typ=1 Len=6 CharacterSet=AL32UTF8: e4,b8,ad,e5,9b,bd

根据AL32UTF8的编码,“中国”两字的正确编码为(都为3个字节):
中–e4,b8,ad
国–e5,9b,bd
因此session 1插入的字符串在数据库中的编码是错误的,session 2正确。这也是为什么一定要设置NLS_LANG为客户端操作系统的字符集。
但是根据上面实验我们可以知道,数据库中存储正确,并不代表客户端能正常显示;同样地,即时数据库没有正确存储,有时候客户端也能够正常显示

场景1:session 1插入,session 1查询,在数据库中存储错误,但显示正确。
插入过程:
”中国“两字在客户端操作系统字符集ZHS16GBK中的编码是”d6,d0,b9,fa",由于NLS_LANG和数据库字符集相同,数据库端对客户端传过来的字符编码不进行任何转换直接存入数据库,因此数据库中存储的编码也是“d6,d0,b9,fa”,
读取过程:
数据库端读取的编码是“d6,d0,b9,fa”,由于NLS_LANG和数据库字符集相同,客户端对数据库端传过来的字符编码不进行任何转换直接显示,编码”d6,d0,b9,fa“在客户端操作系统字符集ZHS16GBK对应的汉字为“中国”。

从以上分析可知,虽然读取时正确的,但那是因为负负得正,实际上数据库中存储是错误的,因此要特别小心这种情况,在生成库中要避免

Session_1>select length(NAME) from test where id=1;
LENGTH(NAME)
-----------
          3

得出的长度居然为3!实际的长度只是2。

场景2:session 1插入,session 2查询,在数据库中存储错误,显示也错误。
插入过程和场景1一样,这里就不再累述。
读取过程:
数据库端读取的编码是“d6,d0,b9,fa”,由于NLS_LANG和数据库字符集不同,客户端对数据库端传过来的字符编码进行转换,数据库端字符集AL32UTF8里编为“d6,d0,b9,fa”无法在客户端操作系统字符集ZHS16GBK里找到对应的编码,所以只好用?代替。

场景3:session 2插入,session 1查询,在数据库中存储正确,但显示错误。
插入过程:
”中国“两字在客户端操作系统字符集ZHS16GBK中的编码是”d6,d0,b9,fa",由于NLS_LANG和数据库字符集不同,Oracle会进行字符编码转换,也就是将字符集ZHS16GBK里“中国”的编码“d6,d0,b9,fa"转换为字符集"AL32UTF8"里”中国“的编码”e4,b8,ad,e5,9b,bd“。
读取过程:
数据库端读取的编码是”e4,b8,ad,e5,9b,bd“,由于NLS_LANG和数据库字符集相同,客户端对数据库端传过来的字符编码不进行任何转换直接显示,编码”e4,b8,ad,e5,9b,bd“在客户端操作系统字符集ZHS16GBK对应的汉字为“涓 浗”(原本2个字符,现在变成了3个字符,因为ZHS16GBK的汉字以2个字节编码)。

场景4:session 2插入,session 2查询,在数据库中存储正确,显示也正确。
插入过程和场景3类似。
读取过程:
数据库端读取的编码是”e4,b8,ad,e5,9b,bd“,由于NLS_LANG和数据库字符集不同,客户端对数据库端传过来的字符编码进行转换,数据库端字符集AL32UTF8里”中国“两字的编码”e4,b8,ad,e5,9b,bd“转换成客户端操作系统字符集ZHS16GBK里“中国”两字的编码“d6,d0,b9,fa",并正常显示。
这种情况虽然经过了两次转换,都确实最正确、最推荐的方式。

结论:NLS_LANG只和客户端操作系统的字符集相关,如果客户端操作系统的字符集和数据库字符集间无法正确转换,则应该首先改变客户端终端的字符集,而不是简单地把NLS_LANG设为和数据库字符集一样。

转载此文章:https://www.jb51.net/article/39719.htm

图示:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

大图: https://www.processon.com/view/link/5fedfcfe6376896cfafd53be

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值