在数据库中,字符型数据是按照特定的字符编码格式存储在磁盘中的。达梦数据库支持两种常用的字符集,分别是 GB18030 和 UTF-8。不同的编码方式表达同一个字符所占用的字节数是不同的。
UTF8 是针对 Unicode 的一种可变长度字符编码,使用 1~4 个字节表示全球范围内的字符。GB18030 是中华人民共和国家标准所规定的变长多字节字符集。
达梦数据库在初始化创建阶段就需要定义好所使用的字符集,属于全局性的参数,建库后无法修改。
通过 v$parameter
视图可以查询你所创建的数据库是采用的何种编码:
SELECT name, value, description FROM v$parameter WHERE name like '%CHAR%';
执行结果:
参数 GLOBAL_CHARSET
表示使用的字符集:
上图执行结果中不仅显示了数据库采用的字符集,还显示了其他与字符集相关的参数。
LENGTH_IN_CHAR
参数表示字符宽度。这里的“宽度”指的是在定义可变长字符类型的字段时,长度是以字符还是字节为单位。
字符宽度参数也是建库时指定,后续不能修改,会影响表中字段可以容纳的汉字等非ACSII编码字符的个数。
以 GB18300 编码,LENGTH_IN_CHAR=0 为例,中文汉字一般占用 2 个字节。
CREATE TABLE tab_gb_byte
(
id NUMBER,
name VARCHAR(2)
);
INSERT INTO tab_gb_byte VALUES(1, '中国');
上面的操作执行后,会报如下的错误,因为在 GB18030 编码方式下,“中国”两个汉字需要占用4个字节。
如果在 GB18300 编码,LENGTH_IN_CHAR=1 的数据库中,是可以正常插入的。
插入字符串并验证
INSERT INTO tab_gb_byte VALUES(1,'中');
SELECT dump(name, 16) FROM tab_gb_byte;
可以看出,d6 d0
正是字符"中"的 GB18030 编码,长度为 2 个字节。
如果是采用 UTF8 的编码方式,varchar(2) 能存入"中"字吗?显然不能,因为“中”在 UTF8 的编码方式中占用 3 个字节。
如果修改了列的定义,如下所示,即可成功插入:
ALTER TABLE tab_gb_byte MODIFY (name VARCHAR(3));
查看后发现长度为 3 的汉字"中"的 UTF8 编码:
SELECT dump(name, 16) FROM tab_gb_byte;
客户端键入 SELECT 语句后字符串显示乱码,现象如下:
出现这种问题的原因很可能就是客户端的字符集设置与实际情况不同。例如客户端使用 UTF8 的编码方式解析字符信息,但是 LANG 环境变量确设置成了 en_US.GB18030,显示中文时就会出现上面的乱码。应该设置 LANG = en_US.UTF-8
就不会显示乱码了
造成这种错误原因之一就是源库和目标库的字符类型的列所使用的宽度单位不一致。例如源端 UTF8 + VARCHAR(20) 代表可以容纳 20 个 UTF8 编码的字符,但是目标端对应的是 GBK + VARCHAR(20) 代表可容纳 20 个字节,也就是说最多存储 10 个中文字符。所以,当源端传递到目标的汉字字符个数乘以目标端每个汉字占用的字节数,大于目标端定义的字节数上限时,就会报错。
注:DTS 迁移工具可以对 UTF8 和 GB18030 两种编码按数据库设定的字符集自动转换。
讨论这个问题前先明确我的数据库字符集环境:GB18030 + 按字节描述宽度 + 数据块大小为16K。8K 和 32K 块大小的数据库都可以成功创建该表。
CREATE TABLE t1
(
name VARCHAR(8188),
address CHAR(8188)
);
alter table t1 modify (name varchar(8189));
alter table t1 modify (address char(8189));
执行上述两条 ALTER 语句均报错如下:
可见 VARCHAR 类型的字段最大定义长度为 8188 个字节。对于按字符数计算宽度的列也能定义 8188 吗?答案是也可以这样定义,但实际能存储这么多个字符吗?
DROP TABLE t1;
/
CREATE TABLE t1
(
name VARCHAR(8188)
);
执行下面的匿名过程可以得到 16K 的数据块中最多可以插入多少个字节的 ACSII 码字符。
DECLARE
EXCEED EXCEPTION;
PRAGMA EXCEPTION_INIT(EXCEED, -2665);
v_maxins NUMBER:= 8188;
BEGIN
<<LX>>
WHILE v_maxins > 0 LOOP
INSERT INTO t1 VALUES(dbms_random.string('x', v_maxins));
PRINT(v_maxins || ' is MAX value.');
v_maxins := 0;
END LOOP;
EXCEPTION
WHEN EXCEED THEN
PRINT(v_maxins || ' is too long.');
v_maxins := v_maxins - 1;
GOTO LX;
END;
/
数据块大小 | 最大存储字符数(ASCII码) | GBK 最大汉字 |
---|---|---|
8K | 3879 | 1939 |
16K | 7979 | 3990 |
32K | 8188 | 4094 |
DROP TABLE t1;
/
CREATE TABLE t1
(
name VARCHAR(8188),
addr VARCHAR(8188)
);
DECLARE
EXCEED EXCEPTION;
PRAGMA EXCEPTION_INIT(EXCEED, -2665);
v_maxins NUMBER:= 8188;
BEGIN
<<LX>>
WHILE v_maxins > 0 LOOP
INSERT INTO t1 VALUES(dbms_random.string('x', v_maxins), rpad('X',100,'+'));
PRINT(v_maxins || ' is MAX value.');
v_maxins := 0;
END LOOP;
EXCEPTION
WHEN EXCEED THEN
PRINT(v_maxins || ' is too long.');
v_maxins := v_maxins - 1;
GOTO LX;
END;
/
执行上述的匿名块,插入一行中有一个字符型字段,占用了 100 个字节的空间,那么剩余可插入的字符个数的最大值应该小于 7979-100=7879。 实际运行的结果为 7878 个字节,还有一个字节应该是用来存放 VARCHAR 类型字段的长度。VARCHAR 改成 CHAR(100) 结果一样。
所有上面表格中存储的最大字符数可以理解成一行记录中所有 VARCHAR 和 CHAR 列的字符所占用的字节数之和的最大值。
执行上述语句会报错,因为两个字符型字段加起来的字节数超过了 7979。准确的说是不能超过 7978,因为每增加一个列,对应的消耗了一个到二个字节存储字段长度信息。
文章
阅读量
获赞