本文为Oracle NLS_LANG 常见问题的阅读笔记,只记录其中要点,并提供必要解读。
我读的英文版,择要点翻译为中文。
NLS表示 National Language Suppor。详见Oracle官方文档:Database Globalization Support Guide。
NLS_LANG 参数基础知识
区域设置(locale)是一组与特定语言和国家/地区相对应的信息,用于满足语言和文化要求。传统上,与区域设置相关的数据支持日期、时间、数字和货币等的格式化和解析。
设置 NLS_LANG 环境参数是指定 Oracle 软件区域设置行为的最简单方法。
NLS_LANG 参数包含三个组成部分:语言、地域和字符集。
bash
NLS_LANG = language_territory.charset
这三个部分都是可选的,没有设置的部分使用默认值。其中:
- 语言:指定约定,例如用于 Oracle 消息、排序、日期名称和月份名称的语言。
- 地域:指定约定,例如默认日期、货币和数字格式。
- 字符集:指定客户端应用程序使用的字符集。
以下命令列出所有可用的locale:
bash
locale -a
但是,非常重要的一点是,修改locale的设置不一定会影响数据库中语言,领土和客户端字符集的设置。JAVA客户端如SQLcl会受影响,但OCI客户端SQL Plus这不会受影响。所以最好的方式还是显示的设置NLS_LANG。
常见的 NLS_LANG 误区
将 NLS_LANG 设置为数据库的字符集可能正确,但通常不正确。
这点不太认同,因为数据库服务器建议字符集为AL32UTF8,即Unicode。客户端也是建议设为Unicode。例如,比较常见的设置为:
bash
NLS_LANG=AMERICAN_AMERICA.AL32UTF8
如果 Oracle 未设置 NLS_LANG ,则其默认值为 AMERICAN_AMERICA.US7ASCII 。
这点说明了为何总是要显式的设置NLS_LANG。NLS_LANG的配置并非来自于OS的locale设置:
bash
[oracle@xy23ai ~]$ locale
LANG=en_US.utf-8
LC_CTYPE="en_US.utf-8"
LC_NUMERIC="en_US.utf-8"
LC_TIME="en_US.utf-8"
LC_COLLATE="en_US.utf-8"
LC_MONETARY="en_US.utf-8"
LC_MESSAGES="en_US.utf-8"
LC_PAPER="en_US.utf-8"
LC_NAME="en_US.utf-8"
LC_ADDRESS="en_US.utf-8"
LC_TELEPHONE="en_US.utf-8"
LC_MEASUREMENT="en_US.utf-8"
LC_IDENTIFICATION="en_US.utf-8"
LC_ALL=
检查当前的 NLS_LANG 设置
OS层面:
bash
echo $NLS_LANG
注意,以下输出中的字符集是数据库字符集,和客户端字符集无关:
sql
SQL> SELECT USERENV ('language') FROM DUAL;
USERENV('LANGUAGE')
____________________________
AMERICAN_AMERICA.AL32UTF8
例如,在此设置下,欧元符号显示仍不正确:
bash
SQL> select '€' from dual;
'???'
---------
???
NLS_LANG相关参数的优先级
可以在三个级别设置 NLS 参数,优先级从低到高为:数据库、实例和会话。
三个层面的设置可以从以下系统表中查询,优先级从高到低:
- NLS_SESSION_PARAMETERS
- NLS_INSTANCE_PARAMETERS
- NLS_DATABASE_PARAMETERS
以下是从SQLcl中查询的,也就是JAVA客户端:
bash
col parameter for a30
col value for a30
set pages 9999
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
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.
SQL> select * from NLS_INSTANCE_PARAMETERS;
PARAMETER VALUE
__________________________ ___________
NLS_LANGUAGE AMERICAN
NLS_TERRITORY AMERICA
NLS_SORT
NLS_DATE_LANGUAGE
NLS_DATE_FORMAT
NLS_CURRENCY
NLS_NUMERIC_CHARACTERS
NLS_ISO_CURRENCY
NLS_CALENDAR
NLS_TIME_FORMAT
NLS_TIMESTAMP_FORMAT
NLS_TIME_TZ_FORMAT
NLS_TIMESTAMP_TZ_FORMAT
NLS_DUAL_CURRENCY
NLS_COMP BINARY
NLS_LENGTH_SEMANTICS BYTE
NLS_NCHAR_CONV_EXCP FALSE
17 rows selected.
SQL> select * from NLS_DATABASE_PARAMETERS;
PARAMETER VALUE
__________________________ _______________________________
NLS_RDBMS_VERSION 23.0.0.0.0
NLS_NCHAR_CONV_EXCP FALSE
NLS_LENGTH_SEMANTICS BYTE
NLS_COMP BINARY
NLS_DUAL_CURRENCY $
NLS_TIMESTAMP_TZ_FORMAT DD-MON-RR HH.MI.SSXFF AM TZR
NLS_TIME_TZ_FORMAT HH.MI.SSXFF AM TZR
NLS_TIMESTAMP_FORMAT DD-MON-RR HH.MI.SSXFF AM
NLS_TIME_FORMAT HH.MI.SSXFF AM
NLS_SORT BINARY
NLS_DATE_LANGUAGE AMERICAN
NLS_DATE_FORMAT DD-MON-RR
NLS_CALENDAR GREGORIAN
NLS_NUMERIC_CHARACTERS .,
NLS_NCHAR_CHARACTERSET AL16UTF16
NLS_CHARACTERSET AL32UTF8
NLS_ISO_CURRENCY AMERICA
NLS_CURRENCY $
NLS_TERRITORY AMERICA
NLS_LANGUAGE AMERICAN
20 rows selected.
以下是从SQL Plus中查询的结果,也就是OCI客户端:
sql
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
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.
SQL> select * from NLS_INSTANCE_PARAMETERS;
PARAMETER VALUE
------------------------------ ------------------------------
NLS_LANGUAGE AMERICAN
NLS_TERRITORY AMERICA
NLS_SORT
NLS_DATE_LANGUAGE
NLS_DATE_FORMAT
NLS_CURRENCY
NLS_NUMERIC_CHARACTERS
NLS_ISO_CURRENCY
NLS_CALENDAR
NLS_TIME_FORMAT
NLS_TIMESTAMP_FORMAT
NLS_TIME_TZ_FORMAT
NLS_TIMESTAMP_TZ_FORMAT
NLS_DUAL_CURRENCY
NLS_COMP BINARY
NLS_LENGTH_SEMANTICS BYTE
NLS_NCHAR_CONV_EXCP FALSE
17 rows selected.
SQL> select * from NLS_DATABASE_PARAMETERS;
PARAMETER VALUE
------------------------------ ------------------------------
NLS_RDBMS_VERSION 23.0.0.0.0
NLS_NCHAR_CONV_EXCP FALSE
NLS_LENGTH_SEMANTICS BYTE
NLS_COMP BINARY
NLS_DUAL_CURRENCY $
NLS_TIMESTAMP_TZ_FORMAT DD-MON-RR HH.MI.SSXFF AM TZR
NLS_TIME_TZ_FORMAT HH.MI.SSXFF AM TZR
NLS_TIMESTAMP_FORMAT DD-MON-RR HH.MI.SSXFF AM
NLS_TIME_FORMAT HH.MI.SSXFF AM
NLS_SORT BINARY
NLS_DATE_LANGUAGE AMERICAN
NLS_DATE_FORMAT DD-MON-RR
NLS_CALENDAR GREGORIAN
NLS_NUMERIC_CHARACTERS .,
NLS_NCHAR_CHARACTERSET AL16UTF16
NLS_CHARACTERSET AL32UTF8
NLS_ISO_CURRENCY AMERICA
NLS_CURRENCY $
NLS_TERRITORY AMERICA
NLS_LANGUAGE AMERICAN
20 rows selected.
其实输出是完全相同的,但是以下命令的输出并不相同:
sql
-- 此时,NLS_LANG未设置,操作系统为Linux。也未用ALTER SESSION显式设置
-- SQLcl中,正确显示
SQL> select '€' from dual;
'€'
______
€
-- SQL*Plus中,错误显示
SQL> select '€' from dual;
'???'
---------
???
这至少说明了两点:
- NLS_CHARACTERSET是数据库字符集,和客户端字符集无关
- 客户端字符集的取值与客户端是Java还是OCI有关。
会话参数
数据库会话层面。以下只有LANGUAGE和TERRITORY,并没有字符集:
sql
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
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.
这些参数或者是继承自NLS_LANG的设置,或者是通过ALTER SESSION设置。
如果未设置 NLS_LANG,则默认值为<Language>_<Territory>.US7ASCII
,
<Language>_<Territory>
部分的值同NLS_INSTANCE_PARAMETERS 中的值。
语言和领土的设置会受OS locale的设置影响吗?
实例参数
sql
SELECT * from NLS_INSTANCE_PARAMETERS;
这些是数据库启动时 init.ora 文件中的设置,或通过ALTER SYSTEM设置。
Oracle 强烈建议您将客户端的 NLS_LANG 至少设置为:
bash
NLS_LANG=.<客户端字符集>
数据库参数
sql
SELECT * from NLS_DATABASE_PARAMETERS;
如果在数据库创建期间未在 init.ora 中明确设置任何参数,则默认为 AMERICAN_AMERICA。
数据库创建后无法更改这些参数。
其他参考SQL:
bash
SQL> SELECT name,value$ from sys.props$ where name like '%NLS%';
NAME VALUE$
__________________________ _______________________________
NLS_RDBMS_VERSION 23.0.0.0.0
NLS_NCHAR_CHARACTERSET AL16UTF16
NLS_NCHAR_CONV_EXCP FALSE
NLS_LENGTH_SEMANTICS BYTE
NLS_COMP BINARY
NLS_DUAL_CURRENCY $
NLS_TIMESTAMP_TZ_FORMAT DD-MON-RR HH.MI.SSXFF AM TZR
NLS_TIME_TZ_FORMAT HH.MI.SSXFF AM TZR
NLS_TIMESTAMP_FORMAT DD-MON-RR HH.MI.SSXFF AM
NLS_TIME_FORMAT HH.MI.SSXFF AM
NLS_SORT BINARY
NLS_DATE_LANGUAGE AMERICAN
NLS_DATE_FORMAT DD-MON-RR
NLS_CALENDAR GREGORIAN
NLS_CHARACTERSET AL32UTF8
NLS_NUMERIC_CHARACTERS .,
NLS_ISO_CURRENCY AMERICA
NLS_CURRENCY $
NLS_TERRITORY AMERICA
NLS_LANGUAGE AMERICAN
20 rows selected.
SQL> SELECT * from v$nls_parameters;
PARAMETER VALUE CON_ID
__________________________ _______________________________ _________
NLS_LANGUAGE AMERICAN 3
NLS_TERRITORY AMERICA 3
NLS_CURRENCY $ 3
NLS_ISO_CURRENCY AMERICA 3
NLS_NUMERIC_CHARACTERS ., 3
NLS_CALENDAR GREGORIAN 3
NLS_DATE_FORMAT DD-MON-RR 3
NLS_DATE_LANGUAGE AMERICAN 3
NLS_CHARACTERSET AL32UTF8 3
NLS_SORT BINARY 3
NLS_TIME_FORMAT HH.MI.SSXFF AM 3
NLS_TIMESTAMP_FORMAT DD-MON-RR HH.MI.SSXFF AM 3
NLS_TIME_TZ_FORMAT HH.MI.SSXFF AM TZR 3
NLS_TIMESTAMP_TZ_FORMAT DD-MON-RR HH.MI.SSXFF AM TZR 3
NLS_DUAL_CURRENCY $ 3
NLS_NCHAR_CHARACTERSET AL16UTF16 3
NLS_COMP BINARY 3
NLS_LENGTH_SEMANTICS BYTE 3
NLS_NCHAR_CONV_EXCP FALSE 3
19 rows selected.
SQL> SELECT name,value from v$parameter where name like '%nls%';
NAME VALUE
__________________________ _______________________________
nls_language AMERICAN
nls_territory AMERICA
nls_sort BINARY
nls_date_language AMERICAN
nls_date_format DD-MON-RR
nls_currency $
nls_numeric_characters .,
nls_iso_currency AMERICA
nls_calendar GREGORIAN
nls_time_format HH.MI.SSXFF AM
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
17 rows selected.
SQL> SELECT userenv ('language') from dual;
USERENV('LANGUAGE')
____________________________
AMERICAN_AMERICA.AL32UTF8
SQL> SELECT userenv ('lang') from dual;
USERENV('LANG')
__________________
US
SQL> show parameter nls%
NAME TYPE VALUE
----------------------- ------ ----------------------------
nls_calendar string GREGORIAN
nls_comp string BINARY
nls_currency string $
nls_date_format string DD-MON-RR
nls_date_language string AMERICAN
nls_dual_currency string $
nls_iso_currency string AMERICA
nls_language string AMERICAN
nls_length_semantics string BYTE
nls_nchar_conv_excp string FALSE
nls_numeric_characters string .,
nls_sort string BINARY
nls_territory string AMERICA
nls_time_format string HH.MI.SSXFF AM
nls_time_tz_format string HH.MI.SSXFF AM TZR
nls_timestamp_format string DD-MON-RR HH.MI.SSXFF AM
nls_timestamp_tz_format string DD-MON-RR HH.MI.SSXFF AM TZR
错误 NLS_LANG 设置示例
非常重要的一点(如前所述):
当客户端 NLS_LANG 字符集设置为与数据库字符集相同 的值时,Oracle 会假定发送或接收的数据采用相同的(正确的)编码 ,因此出于性能原因,可能不会进行任何转换或验证。数据只是按照客户端传递的格式逐位存储。
如何为 UNIX 正确设置 NLS_LANG
使用locale命令查看:
bash
$ locale LC_CTYPE | head
upper;lower;alpha;digit;xdigit;space;print;graph;blank;cntrl;punct;alnum;combining;combining_level3
toupper;tolower;totitle
16
6
UTF-8
72
86
1
0
1
因此,NLS_LANG的第三部分(字符集部分)应设为UTF-8:
bash
NLS_LANG=AMERICAN_AMERICA.AL32UTF8
如何查看数据库中实际存储的内容?
要查找数据库中存储的字符的实际数值,请使用 dump 命令:
该函数调用的语法如下:
sql
DUMP( <value> [, <format> [, <offset> [, <length> ] ] ] )
DUMP 返回一个 VARCHAR2 值,其中包含 expr 的数据类型代码、长度(以字节为单位)和内部表示形式。返回的结果始终采用数据库字符集。详见帮助。
默认情况下,返回值不包含字符集信息。要检索 expr 的字符集名称,请将 1000 添加到上述任意格式值中。例如,return_fmt 为 1008 时,将以八进制形式返回结果,并提供 expr 的字符集名称。
字符集转换在哪里完成?
出于性能考虑,通常会在客户端进行字符集转换。若数据库使用的字符集不被客户端识别,则转换将在服务器端完成。
如何检查由UNIX操作系统管理的代码点?
使用"od"命令:
bash
$ echo -n "€"|od -t x1
0000000 e2 82 ac
0000003
$ printf € | hexdump
0000000 82e2 00ac
0000003
字符€的UTF8编码为0xE2 0x82 0xAC,详见这里。
可以用printf打印:
bash
## UTF-8 编码
$ printf "\xE2\x82\xAC"
€
$ echo -e "\xE2\x82\xAC"
€
## UTF-32 编码
$ echo -e '\U000020AC'
€
## UTF-16 编码
$ echo -e '\U20AC'
€
$ echo -e $'\u4F60\u597D'
你好
注意,以上的-U不能换成-u,因为-u只支持BMP(Basic Multilingual Plane)字符,而-U支持BMP之外的字符。
您可以使用 Locale Builder 或下方链接来验证您的本地代码页和 NLS_LANG 设置是否正确对应:
那么 SQL*Loader、Import、Export、实用程序等命令行工具怎么样?
Windows下显示当前code page:
bash
C:\>chcp
Active code page: 437
Linux下的命令:
bash
$ locale charmap
UTF-8
在 Oracle9i 及以上,export实用程序始终以数据库的字符集导出用户数据(包括 Unicode 数据)。import实用程序会自动将数据转换为目标数据库的字符集。
也可以显式指定。可以临时将 NLS_LANG 更改为您正在加载的文件的字符集。或使用 .ctl 文件中的 characterset 关键字指定数据文件中数据的字符集。在这种情况下,无论客户端的 NLS_LANG 字符集设置如何,SQL*Loader 都会将数据文件中的数据解释为该字符集。
数据库链接怎么办?
服务器(或客户端)上的 NLS_LANG 对通过数据库链接进行的字符集转换没有影响。Oracle 会将源数据库的字符集转换为目标数据库的字符集(或反向转换)。
什么是字符集或代码页?
字符集只是对符号数值的一种约定。计算机无法识别"A"或"B",它只知道该符号的(二进制)数值,该数值由其操作系统 (OS) 或终端硬件(固件)使用的字符集定义。计算机只能处理数字,因此需要字符集。例如,"ASCII"(旧的 7 位字符集)、"ROMAN8"(UNIX 上的 8 位字符集)或"UTF8"(多字节字符集)。
代码页是 Windows/DOS 编码方案的名称,对于 Oracle NLS,您可以将其视为字符集。您还需要区分字体 (FONT) 和字符集/代码页。操作系统使用字体将数值转换为屏幕上的图形"打印"。Windows 上的 Wingdings 字体就是最好的字体示例,其中"A"在屏幕上不会显示为"A",但对于操作系统而言,数值代表"A"。所以你看不到"A",但在 Windows 系统中,它就是"A",并且会被保存(或使用)为"A"。
为了更好地理解上述解释,只需打开 MS Word,选择 Wingdings 字体,输入你的名字(你会看到符号),然后将其保存为 html 文件。如果你用记事本打开该 html 文件,你会看到在
也有可能你看不到某种字体中你正在使用的代码页中定义的所有符号,这是因为字体的创建者没有为该字体中的所有符号提供图形表示。这就是为什么你有时在更改字体时会在屏幕上看到黑色方块的原因。在 Windows 上,您可以使用"字符映射"工具查看字体中定义的所有符号。
为什么有不同的字符集?
主要有两个原因:
- 过去,供应商为其硬件和软件定义了不同的"字符集",主要是因为没有官方标准。
- 新的字符集被定义来支持新的语言。8 位字符集支持的符号数量有限,因此不同的书面语言会使用不同的字符集。
7 位、8 位和 Unicode 字符集有什么区别?
7 位字符集只能识别 128 个符号 (2^7)
8 位字符集可以识别 256 个符号 (2^8)
Unicode (UTF-8) 是一种多字节字符集。Unicode 能够定义超过一百万个字符。有关 Unicode 的更多信息,请参阅白皮书《Oracle Unicode 数据库支持》。
如何选择正确的数据库字符集?
选择字符集的一个基本考虑因素是确保它能够处理任何需要立即支持以及在不确定的未来支持的语言。另一个容易被忽视的考虑因素是考虑您可能想要使用或与数据库交互的应用程序和技术。
简而言之,UNICODE是最佳选择。新系统请使用UNICODE,老系统请迁移至UNICODE。