这篇博客的目的要搞清以下两个问题:
1. 如何正确设置NLS_LANG?
2. 如果正确使用国家字符集?
参考: Character Sets & Conversion - Frequently Asked Questions [ID 227330.1]
1. 如何正确设置NLS_LANG
metalink上有两篇文章是专门讨论如何设置NLS_LANG,一篇针对Windows系统,一篇针对Unix系统:
The correct NLS_LANG in a Windows Environment [ID 179133.1]
The correct NLS_LANG setting in Unix Environments [ID 264157.1]
文中提到了设置NLS_LANG的几个关键点:
* Setting the NLS_LANG to the characterset of the database (NLS_CHARACTERSET) MAY be correct but IS NOT ALWAYS correct. Please DO NOT assume that NLS_LANG needs to be ALWAYS the same as the database characterset. THIS IS NOT TRUE.
* The characterset defined with the NLS_LANG parameter does NOT CHANGE your client's characterset, it is used to let Oracle know what characterset you are USING on the client side, so Oracle can do the proper conversion.
我的结论是:NLS_LANG只和客户端终端的字符集相关,如果客户端终端的字符集和数据库字符集间无法正确转换,则应该首先改变客户端终端的字符集,而不是简单地把NLS_LANG设为和数据库字符集一样。
下面以Window XP为例子设置NLS_LANG:
1) 怎么知道我的客户端终端的字符集?
Windows上有两种类型的终端ANSI 和OEM,ANSI终端指的是图形化的终端(如sqlplusW.exe),而OEM终端指的是dos/cmd等(如sqlplus.ext)。
我们打开cmd,输入chcp(这里是OEM终端):
C:\Documents and Settings\a105024\Desktop>chcp
Active code page: 936
可以查出code page为936,再到微软的官方网站:(http://msdn.microsoft.com/en-gb/goglobal/bb896001.aspx )查询936所对应的Locale是Chinese:
再到文章 The correct NLS_LANG in a Windows Environment [ID 179133.1] point 7 里可以查出Chinese (PRC)对应的字符集为ZHS16GBK ,因此我们应该把NLS_LANG最终设为SIMPLIFIED CHINESE_CHINA.ZHS16GBK
2) 如何修改客户端终端字符集?
如果客户端终端字符集不支持中文,那么我们需要修改操作系统配置使得它支持中文,可参考该文档修改:
How to change the ANSI Code Page (ACP) on Windows NT 4.0 and Windows 2000 / XP [ID 199926.1]
2. 如果正确使用国家字符集?
The National Character Set ( NLS_NCHAR_CHARACTERSET ) in Oracle 9i, 10g and 11g [ID 276914.1]
Character Sets & Conversion - Frequently Asked Questions [ID 227330.1] Point 14
Oracle针对国家字符集引入了一个新的参数:ORA_NCHAR_LITERAL_REPLACE,该参数默认为false,表示任何从客户端传过来的NCHAR类型的字符先转换为数据库字符集,再转换为国家数据库字符集,而把该参数设为true后,从客户端传过来的NCHAR类型的字符直接转换为国家字符集存储,因此要想正确存储NCHAR字符集,必须得把该参数设为TRUE。
注意:该参数只有在10gr2或以上的数据库才有作用(即Oracle server和Oracle client最低要10gr2)!
3. 实验
实验解释:
实验1(客户端查询正确,数据库存储正确): ‘中’在ZHS16GBK下的编码是2字节的‘d6,d0',由于ORA_NCHAR_LITERAL_REPLACE参数为TRUE,因此直接把ZHS16GBK的'中'转换为国家字符集UTF8对应的编码'e4,b8,ad’ (中文字符在UTF8中为3字节的编码);
实验2(客户端查询错误,数据库存储错误):‘中’在ZHS16GBK下的编码是2字节的‘d6,d0',由于ORA_NCHAR_LITERAL_REPLACE参数为FALSE,因此首先尝试把ZHS16GBK的‘中’转换为数据库字符集 WE8ISO8859P1 的'中',但是由于 WE8ISO8859P1 不支持中文字符,会用一个1字节的替代字符表示原来2字节的字符。接着又由于是NCHAR字符,故Oracle把该1字节的替代字符又转换成UTF8两字节的字符(c2,bf);
实验3,4 (客户端查询正确,数据库存储错误):当NLS_LANG和数据库字符集一样时,不管ORA_NCHAR_LITERAL_REPLACE怎么设置,数据库不转换直接接收'中‘在ZHS16GBK下的编码'd6,d0',此时由于数据库时1字节的编码 WE8ISO8859P1 ,因此会把它当做是两个字符看待。接着又由于是NCHAR字符,故Oracle把这两个 WE8ISO8859P1 下1字节的两个字符转换重UTF8下 2字节的两个字符(c3,96,cd,90)。
特别要注意实验3,4的情况,虽然客户端查询显示是正确的,但实际上数据库存储是错误的,这时只要对该字符做一个length操作,便一目了然了:
select length(caption) from jars.NAVIGATEMENU_YJ where id =2013;
length(caption)
-------------------
2
我们的本意是存进'中’一个字符,结果数据库里实际寸的是两个字符,因此这种情况要特别注意,如果你只是为了显示(如报表)用,图方便可以把NLS_LANG设为和数据库字符集一样,但是一旦你要在数据库层面对字符进行操作,则肯定会出错,且错误会很诡异。
1. 如何正确设置NLS_LANG?
2. 如果正确使用国家字符集?
参考: Character Sets & Conversion - Frequently Asked Questions [ID 227330.1]
1. 如何正确设置NLS_LANG
metalink上有两篇文章是专门讨论如何设置NLS_LANG,一篇针对Windows系统,一篇针对Unix系统:
The correct NLS_LANG in a Windows Environment [ID 179133.1]
The correct NLS_LANG setting in Unix Environments [ID 264157.1]
文中提到了设置NLS_LANG的几个关键点:
* Setting the NLS_LANG to the characterset of the database (NLS_CHARACTERSET) MAY be correct but IS NOT ALWAYS correct. Please DO NOT assume that NLS_LANG needs to be ALWAYS the same as the database characterset. THIS IS NOT TRUE.
* The characterset defined with the NLS_LANG parameter does NOT CHANGE your client's characterset, it is used to let Oracle know what characterset you are USING on the client side, so Oracle can do the proper conversion.
我的结论是:NLS_LANG只和客户端终端的字符集相关,如果客户端终端的字符集和数据库字符集间无法正确转换,则应该首先改变客户端终端的字符集,而不是简单地把NLS_LANG设为和数据库字符集一样。
下面以Window XP为例子设置NLS_LANG:
1) 怎么知道我的客户端终端的字符集?
Windows上有两种类型的终端ANSI 和OEM,ANSI终端指的是图形化的终端(如sqlplusW.exe),而OEM终端指的是dos/cmd等(如sqlplus.ext)。
我们打开cmd,输入chcp(这里是OEM终端):
C:\Documents and Settings\a105024\Desktop>chcp
Active code page: 936
可以查出code page为936,再到微软的官方网站:(http://msdn.microsoft.com/en-gb/goglobal/bb896001.aspx )查询936所对应的Locale是Chinese:
LCID
| Culture Name | Locale
| Language | Local language name | ANSI codepage | OEM codepage | Country or Region name abbreviation * | Language name abbreviation ** |
---|
0x0804 | zh-CN | Chinese (People's Republic of China) | Chinese | 中文(中华人民共和国) | 936 | 936 | CHN | CHS |
0x0004 | zh-CHS | Chinese (Simplified) | Chinese | 中文(简体) | 936 | 936 | TWN | CHS |
0x1004 | zh-SG | Chinese (Singapore) | Chinese | 中文(新加坡) | 936 | 936 | SGP | ZHI |
再到文章 The correct NLS_LANG in a Windows Environment [ID 179133.1] point 7 里可以查出Chinese (PRC)对应的字符集为ZHS16GBK ,因此我们应该把NLS_LANG最终设为SIMPLIFIED CHINESE_CHINA.ZHS16GBK
2) 如何修改客户端终端字符集?
如果客户端终端字符集不支持中文,那么我们需要修改操作系统配置使得它支持中文,可参考该文档修改:
How to change the ANSI Code Page (ACP) on Windows NT 4.0 and Windows 2000 / XP [ID 199926.1]
2. 如果正确使用国家字符集?
The National Character Set ( NLS_NCHAR_CHARACTERSET ) in Oracle 9i, 10g and 11g [ID 276914.1]
Character Sets & Conversion - Frequently Asked Questions [ID 227330.1] Point 14
Oracle针对国家字符集引入了一个新的参数:ORA_NCHAR_LITERAL_REPLACE,该参数默认为false,表示任何从客户端传过来的NCHAR类型的字符先转换为数据库字符集,再转换为国家数据库字符集,而把该参数设为true后,从客户端传过来的NCHAR类型的字符直接转换为国家字符集存储,因此要想正确存储NCHAR字符集,必须得把该参数设为TRUE。
注意:该参数只有在10gr2或以上的数据库才有作用(即Oracle server和Oracle client最低要10gr2)!
3. 实验
Test | Code page | NLS_LANG | ORA_NCHAR_LITERAL_REPLACE | NLS_CHARACTERSET | NLS_NCHAR_CHARACTERSET | Input | Output | Dump |
1 | 936 | SIMPLIFIED CHINESE_CHINA.ZHS16GBK | TRUE | WE8ISO8859P1 | UTF8 | N'中' d6,d0 | 中 | Typ=1 Len=3 CharacterSet=UTF8: e4,b8,ad |
2 | 936 | SIMPLIFIED CHINESE_CHINA.ZHS16GBK | FALSE | WE8ISO8859P1 | UTF8 | N'中' d6,d0 | ? | Typ=1 Len=2 CharacterSet=UTF8: c2,bf |
3 | 936 | AMERICAN_AMERICA.WE8ISO8859P1 | TRUE | WE8ISO8859P1 | UTF8 | N'中' d6,d0 | 中 | Typ=1 Len=4 CharacterSet=UTF8: c3,96,c3,90 |
4 | 936 | AMERICAN_AMERICA.WE8ISO8859P1 | FALSE | WE8ISO8859P1 | UTF8 | N'中' d6,d0 | 中 | Typ=1 Len=4 CharacterSet=UTF8: c3,96,c3,90 |
实验解释:
实验1(客户端查询正确,数据库存储正确): ‘中’在ZHS16GBK下的编码是2字节的‘d6,d0',由于ORA_NCHAR_LITERAL_REPLACE参数为TRUE,因此直接把ZHS16GBK的'中'转换为国家字符集UTF8对应的编码'e4,b8,ad’ (中文字符在UTF8中为3字节的编码);
实验2(客户端查询错误,数据库存储错误):‘中’在ZHS16GBK下的编码是2字节的‘d6,d0',由于ORA_NCHAR_LITERAL_REPLACE参数为FALSE,因此首先尝试把ZHS16GBK的‘中’转换为数据库字符集 WE8ISO8859P1 的'中',但是由于 WE8ISO8859P1 不支持中文字符,会用一个1字节的替代字符表示原来2字节的字符。接着又由于是NCHAR字符,故Oracle把该1字节的替代字符又转换成UTF8两字节的字符(c2,bf);
实验3,4 (客户端查询正确,数据库存储错误):当NLS_LANG和数据库字符集一样时,不管ORA_NCHAR_LITERAL_REPLACE怎么设置,数据库不转换直接接收'中‘在ZHS16GBK下的编码'd6,d0',此时由于数据库时1字节的编码 WE8ISO8859P1 ,因此会把它当做是两个字符看待。接着又由于是NCHAR字符,故Oracle把这两个 WE8ISO8859P1 下1字节的两个字符转换重UTF8下 2字节的两个字符(c3,96,cd,90)。
特别要注意实验3,4的情况,虽然客户端查询显示是正确的,但实际上数据库存储是错误的,这时只要对该字符做一个length操作,便一目了然了:
select length(caption) from jars.NAVIGATEMENU_YJ where id =2013;
length(caption)
-------------------
2
我们的本意是存进'中’一个字符,结果数据库里实际寸的是两个字符,因此这种情况要特别注意,如果你只是为了显示(如报表)用,图方便可以把NLS_LANG设为和数据库字符集一样,但是一旦你要在数据库层面对字符进行操作,则肯定会出错,且错误会很诡异。
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/26277071/viewspace-710454/,如需转载,请注明出处,否则将追究法律责任。
转载于:http://blog.itpub.net/26277071/viewspace-710454/