NLS_LANG引起的SQLPLUS乱码和length长度不正确.
创建一个实验表语句如下
SQL> create table test(id number,name varchar2(10));
当我们在SQLPLUS里面敲入下面的语句并回车执行的时候,SQLPLUS传到Oracle服务器端的数据实际上是经过OS系统默认编码后的字节流.
而NLS_LANG则用来告诉Oracle服务器端这个字节流的编码格式。
insert into test values(1,'好');
1. 当NLS_LANG所指定的编码和数据库编码一致时,这些字节数据部做任何转换,直接存入数据库。
2. 当NLS_LANG所指定的编码和数据库编码不一致时,则oralce在服务器端先用收到的数据按NLS_LANG所指定的编码进行解析,得到原始数据,
然后再按数据库编码存储原始数据。
3. 如果NLS_LANG指定的编码格式和OS系统的编码方式不一致,将出现张冠李戴的现象导致乱码。
实验一
1. 系统编码 ZHS16GBK (CHCP 命令可以看系统默认编码,936表示ZHS16GBK)
C:\Documents and Settings\Administrator>chcp
活动的代码页: 936
2. Database编码 AL32UTF8;
3. SQLPLUS客户端设置. set NLS_LANG=American_America.ZHS16GBK
下面显示了数据库和session的NLS 参数.
SQL> select * from nls_database_parameters;
PARAMETER VALUE
------------------------------------------------------------ -----------------------------------------
NLS_LANGUAGE AMERICAN
NLS_TERRITORY AMERICA
NLS_CURRENCY $
NLS_ISO_CURRENCY AMERICA
NLS_NUMERIC_CHARACTERS .,
NLS_CHARACTERSET AL32UTF8
NLS_CALENDAR GREGORIAN
NLS_DATE_FORMAT DD-MON-RR
NLS_DATE_LANGUAGE AMERICAN
NLS_SORT BINARY
NLS_TIME_FORMAT HH.MI.SSXFF AM
PARAMETER VALUE
------------------------------------------------------------ -----------------------------------------
NLS_TIMESTAMP_FORMAT DD-MON-RR HH.MI.SSXFF AM
NLS_TIME_TZ_FORMAT HH.MI.SSXFF AM TZR
NLS_TIMESTAMP_TZ_FORMAT DD-MON-RR HH.MI.SSXFF AM TZR
NLS_DUAL_CURRENCY $
NLS_COMP BINARY
NLS_LENGTH_SEMANTICS BYTE
NLS_NCHAR_CONV_EXCP FALSE
NLS_NCHAR_CHARACTERSET AL16UTF16
NLS_RDBMS_VERSION 11.2.0.1.0
SQL> select * from nls_session_parameters;
PARAMETER VALUE
------------------------------ ------------------------------
NLS_LANGUAGE AMERICAN
NLS_TERRITORY AMERICA
NLS_CURRENCY $
NLS_ISO_CURRENCY AMERICA
NLS_NUMERIC_CHARACTERS .,
NLS_CALENDAR GREGORIAN
NLS_DATE_FORMAT DD-MON-RR
NLS_DATE_LANGUAGE AMERICAN
NLS_SORT BINARY
NLS_TIME_FORMAT HH.MI.SSXFF AM
NLS_TIMESTAMP_FORMAT DD-MON-RR HH.MI.SSXFF AM
PARAMETER VALUE
------------------------------ ------------------------------
NLS_TIME_TZ_FORMAT HH.MI.SSXFF AM TZR
NLS_TIMESTAMP_TZ_FORMAT DD-MON-RR HH.MI.SSXFF AM TZR
NLS_DUAL_CURRENCY $
NLS_COMP BINARY
NLS_LENGTH_SEMANTICS BYTE
NLS_NCHAR_CONV_EXCP FALSE
17 rows selected.
[D:\app\Administrator\product\11.2.0\dbhome_1]set NLS_LANG=American_America.AL32UTF8
[D:\app\Administrator\product\11.2.0\dbhome_1]sqlplus /@test as sysdba
SQL*Plus: Release 11.2.0.1.0 Production on Wed Dec 11 21:16:47 2013
Copyright (c) 1982, 2010, Oracle. All rights reserved.
Connected to:
Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options
SQL> desc test
Name Null? Type
----------------------------------------- -------- ----------------------------
ID NUMBER
NAME VARCHAR2(10)
SQL> insert into test values(1,'好');
ERROR:
ORA-01756: quoted string not properly terminated
这里居然直接报语法错误,大家可能会认为是单引号字符不对导致第二个引号没有认出来,其实不是这个原因.
汉字"好" 通过系统的ZHS16GBK编码后是两个字节"BAC3",而此时NLS_lang 指定的编码UTF8和服务器是一致的。
汉字"好"各编码如下 查询地址 http://bianma.supfree.net/
GBK编码 unicode编码 大五码(Big5) 区位码(GB2312) utf8编码 10位unicode
BAC3 597D A66E 2635 %E5%A5%BD 22909
当数据库收到这条SQL后没有对汉字"好"的编码进行转换,收到的东西可以理解成下面的形式。
insert into test values(1,'BAC3')
当数据库开始解析语句的时候,用UTF-8去解析参数(之所以这里会用UTF8解析是因为到数据库端的SQL如果含有直接字符,那么字符都应该是已经转码完成的,而这就是NLS_LANG应该保证的),
当碰到第一个16进制数据"27"时,它知道这个单引号表示字符串开始,并不是数据,
然后往下找到16进制数据"BA",在UTF8编码规则里,第一个字节最高位连续1的个数表示这个UTF8编码的字节总数。
BA的二进制"10111010",高位只有一个1,那么表示本字节就是一个完整的UTF8编码,
C3的二进制"11000011",高位有两个连续1,表示后面连续的两个字符是一个完整的UTF8编码
于是11000011 (C3 ) + 00100111 (27) 被当成了一个完整的UTF8编码,最后解析器发现没有字符结束的单引号,于是报错ORA-01756: quoted string not properly terminated
下面是16进制对应的二进制码。
27 BA C3 27
00100111 10111010 11000011 00100111
如果以上推断正确,那么我们在后面再加上一个单引号 就应该是可以保存的。 SQL如下,虽然很怪异,但是确实是成功保存了。
SQL> insert into test values(1,'好'');
1 row created.
SQL> select * from test;
ID NAME
---------- ----------
1 好'
同理我们也应该可以找到一些汉字,经过ZHS16GBK编码后,每个字节的高位都只有一个连续的1,然后根据UTF8规则,那么单个字节就是一个UTF8编码,这样就不会报错了。
汉字 "撼"各编码如下 查询地址 http://bianma.supfree.net/
GBK编码 unicode编码 大五码(Big5) 区位码(GB2312) utf8编码 10位unicode
BAB3 64BC BED9 2619 %E6%92%BC 25788
BAB3对应的二进制 10111010+10110011,经过尝试,插入成功。
BA B3
10111010 10110011
SQL> insert into test values(1,'撼')
1 row created.
下面我们把数据和dump显示出来,可以看见"撼"对应的编码原封不动的存储了.
SQL> select name,dump(name,1016) as dump from test;
NAME DUMP
---------- --------------------------------------------------
好' Typ=1 Len=3 CharacterSet=AL32UTF8: ba,c3,27
撼 Typ=1 Len=2 CharacterSet=AL32UTF8: ba,b3
我们都知道汉字用UTF8编码是3个字节,而这里只有两个字节,
显然是不对的,可是为什么我们看到的数据是正确的呢?原因如下.
数据库在返回数据的时候,会先比对数据库的存储格式和客户端环境变量NLS_LANG指定的格式,发现两边是一样的UTF8,所以不进行任何转换,
数据原封不动的传回来。收到字符后进行解码和显示到屏幕上,这两个过程是由OS系来做的,所以OS按照它的编码ZHS16GBK解码收到的数据,
刚刚好还原出了最原始的数据。其实这是一种负负得正的巧合,另外开一个Session,把NLS_LANG的编码格式设置为ZHS16GBK(只要是非UTF8都行)
查询数据的时候就会看到乱码.
C:\Documents and Settings\Administrator>set NLS_LANG=American_America.ZHS16GBK
C:\Documents and Settings\Administrator>sqlplus /@test as sysdba
SQL*Plus: Release 11.2.0.1.0 Production on Wed Dec 11 23:50:36 2013
Copyright (c) 1982, 2010, Oracle. All rights reserved.
Connected to:
Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options
SQL> select * from test;
ID NAME
---------- --------------------
1 ??
1 ??
实验二
1. 系统编码 ZHS16GBK (CHCP 命令可以看系统默认编码,936表示ZHS16GBK) C:\Documents and Settings\Administrator>chcp 活动的代码页: 936
2. Database编码 AL32UTF8;
3. SQLPLUS客户端设置. set NLS_LANG=American_America.WE8ISO8859P1
数据库和session的NLS参数同上
[D:\app\Administrator\product\11.2.0\dbhome_1]set NLS_LANG=American_America.WE8ISO8859P1
[D:\app\Administrator\product\11.2.0\dbhome_1]sqlplus /@test as sysdba
SQL*Plus: Release 11.2.0.1.0 Production on Thu Dec 12 00:02:49 2013
Copyright (c) 1982, 2010, Oracle. All rights reserved.
Connected to:
Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options
SQL> select * from test;
ID NAME
---------- ----------
1 靠
1 靠
前一个实验的数据在当NLS_LANG编码不是UTF8是再次显示为乱码,为了不影响本次实验,我们新建一个表test2如下.
SQL> create table test2 (id number, des varchar2(10));
Table created.
SQL> insert into test2 values (1,'宋春风'); ---试图插入一行数据,报字段DES长度为10不够,插入的数据要12.
insert into test2 values (1,'宋春风')
*
ERROR at line 1:
ORA-12899: value too large for column "SYS"."TEST2"."DES" (actual: 12, maximum:
10)
先不管上面的错误,继续执行下面的SQL.
SQL> select length('宋春风'),lengthb('宋春风') ,length('宋'),lengthb('宋') from dual;
LENGTH('宋春非') LENGTHB('宋春非') LENGTH('宋') LENGTHB('宋')
---------------- ----------------- ------------ -------------
6 12 2 4
我们发现"宋春风"的byte长度为12,单个汉字的byte长度为4,字符长度居然为2,真是怪异。
汉字"宋"的ZHS16GBK编码为"CBCE",而当前NLS_LANG指定的编码格式为WE8ISO8859P1,这是个单字节字符编码。
宋
GBK编码 unicode编码 大五码(Big5) 区位码(GB2312) utf8编码 10位unicode
CBCE 5B8B A7BA 4346 %E5%AE%8B 23435
所以"CBCE"传给Oracle服务端的时候,一个字节被当成一个字符,因此ZHS16GBK编码的"CBCE"就被服务器误认为是WE8ISO8859P1编码"CB","CE"对应的两个字符,然后返回的汉字"宋"的字符长度就是2.
如果是统计字符长度比如length函数,那么服务端收到的时候没必要转换成UTF8,因为数据库存的是UTF8编码的字节,当统计字符数目的时候,还是要用UTF8解码成字符,转换是多此一举而已。
WE8ISO8859P1编码里
"CB"对应 Ë ,后者在UTF8里的编码为"C38B"
"CE"对应 Î ,后者在UTF8里的编码为"C38E" 查编码可以用网页 http://bianma.911cha.com/
所以最后存储的是4个字节 C3 8B C3 8E,这也就是lengthb返回4的原因了。下面我们来仔细看看。
SQL> insert into test2 values (1,'宋');
1 row created.
SQL> select dump(des,1016) from test2;
DUMP(DES,1016)
-------------------------------------------------
Typ=1 Len=4 CharacterSet=AL32UTF8: c3,8b,c3,8e
结论:只有当NLS_LANG所指定的编码格式与OS系统的编码格式一致时,SQLPLUS 插入的数据(尤其是汉字)的编码才是正确的,才能保证数据库里数据的字节码与数据库编码对应.
SQLPLUS 乱码问题的更本解决办法就是设置正确的NLS_LANG.
NLS_LANG引起的SQLPLUS乱码和length长度不正确.的更多相关文章
- sqlplus乱码
使用SecureCRT或是pietty_ch连接到一台安装有Oracle DB 10g的RHEL4.2的机器,linux使用的shell是默认的bash. 在bash提示符下,使用Del键或者Back ...
- Python3 tkinter基础 Scale orient 横竖 resolution单步步长 length 长度 tickinterval 指示刻度
Python : 3.7.0 OS : Ubuntu 18.04.1 LTS IDE : PyCharm 2018.2.4 Conda ...
- 限制UITextField的输入字数(长度)最正确的方法
在开发中, 有些时候会碰到这样的需求: 希望输入框有最大字数限制. 比如, 用户昵称长度限制, 评论最大字数限制.所以通过相关测试和浏览文章,使用下面的方法可以基本解决问题. 在viewDidLoad ...
- Windows转到linux中,文件乱码,文件编码转换 & 解决sqlplus连接oracle乱码
转载:http://www.cnblogs.com/wanyao/p/3399269.html 最近,学习又重新开始Linux学习,所以一直在Centos中,昨天一朋友把他在Windows下写的C程序 ...
- linux oracle sqlplus中文乱码解决
在oracle用户的~/.bash_profile中添加 NLS_LANG="SIMPLIFIED CHINESE"_CHINA.ZHS16GBKexport NLS_LANG 然 ...
- 【线性代数】1-2:点乘和长度(Dot Products and Length)
title: [线性代数]1-2:点乘和长度(Dot Products and Length) toc: true categories: Mathematic Linear Algebra date ...
- Oracle数据库在plsql中文乱码,显示问号????
1.错误显示结果: 2.错误的原因: SqlPlus乱码与操作系统用户中的NLS_LANG这个环境变量有关系,如果这个与数据库字符集不一致的话就会产生乱码 3.解决方法1:(配置环境变量) 1.sel ...
- java String长度与varchar长度匹配理解(字符和字节长度理解)
java String长度与varchar长度匹配理解(字符和字节长度理解) string中的length()长度,返回的是char的数量,每个char可以存储世界上任何类型的文字和字符,一个char ...
- split拆分数组长度问题
package test; public class test1_format { public static void main(String[] args) { System.out.printl ...
随机推荐
- (转)Linux ldconfig 与 ldd指令
原文:https://blog.csdn.net/iamzhangzhuping/article/details/49203981 一.ldconfig ldconfig是一个动态链接库管理命令,为了 ...
- openssl生成RSA格式的公私钥,并转为pkcs8格式
第一步:生成私钥,这里我们指定私钥的长度为2048 openssl genrsa -out rsa_private_key.pem 2048 第二步:根据私钥生成对应的公钥: openssl rsa ...
- springclould nginx转发 websocket400报错问题
之前一直找原因一直围绕着nginx转发的问题 说头信息没设置全 然后nginx配置文件上加了这些 #http块加以下几行: map $http_upgrade $connection_upgrade ...
- springboot-12-自定义拦截器的配置interceptor
springmvc中拦截器的概念已经被弱化了, springboot中使用的也不甚广泛, 通常在用户登录等方面仍有用处 创建拦截器步骤: , 创建拦截器类继承HandlerInterceptor , ...
- Go RabbitMQ (一)
RabbitMQ 简介 RabbitMQ是一个消息代理,用来负责接收和转发消息. 术语 生产者:生产者是负责发送消息的 队列:队列是RabbitMQ用来存储消息的,受主机内存和磁盘大小的限制,本质上是 ...
- c# 获取应用程序exe文件路径及退出应用程序的几种方法
this.GetType().Assembly.Location; Application.ExecutablePath; Application.StartupPath:和上面的相比缺少可执行文件 ...
- C#字符串中的中文逗号转英文逗号
public static string ToDBC(string input) { char[] c = input.ToCharArray(); for (int i = 0; i < c. ...
- Angular的第一个组件
创建组件 在vscode的命令窗口输入: ng generate component login --inline-template --inline-style 或者简写 ng g c login ...
- Java基础教程(9)--流程控制
一.分支结构 1.if语句 if语句会与其后的第一条语句或代码块结合,且只有当判断条件为true时才执行语句或代码块.例如,自行车只有在运动的时候才可以减速,就像下面这样: void applyB ...
- 招新系统(jsp+servlet,实现简略前端网页注册登录+后台增删改查,分学生和管理员,Java语言,mysql数据库连接,tomcat服务器)
生活不只是眼前的苟且,还有诗和远方. 架构说明: 要求是采用MVC模式,所以分了下面的几个包,但是由于是第一次写,可能分的也不是很清楚: 这个是后台部分的架构: 这个是前端的的展示: (那个StuLo ...