聊聊字符串数据长度和nls_length_semantics参数

 

字符串是我们设计数据库经常用到的类型,从传统的ASCII格式到UTF-8格式,不同应用需求对应不同的字符类型和长度配置。针对Oracle而言,最常用的类型无外乎charvarchar2两个基本类型。

对于一些中文应用,设计人员就需要重点关注数据表中字符串长度问题。因为在不同的字符串编码方式下,一个中文字符对应的字符byte长度是不同的。比如,一个字段长度设置为10,如果是英文字符就可以容纳10位长度,如果是中文字符就只能容纳最多5位长度。如果采用如UTF-8类宽泛字符集类型,也就3-4个中文字符。

字符串类型的长度定义,这个含义是什么?在Oracle中,我们定义varchar2(10)其实是有两层理解,一则是10byte位长度,另一则是10个输入字符长度。两种理解前者是技术派,后者理解是从业务应用角度看问题。其实,在Oracle实现层面,两种策略都是支持的。本篇主要说明一下字符串长度问题,供日后待查。

 

1、环境说明

 

笔者选择Oracle 11gR2版本进行测试实验,具体版本号为11.2.0.4

 

 

SQL> select * from v$version;

 

BANNER

--------------------------------------------------------------------------------

Oracle Database 11g Enterprise Edition Release 11.2.0.4.0 - 64bit Production

PL/SQL Release 11.2.0.4.0 - Production

CORE    11.2.0.4.0 Production

TNS for 64-bit Windows: Version 11.2.0.4.0 - Production

NLSRTL Version 11.2.0.4.0 – Production

 

 

当前字符集为AL32UTF8

 

 

SQL> select * from nls_database_parameters where parameter='NLS_CHARACTERSET';

 

PARAMETER                      VALUE

------------------------------ ---------------

NLS_CHARACTERSET               AL32UTF8

 

 

 

2、默认参数设置

 

首先实验一下默认条件下,Oracle字符串行为方式。创建实验表t

 

 

 

SQL> create table t (v_char varchar2(10));

Table created

 

SQL> desc t;

Name   Type         Nullable Default Comments

------ ------------ -------- ------- --------

V_CHAR VARCHAR2(10) Y                        

 

SQL> insert into t values ('ttt');

1 row inserted

 

SQL> insert into t values ('tttttttttt');

1 row inserted

 

SQL> commit;

Commit complete

 

 

默认情况下,对于varchar2(10)类型,英文字符可以容纳下10个字符。下面测试中文字符情况。

 

 

SQL> insert into t values ('保护');

1 row inserted

 

SQL> commit;

Commit complete

 

SQL> insert into t values ('保护模式');

insert into t values ('保护模式')

 

ORA-12899: "TEST"."T"."V_CHAR" 的值太大 (实际值: 12, 最大值: 10)

 

SQL> insert into t values ('保护模');

1 row inserted

 

SQL> commit;

Commit complete

 

 

对于长度varchar2(10)的字段类型,三位中文似乎是一个上限。从四位中文输入报错的情况看,当前数据库将一个中文识别为三个长度进行计量。潜含的意思是在UTF-8编码情况下,varchar2(10)表示的是10byte长度。

那么,控制长度单位是参数是什么呢?

 

3Char & Byte

 

Oracle语法上看,varchar2(10)这个10后面其实是包括单位的。默认情况下,我们都不需要去管理这个配置。如果我们不显示的进行指定,Oracle会取自参数nls_length_semantics

 

 

SQL> show parameter nls_length

 

NAME                                 TYPE        VALUE

------------------------------------ ----------- ------------------------------

nls_length_semantics                 string      BYTE

 

 

在创建数据表和定义字段过程中,我们是可以明确指定长度单位的。Oracle提供了两种选择,一个是byte,另一个是char类型。

 

 

SQL> create table t_char (v_char varchar2(10 char), v_byte varchar2(10));

 

Table created

 

SQL> desc t_char;

Name   Type              Nullable Default Comments

------ ----------------- -------- ------- --------

V_CHAR VARCHAR2(10 CHAR) Y                         

V_BYTE VARCHAR2(10)      Y                      

 

 

当前是byte默认单位模式下(注意这个前提),v_char字段明显显示10位字符长度。

 

 

SQL> insert into t_char(v_char) values ('tttttttttt');

1 row inserted

 

SQL> insert into t_char(v_char) values ('ttttttttttt');

insert into t_char(v_char) values ('ttttttttttt')

 

ORA-12899: "TEST"."T_CHAR"."V_CHAR" 的值太大 (实际值: 11, 最大值: 10)

 

SQL> commit;

Commit complete

 

 

10位英文字符测试通过,下面试验一下中文字符。

 

 

SQL> insert into t_char(v_char) values ('实验实验实验实验实验');

1 row inserted

 

SQL> commit;

Commit complete

 

SQL> insert into t_char(v_char) values ('实验实验实验实验实验实验');

insert into t_char(v_char) values ('实验实验实验实验实验实验')

 

