结论:
Oracle 11g 的 SQL Loader(sqlldr)命令行输出的语言和字符集受到
NLS_LANG
环境变量控制。
比如:NLS_LANG=SIMPLIFIED CHINESE_CHINA.ZHS16GBK
表示使用简体中文
语言和GBK
编码。
有兴趣可以继续看下面啰嗦的分析过程内容。
(一)现象
程序在Linux
下调用sqlldr时,发现其返回的命令行内容存在乱码。
手动测试sqlldr命令,果然和程序读到的一样,是乱码。
仔细检查发现是GBK
格式,因为Linux下基本都默认UTF8
编码,所以直接终端显示就变乱码了。
$shion@shionwsl ~> sqlldr
SQL*Loader: Release 11.2.0.4.0 - Production on ������ 4�� 27 09:08:30 2023
Copyright (c) 1982, 2011, Oracle and/or its affiliates. All rights reserved.
�÷�: SQLLDR keyword=value [,keyword=value,...]
��Ч�Ĺؼ���:
userid -- ORACLE �û���/����
control -- �����ļ���
log -- ��־�ļ���
bad -- �����ļ���
data -- �����ļ���
discard -- �����ļ���
......
回到Windows
下调用同版本sqlldr,正常显示:
PS C:\Users\Shion> sqlldr
SQL*Loader: Release 11.2.0.4.0 - Production on 星期四 4月 27 09:11:50 2023
Copyright (c) 1982, 2011, Oracle and/or its affiliates. All rights reserved.
用法: SQLLDR keyword=value [,keyword=value,...]
有效的关键字:
userid -- ORACLE 用户名/口令
control -- 控制文件名
log -- 日志文件名
bad -- 错误文件名
data -- 数据文件名
discard -- 废弃文件名
......
(二)分析
(2.1)入库文件字符集?(No)
我们都知道,文件内容如果带有中文是有编码的,所以调用sqlldr入库时,需要指定其编码。
这部分在控制文件.ctl
中完成。
如下例,指定了ZHS16GBK
编码方式:
OPTIONS (skip=0) LOAD DATA CHARACTERSET ZHS16GBK
INFILE 'xxxxx/eps.txt'
APPEND INTO TABLE yyyyy
FIELDS TERMINATED BY X'09'
TRAILING NULLCOLS
(xxxx char(16),yyyy,zzzz,......)
会不会因为导入的文件都是GBK
编码影响了sqlldr的命令行输出呢?当然不是!!!
因为我们只输入了sqlldr命令本身,没有任何参数,中文已经是乱码了。
(2.2)客户端设置?(Yes)
既然Windows和Linux都输出GBK
,而不是尊重操作系统的字符集(编码)……
那么是不是因为同样的客户端设置引起的呢?
两边都设置了:
NLS_LANG=SIMPLIFIED CHINESE_CHINA.ZHS16GBK
尝试将Linux
那边改为SIMPLIFIED CHINESE_CHINA.AL32UTF8
。
再调用sqlldr,果然没有乱码了。但 问题并没有解决!!!
因为Oracle客户端设置是需要和服务端一致的,既然服务端是GBK
而客户端改为了UTF8
,会导致你用SQL选出的中文变成乱码。这是Oracle一个很神奇的设置,它完全不考虑你需要同时连接2个不同字符集服务端的情况。Java已经不再读这个客户端配置,但也引发了其它的问题(之前有遇到过并记录到了文章)。
那怎么办呢?
(2.3)分析NLS_LANG设置
我们来看一下(当然还有很多其它国家和语言/字符集设置):
NLS_LANG=SIMPLIFIED CHINESE_CHINA.ZHS16GBK
NLS_LANG=JAPANESE_JAPAN.AL32UTF8
NLS_LANG=AMERICAN_AMERICA.WE8ISO8859P15
......
NLS_LANG的值,明显分为两部分:
中间用点连接,格式为:NLS_LANG=语言_国家
.
字符集
- 语言和国家
- 字符集(编码方式)
现在的问题是:
- sqlldr的命令行显示,不尊重操作系统字符集,而是采用了NLS_LANG的
字符集
。 - 这个客户端的
字符集
设置又必须和服务端一致不能随便改。 - Linux操作系统字符集是
UTF8
和ORACLE客户端字符集GBK
不一样,导致冲突。
所以?改Linux操作系统字符集?当然不能!!!
随便改操作系统字符集,无异于单表有乱码改整个数据库字符集!!!
。
在生产系统中毫不考虑还其它程序在跑,⚠️属于破坏行为。
量刑标准我不清楚,同时一般人权限也不够。
(三)解决(无奈)
由于Oracle自身的设定的原因,通常情况下Linux的UTF8
和Oracle服务端要求的GBK
编码一定冲突。
用程序读取很简单,创建流读取命令行输出信息时,指定成NLS_LANG同样的编码就OK了。
但是人敲命令行怎么办呢???
只能看乱码么,还是每次设法转成看GBK?
刚才提到了既然分两部分,第二部分不能改,就只能看第一部分了,也就是语言_国家
设置。
那就只能把语言改为任何编码下都能正常显示的语言:英语
了。
NLS_LANG=AMERICAN_AMERICA.ZHS16GBK
谁说米国人不能说中文呢?
设置后如下,同时还解决了翻译成中文后看不懂
的问题(手动狗头)。
$shion@shionwsl ~> sqlldr
SQL*Loader: Release 11.2.0.4.0 - Production on Thu Apr 27 10:01:24 2023
Copyright (c) 1982, 2011, Oracle and/or its affiliates. All rights reserved.
Usage: SQLLDR keyword=value [,keyword=value,...]
Valid Keywords:
userid -- ORACLE username/password
control -- control file name
log -- log file name
bad -- bad file name
data -- data file name
discard -- discard file name
......