根据官方文档和实验测试整理一下常见问题以及相关结论,以NVARCHAR2为主。
NCHAR和NVARCHAR2都是Unicode数据类型,存储Unicode字符数据。NCHAR和NVARCHAR2数据类型的对应的国家字符集(NLS_NCHAR_CHARACTERSET)只能是AL16UTF16或者UTF8。
NCHAR和NVARCHAR2均有最大字符长度和最大字节数的限制,这两个要求必须同时满足。下面分别来看
我们知道,对于char和varchar类型,是由nls_length_semantics参数的值来决定varchar(100)中的100指的字节数还是字符数(参考 聊聊字符串数据长度和nls_length_semantics参数),但对于NCHAR和NVARCHAR2来说,无论nls_length_semantics的值是什么,都相当于是char。因此NVARCHAR2(100)中的100一定是字符数。
When you create a table with an
NCHAR
orNVARCHAR2
column, the maximum size specified is always in character length semantics. Character length semantics is the default and only length semantics forNCHAR
orNVARCHAR2
.
最大字符长度可以定义为多少呢?
12c之前,取决于国家字符集设置
从12.1开始,取决于两个设置 —— MAX_STRING_SIZE和国家字符集
16383 if MAX_STRING_SIZE
=
EXTENDED
and the national character set is AL16UTF16
32767 if MAX_STRING_SIZE
=
EXTENDED
and the national character set is UTF8
2000 if MAX_STRING_SIZE
=
STANDARD
and the national character set is AL16UTF16
4000 if MAX_STRING_SIZE
=
STANDARD
and the national character set is UTF8
根据这个定义,例如我们设置MAX_STRING_SIZE = STANDARD,NLS_NCHAR_CHARACTERSET=AL16UTF16,那最多就只能定义为NVARCHAR2(2000),即最多能存2000个字符;如果NLS_NCHAR_CHARACTERSET=UTF8,那最多能定义为NVARCHAR2(4000),即最多能存4000个字符。
对于NCHAR(无论版本),最大长度为2000字节,这个值是固定死的,不受其他参数影响。
对于NVARCHAR2,12c之前,最大长度为4000字节,这个值也是固定死的。
12.1开始,NVARCHAR2还受MAX_STRING_SIZE 参数影响,如果MAX_STRING_SIZE = STANDARD则与之前版本相同,最大长度为4000字节;如果MAX_STRING_SIZE = EXTENDED,最大长度为32767字节
即使按照前面的定义NLS_NCHAR_CHARACTERSET=UTF8,NVARCHAR2(4000)最多应该能存4000个字符,如果实际的数据大小超过了4000字节,就会遇到报错。由于UTF8中一个英文占1 byte,一个中文占3 byte,此时NVARCHAR2(4000)最多只能存1333个中文(即3999 byte)。
如果是用的NLS_NCHAR_CHARACTERSET=AL16UTF16,由于AL16UTF16无论中文英文每个都是占2 byte,此时NVARCHAR2(2000) 最多只能存2000个中/英文(即4000 byte)。
如果你是用的NLS_NCHAR_CHARACTERSET=AL16UTF16,用客户端实际insert中文时应该会发现最多就只能插入1333个中文,再多就报错了,纯英文则没有这个限制,并且插入之后使用lengthb查看字节数,只是2666。很奇怪,中文字符数据并不符合第三节对AL16UTF16的规定,反而符合对UTF8的规定。
经过一番搜索,在文档 Selecting LENGTHB of an NVARCHAR2 does not return the byte length (文档 ID 2390023.1) 中找到了答案。
这个问题跟客户端国家字符集NLS_NCHAR的设置有关,NLS_NCHAR默认是UTF8,所以我们在客户端输入一个中文,实际上是占的3个byte。而输入1333个字符时,实际上已经占了3999 byte,所以再加中文就报错了。如果想要能输入2000个中文,应该要设置NLS_NCHAR环境变量为AL16UTF16(但测试的时候发现改了也没用,还没研究出来为啥)。
为什么把1333个中文成功插入表后用lengthb查又变成了2666,应该是在插入时Oracle进行了字符集转换,把客户端的UTF8国家字符集转成了AL16UTF16,因此每个字符长度又变成了2 byte。
如果使用concat函数拼接我们前面输入的1000个中文变成2000个中文,会发现实际上是可以成功的,因为此时每个字符长度为2 byte,NVARCHAR2(2000)最多可以放2000个字符,最多刚好占4000 byte。
select length(v_byte),lengthb(v_byte) from t_byte;
insert into t_byte select concat(v_byte,v_byte) from t_byte where length(v_byte)=1000;
参考
https://docs.oracle.com/database/121/SQLRF/sql_elements001.htm#SQLRF30020
https://docs.oracle.com/cd/B28359_01/server.111/b28318/datatype.htm#CNCPT1825
http://www.databases-la.com/?q=node/55
https://blog.darkthread.net/blog/nvarchar2-max-len-for-chinese/
https://oracle-base.com/articles/12c/extended-data-types-12cR1
https://docs.oracle.com/en/database/oracle/oracle-database/19/refrn/datatype-limits.html#GUID-963C79C9-9303-49FE-8F2D-C8AAF04D3095
Selecting LENGTHB of an NVARCHAR2 does not return the byte length (文档 ID 2390023.1)