ORA-12899: "TEST"."T_CHAR"."V_CHAR" 的值太大 (实际值: 12, 最大值: 10)

 

SQL> select * from t_char;

 

V_CHAR                                   V_BYTE

---------------------------------------- ----------

tttttttttt                              

实验实验实验实验实验                     

 

 

十个中文字符可以容纳,使用dump查看存储结构编码。

 

 

SQL> select dump(v_char, 1016) as a from t_char;

 

A

------------------------------------------------------------------------------------------------------------------------------

Typ=1 Len=10 CharacterSet=AL32UTF8: 74,74,74,74,74,74,74,74,74,74

Typ=1 Len=30 CharacterSet=AL32UTF8: e5,ae,9e,e9,aa,8c,e5,ae,9e,e9,aa,8c,e5,ae,9e,e9,aa,8c,e5,ae,9e,e9,aa,8c,e5,ae,9e,e9,aa,8c

 

 

说明:在char计量单位下,数据库不会按照存储技术结构进行长度选取,而是根据实际存放长度进行计量。

那么,默认情况下的取值,与显示进行byte相对应。

 

 

SQL> create table t_byte (v_char varchar2(10 byte));

Table created

 

SQL> desc t_byte;

Name   Type         Nullable Default Comments

------ ------------ -------- ------- --------

V_CHAR VARCHAR2(10) Y

 

 

4、参数变更实验

 

如果进行参数变化,数据库行为是如何呢?

 

 

SQL> conn sys/oracle@sicsdb as sysdba

Connected to Oracle Database 11g Enterprise Edition Release 11.2.0.4.0

Connected as SYS

 

SQL> show parameter nls_leng

 

NAME                                 TYPE        VALUE

------------------------------------ ----------- ------------------------------

nls_length_semantics                 string      BYTE

 

 

修改参数取值。

 

 

SQL> alter system set nls_length_semantics=char scope=both;

System altered

 

SQL> show parameter nls_leng

NAME                                 TYPE        VALUE

------------------------------------ ----------- ------------------------------

nls_length_semantics                 string      CHAR

 

 

注意,此时已经修改了参数,进行实验。

 

 

SQL> create table t_test (v_char varchar2(10));

Table created

 

SQL> desc t_test;

Name   Type         Nullable Default Comments

------ ------------ -------- ------- --------

V_CHAR VARCHAR2(10) Y                        

 

SQL> insert into t_test values ('实验实验实验');

 

insert into t_test values ('实验实验实验')

 

ORA-12899: "TEST"."T_TEST"."V_CHAR" 的值太大 (实际值: 18, 最大值: 10)

 

 

长度配置默认单位没有变化。依然是byte的效果。如果需要参数生效,需要重新启动数据库实例。

 

 

SQL> startup force

ORACLE instance started.

 

Total System Global Area 4275781632 bytes

Fixed Size                  2288080 bytes

Variable Size             939525680 bytes

Database Buffers         3321888768 bytes

Redo Buffers               12079104 bytes

Database mounted.

Database opened.

 

SQL> show parameter nls_leng

 

NAME                                 TYPE        VALUE

------------------------------------ ----------- ------------------------------

nls_length_semantics                 string      CHAR

 

 

此时查看默认创建行为。

 

 

SQL> conn test/test@sicsdb

Connected to Oracle Database 11g Enterprise Edition Release 11.2.0.4.0

Connected as test

 

SQL> create table t_test (v_char varchar2(10));

 

Table created

 

SQL> desc t_test

Name   Type         Nullable Default Comments

------ ------------ -------- ------- --------

V_CHAR VARCHAR2(10) Y                        

 

SQL> insert into t_test values ('实验实验实验实验实验');

 

1 row inserted

 

SQL> commit;

 

Commit complete

 

SQL> select * from t_test;

 

V_CHAR

----------------------------------------

实验实验实验实验实验

 

 

同时,过去定义为byte计量的单元字段也被明显显示出来。

 

 

SQL> desc t_char

Name   Type              Nullable Default Comments

------ ----------------- -------- ------- --------

V_CHAR VARCHAR2(10)      Y                        

V_BYTE VARCHAR2(10 BYTE) Y                         

 

SQL> desc t_byte

Name   Type              Nullable Default Comments

------ ----------------- -------- ------- --------

V_CHAR VARCHAR2(10 BYTE) Y                         

 

 

 

5、结论

 

数据库字段长度在传统的应用开发领域是一个比较重要的问题。传统观点认为这部分应该体现业务逻辑思想,也就是将字段的长度与业务逻辑对应,甚至可以通过数据库来控制用户操作。但是,由于字符串长度因素、软件设计思想的变化影响,这部分逻辑越来越被推到了界面和应用逻辑层次来完成。

Oracle而言,提供char方式定义字段是一种备选的策略。在确实需要数据库严格反应业务逻辑的情况下,是一种不错的选择。


上一篇:PostgreSQL 忘记提交2PC事务对数据库造成的危害.


下一篇:HR SaaS系统,虎蔓开创云端人力资源管理的领先新模式!