ue
同时在中文字符串前添加“N”前缀:
INSERT INTO charset_test VALUES(
2,'中',N'中');
--NVARCHAR2列中的中文不再是乱码了
SELECT * FROM charset_test WHERE id=
2;
2 ? 中
这个配置起到的作用是这样的:在INSERT语句从客户端传输到服务器端之前,Oracle SQL Developer检测(实际上是JDBC检测)语句,如果发现“N”前缀,则事先将这部分的字符串按UTF-16进行编码得到16进制串。也就是相当于执行了这个命令:
INSERT INTO charset_test VALUES(2,’中’,UNISTR('\4e2d'));
C#不需要做特殊的配置来让NVARCHAR2正常工作,只需要在执行INSERT时使用参数并选择正确的参数类型选:
cmd.CommandText = "insert into charset_test values(3,:vc,:nvc)";
OracleParameter p1 = new OracleParameter("vc", OracleDbType.Varchar2);
OracleParameter p2 = new OracleParameter("nvc", OracleDbType.NVarchar2);
p1.Value = "中";
p2.Value = "中";
cmd.Parameters.Add(p1);
cmd.Parameters.Add(p2);
cmd.ExecuteNonQuery();
4.客户端的NLS_LANG设置及编码转换
前面我说过Oracle SQL Developer忽略客户端NLS_LANG设置,那么对于其它的工具呢?(这里我们主要关注字符集及编码,不讨论NLS_LANG对日期格式、排序方式、数字显示格式等等的影响)
ü SQLPLUS,插入与查询都依赖于客户端NLS_LANG设置。通常,客户端NLS_LANG设置要与当前的OEM Codepage一致,比如US8PC437。
ü PL/SQL Developer,插入与查询都依赖于客户端NLS_LANG设置。通常,客户端NLS_LANG设置要与数据库字符集一致。
使用SQLPLUS可以清晰地看到Oracle编码转换的过程:
1) 在Oracle客户端向服务器端提交SQL语句时,Oracle客户端根据NLS_LANG和数据库字符集,对从应用程序接传送过来的字符串编码进行转换处理。如果NLS_LANG与数据库字符集相同,不作转换,否则要转换成数据库字符集并传送到服务器。服务器在接收到字符串编码之后,对于普通的CHAR或VARCHAR2类型,直接存储;对于NCHAR或NVARCHAR2类型,服务器端将其转换为国家字符集再存储。

2) 在查询数据时,服务器端原样返回存储在库中的数据,由客户端根据返回的元数据中的字符集信息与NLS_LANG和NLS_NCHAR的设置进行比较(如果NLS_NCHAR没有设置,则其默认值为NLS_LANG中的字符集设置),如果元数据中的字符集信息与客户端设置一致,不进行转换,否则要进行转换。国家字符集的转换根据NLS_NCHAR设置进行转换。

这里我也举几个使用SQLPLUS的测试例子,分别在A、B两库执行相同的语句,然后通过网络抓包查看从Oracle client传输到服务器的具体内容。
例1 客户端NLS_LANG:WE8MSWIN1252
SQL命令:insert into charset_test values(1,'?',null);
网络抓包(A库,数据库字符集为WE8MSWIN1252):91
解释:由于应用程序(即SQLPLUS)使用的编码是Codepage437,所以?的编码是91。当91被传给Oracle client后,Oracle client根据NLS_LANG误判其使用的编码是Codepage1252,又由于NLS_LANG设置与数据库字符集一致,于是Oracle client不进行编码转换,91被直接传给服务器并存储,考虑到数据库字符集是Codepage1252,很显然91是错误的数据(字符[?]在Codepage1252下的编码是E6,而非91)。
这个错误导致了一个有趣的现象,那就是在同一个客户端使用SQLPLUS查询居然可以看到正确字符[?],这是由于SELECT的时候91也被直接返回,并且在Oracle client也不进行编码转换而是直接传给了应用程序,恰巧应用程序根据自己使用的编码可以正确解析91。但是换一个客户端机器,或者换一个客户端工具都可能得到不一样的查询结果。
网络抓包(B库,数据库字符集为AL32UTF8):E2 80 98
解释:由于应用程序(即SQLPLUS)使用的编码是Codepage437,所以?的编码是91。当91被传给Oracle client后,Oracle client根据NLS_LANG误判其使用的编码是Codepage1252,而91在Codepage1252中对应的是字符[‘],根据Codepage1252到数据字符集UTF8的转换,最终转换成了E2 80 98,即UTF8下的[‘]。
例2客户端NLS_LANG:US7ASCII
SQL命令:insert into charset_test values(1,'?',null);
网络抓包(A库):BF
解释:由于应用程序(即SQLPLUS)使用的编码是Codepage437,所以?的编码是91。当91被传给Oracle client后,Oracle client根据NLS_LANG误判其使用的编码是ASCII,而91在ASCII中是无效编码,根据ASCII到数据字符集Codepage1252的转换,最终转换成了BF,BF是Codepage1252遇到无效编码时使用的默认替换编码。
网络抓包(B库): EF BF BD
解释:由于应用程序(即SQLPLUS)使用的编码是Codepage437,所以?的编码是91。当91被传给Oracle client后,Oracle client根据NLS_LANG误判其使用的编码是ASCII,而91在ASCII中是无效编码,根据ASCII到数据字符集UTF8的转换,最终转换成了EF BF BD,EF BF BD是UTF8遇到无效编码时使用的默认替换编码。
例3客户端NLS_LANG:US8PC437
SQL命令:insert into charset_test values(1,'?',null);
网络抓包(A库):E6
解释:E6是字符[?]的正确的Codepage1252编码,此次由于应用程序(即SQLPLUS)使用的是Codepage437,Oracle client从NLS_LANG获得的编码信息也是Codepage437,于是进行了正确的编码转换。
网络抓包(B库):C3 A6
解释:C3 A6是字符[?]的正确的UTF8编码,此次由于应用程序(即SQLPLUS)使用的是Codepage437,Oracle client从NLS_LANG获得的编码信息也是Codepage437,于是进行了正确的编码转换。
我觉得,只有SQLPLUS的日子总是那么美好,一切看起来既合理又可解释。当其它工具出现之后,世界就变