1. 概述
GB18030 标准作为信息技术领域的强制性国家标准之一,规定了信息技术用的中文图形字符及其二进制编码的十六进制表示,其重要性不言而喻。
GB18030 首次发布于 2000 年,于 2005 年进行了首次修订,OceanBase 也在之前的版本中支持了 GB18030-2005 标准。 2022年7月19日,GB18030-2022 标准发布,代替 GB18030-2005 成为信息系统的全文强制执行标准。新标准支持了更多的生僻汉字及少数民族文字,并且与 GB18030-2005 标准有所不同。因此,OceanBase 在 4.2 版本实现了支持新标准的字符集 GB18030_2022 。同时,也配套更新了相应的排序方法。
2. GB18030-2022 标准变化
GB18030-2005 标准规定了 70195 个 CJK 统一汉字以及 6 种少数民族文字,以及这些字符的十六进制表示与 UNICODE 代码位置。GB18030-2022 标准所规定的 CJK 统一汉字增添到了 87875 个,少数民族文字增添到了 10 种。具体的差异可以参考 GB18030-2022 的前言部分,主要可以分为两部分:新字符的增加 (d-n) 与 UNICODE 代码位置的变化(b,c)。
需要说明的是,OceanBase Server 并不关心新字符的增加,因为新字符的编码在 2005 标准中本就合法,Server 也只是运算存储这些编码,而不展示字形。例如,2022 标准中新增的字符 0x82359033(龰), 在基于 2005 标准的 GB18030 字符集下也是能正常处理。而对于后者,即UNICODE代码位置的变化,是OceanBase Server 主要关心的变化。
OceanBase(root@test)>select _gb18030 0x82359033 as c from dual;
+-----+
| c |
+-----+
| 龰 |
+-----+
1 row in set (0.00 sec)
因此,对于 2022 新标准,Server 主要关注 GB18030-2022 编码与 Unicode 编码的转换关系。
老标准GB18030-2005 发布时包含7w多个汉字,而当时的 Unicode 编码中包含的中文字符不全,这就导致有部分GB18030 字符没有对应的 Unicode,老标准选择临时映射到 Unicode BMP 的 PUA(用户自定义区,这个区域的 Unicode 原则上不能使用)。在 Unicode 升级到5.0后,这些字符有了他们标准的码位,因此新标准GB18030-2022 中的字符需要将原本"错误的"映射改为"正确"映射。以汉字矛的字头"龴"举例解释:
从上图可以看到 "龴"在2005年与2022年的码位变化:
- 2005年 Unicode 中没有 "龴",因此GB18030-2005选择将这个字符映射到临时非标码位(PUA区)。
- 2022年 Unicode "龴"的正式编码为 U+9FB4,落在Unicode BMP区,GB18030的"龴"映射应改为正式编码。
- 由于原本的 U+9FB4 已经有映射的 G+82359037,为了保证 GB18030 与 Unicode 的码位应为一对一映射,因此G+82359037也需要调整,改为映射到临时非标准编码,即对调了映射关系。
与 "龴" 一样被调换了映射关系的字符共有 18 个,这就导致 GB18030-2005 与 GB18030-2022 两个标准从 UNICODE 看来是不兼容的。(GB18030-2005 下的 0xFE59(龴)转换为 UNICODE (0xE81E)再转换为 GB18030-2022 成为了没有意义的 0x82359037 )。因此,Server 需要一个新的字符集支持 GB18030-2022 标准,而非修改已有的字符集。
具体的 GB18030-2022 标准内容见 国家标准|GB 18030-2022。
3. GB18030_2022 字符集功能介绍
OceanBase 4.2版本支持的字符集名称为 gb18030_2022,在 Oracle 模式下的名称为 ZHS32GB18030_2022。
那么,在使用时该如何指定租户字符集呢?创建租户时可以设置字符集为 gb18030_2022。Oracle 模式下,字符集是租户级别,在 gb18030_2022 租户中,所有用户表的 char、varchar2、clob 字段都是 gb18030_2022 字符集。
create tenant oracle replica_num = 1,
resource_pool_list =('pool1'),
charset = gb18030_2022
set
ob_tcp_invited_nodes = '%',
ob_compatibility_mode = 'oracle',
parallel_servers_target = 10,
ob_sql_work_area_percentage = 20,
secure_file_priv = "";
客户端可以通过 set names 配置当前会话的字符集和字符序的系统变量。需要注意的是,set names 语句不会更改客户端输入字符的编码。例如,使用 set names 配置客户端编码为 gb18030_2022 的前提是客户端已经使用了 gb18030_2022 编码,否则会导致乱码出现。
-- 客户端使用 utf8mb4 编码并创建了 utf8mb4 编码的表 t
OceanBase(root@test)>create table t(c varchar(100));
Query OK, 0 rows affected (0.917 sec)
-- 插入了 utf8mb4 编码的字符
OceanBase(root@test)>insert into t values ('字符集');
Query OK, 1 row affected (0.281 sec)
-- 修改了当前会话的字符集,但没有改变客户端实际使用的字符集
OceanBase(root@test)>set names gb18030_2022;
Query OK, 0 rows affected (0.001 sec)
-- 仍然使用 utf8mb4 编码插入字符
OceanBase(root@test)>insert into t values ('字符集');
Query OK, 1 row affected (0.011 sec)
-- 出现乱码
OceanBase(root@test)>select * from t;
+----------+
| c |
+----------+
| �ַ��� |
| 字符� |
+----------+
-- 修改当前会话的字符集回 utf8mb4
OceanBase(root@test)>set names utf8mb4;
Query OK, 0 rows affected (0.001 sec)
-- 第二次插入的字符仍为乱码
OceanBase(root@test)>select * from t;
+--------------+
| c |
+--------------+
| 字符集 |
| 瀛楃闆 |
+--------------+
2 rows in set (0.006 sec)
3.1. MySQL 模式下使用 GB18030_2022 字符集
MySQL 模式下,同一个租户中可以使用多种不同的字符集,本节中将介绍一些 MySQL 模式下常见的字符集使用场景。
场景1:通过 show 语句查看 Server 是否支持的 GB18030_2022 字符集以及相应的字符序。
OceanBase(root@oceanbase)>show charset;
+--------------+-----------------------+-------------------------+--------+
| Charset | Description | Default collation | Maxlen |
+--------------+-----------------------+-------------------------+--------+
| binary | Binary pseudo charset | binary | 1 |
| utf8mb4 | UTF-8 Unicode | utf8mb4_general_ci | 4 |
| gbk | GBK charset | gbk_chinese_ci | 2 |
| utf16 | UTF-16 Unicode | utf16_general_ci | 2 |
| gb18030 | GB18030 charset | gb18030_chinese_ci | 4 |
| latin1 | cp1252 West European | latin1_swedish_ci | 1 |
| gb18030_2022 | GB18030-2022 charset | gb18030_2022_chinese_ci | 4 |
+--------------+-----------------------+-------------------------+--------+
7 rows in set (0.01 sec)
OceanBase(root@oceanbase)>show collation;
+-------------------------+--------------+-----+---------+----------+---------+
| Collation | Charset | Id | Default | Compiled | Sortlen |
+-------------------------+--------------+-----+---------+----------+---------+
| utf8mb4_general_ci | utf8mb4 | 45 | Yes | Yes | 1 |
| utf8mb4_bin | utf8mb4 | 46 | | Yes | 1 |
| binary | binary | 63 | Yes | Yes | 1 |
| gbk_chinese_ci | gbk | 28 | Yes | Yes | 1 |
| gbk_bin | gbk | 87 | | Yes | 1 |
| utf16_general_ci | utf16 | 54 | Yes | Yes | 1 |
| utf16_bin | utf16 | 55 | | Yes | 1 |
| utf8mb4_unicode_ci | utf8mb4 | 224 | | Yes | 1 |
| utf16_unicode_ci | utf16 | 101 | | Yes | 1 |
| gb18030_chinese_ci | gb18030 | 248 | Yes | Yes | 1 |
| gb18030_bin | gb18030 | 249 | | Yes | 1 |
| latin1_swedish_ci | latin1 | 8 | Yes | Yes | 1 |
| latin1_bin | latin1 | 47 | | Yes | 1 |
| gb18030_2022_bin | gb18030_2022 | 216 | | Yes | 1 |
| gb18030_2022_chinese_ci | gb18030_2022 | 217 | Yes | Yes | 1 |
| gb18030_2022_chinese_cs | gb18030_2022 | 218 | | Yes | 1 |
| gb18030_2022_radical_ci | gb18030_2022 | 219 | | Yes | 1 |
| gb18030_2022_radical_cs | gb18030_2022 | 220 | | Yes | 1 |
| gb18030_2022_stroke_ci | gb18030_2022 | 221 | | Yes | 1 |
| gb18030_2022_stroke_cs | gb18030_2022 | 222 | | Yes | 1 |
+-------------------------+--------------+-----+---------+----------+---------+
场景2:指定 SQL 中字符串常量的字符集。在十六进制数前添加 _gb18030_2022
可以将该编码转换为 gb18030_2022
下的相应字符。在字符串前添加 _gb18030_2022
可以指定该字符串的字符集为 gb18030_2022
。
OceanBase(root@test)>select _gb18030_2022 0xCDE5 as c from dual;
+-----+
| c |
+-----+
| 湾 |
+-----+
1 row in set (0.00 sec)
OceanBase(root@test)>select _gb18030_2022 '湾' as c from dual;
+-----+
| c |
+-----+
| 湾 |
+-----+
1 row in set (0.00 sec)
场景3:创建(修改)数据库/表/列时可以指定字符集(序)。数据库中一列的字符集(序)会按照 列、表、库、服务器 定义的字符集(序)的顺序进行设置。
OceanBase(root@test)>create database gb collate gb18030_2022_bin;
Query OK, 1 row affected (0.07 sec)
OceanBase(root@test)>create table t(a varchar(10) collate gb18030_2022_bin, b varchar(10)) collate gb18030_2022_radical_ci;
Query OK, 0 rows affected (0.13 sec)
OceanBase(root@test)>show create table t;
+-------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table |
+-------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| t | CREATE TABLE `t` (
`a` varchar(10) CHARACTER SET gb18030_2022 COLLATE gb18030_2022_bin DEFAULT NULL,
`b` varchar(10) COLLATE gb18030_2022_radical_ci DEFAULT NULL
) DEFAULT CHARSET = gb18030_2022 COLLATE = gb18030_2022_radical_ci ROW_FORMAT = DYNAMIC COMPRESSION = 'zstd_1.3.8' REPLICA_NUM = 1 BLOCK_SIZE = 16384 USE_BLOOM_FILTER = FALSE TABLET_SIZE = 134217728 PCTFREE = 0 |
+-------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
场景4:SQL 中 GB18030 与 GB18030_2022 的混用。这两种字符集的隐式混用在 OceanBase 中时不被允许的,因为 2005 标准与 2022 标准在 Unicode 看来是不一致的(见 本文第二章),因此我们认为 GB18030 与 GB18030_2022 的混用是危险的,并在类型推导系统中禁止了这两者的混合。
OceanBase(root@test)>create table t(c varchar(100) charset gb18030_2022);
Query OK, 0 rows affected (0.349 sec)
-- gb18030_2022 编码的列 c 与 gb18030 字符的比较
OceanBase(root@test)>explain select * from t where c = convert('字' using gb18030);
ERROR 1267 (HY000): Illegal mix of collations
OceanBase(root@test)>explain select * from t where c = _gb18030 0xD7D6;
ERROR 1267 (HY000): Illegal mix of collations
场景5:GB18030 与 GB18030_2022 的强制转换。虽然我们禁止了 SQL 中 GB18030 与 GB18030_2022 的隐式转换,但我们还是保留了这两者的强制转换。用户可以通过 convert 显式地转换一个 GB18030 的字符串的字符集为 GB18030_2022。该转换没有经过 Unicode,采取了保留编码的方法。
OceanBase(root@test)>create table t(c varchar(100) charset gb18030_2022);
Query OK, 0 rows affected (0.349 sec)
OceanBase(root@test)>insert into t values ('字');
Query OK, 1 row affected (0.008 sec)
-- gb18030_2022 编码的列 c 与 转换为 gb18030_2022 的 0xD7D6 的比较
OceanBase(root@test)>select * from t where c = convert(_gb18030 0xD7D6 using gb18030_2022);
+------+
| c |
+------+
| 字 |
+------+
1 row in set (0.018 sec)
-- ‘龴’ 的编码在转换前后都为 0xFE59,没有变化
OceanBase(root@test)>select hex(convert(_gb18030 0xFE59 using gb18030_2022)), hex(convert(_gb18030_2022 0xFE59 using gb18030));
+--------------------------------------------------+--------------------------------------------------+
| hex(convert(_gb18030 0xFE59 using gb18030_2022)) | hex(convert(_gb18030_2022 0xFE59 using gb18030)) |
+--------------------------------------------------+--------------------------------------------------+
| FE59 | FE59 |
+--------------------------------------------------+--------------------------------------------------+
MySQL 模式下的详细操作可参考字符集文档。
3.2. Oracle 模式下使用 GB18030_2022 字符集
Oracle 模式下,字符集是租户级别,在 GB18030_2022 租户中,所有用户表的 char、varchar2、clob 字段都是 GB18030_2022 字符集,并且都按照二进制 bin 排序。本节中将介绍一些 Oracle 模式下常见的字符集使用场景。
场景1:基础场景。Oracle 模式下,字符串常量在解析时即被转换为租户字符集,以统一 SQL 中各表达式的字符集。
-- GB18030_2022 租户下,列 C 的字符集为 GB18030_2022,字符序为 GB18030_2022_BIN
CREATE TABLE T1(C VARCHAR(100));
-- 设置客户端字符集为 GB18030
set names gb18030;
-- SQL 在解析时将字符串 '字' 转换为 GB18030_2022,SQL 正常执行不报错
SELECT * FROM T1 WHERE C = '字';
场景2:转换字符集。可以通过系统函数 convert
显式地转换字符串的字符集。与 MySQL 模式相同,GB18030 与 GB18030_2022 的转换不经过 Unicode, 采取保留编码的方式。
OceanBase(SYS@SYS)>CREATE TABLE t1 (c1 varchar(10));
Query OK, 0 rows affected (1.250 sec)
OceanBase(SYS@SYS)>insert into t1 values ('字');
Query OK, 1 row affected (0.400 sec)
OceanBase(SYS@SYS)>select rawtohex(c1), rawtohex(convert(c1, 'ZHS32GB18030_2022')) from t1;
+--------------+-------------------------------------------+
| RAWTOHEX(C1) | RAWTOHEX(CONVERT(C1,'ZHS32GB18030_2022')) |
+--------------+-------------------------------------------+
| D7D6 | D7D6 |
+--------------+-------------------------------------------+
1 row in set (0.028 sec)
4. GB18030_2022 字符序功能介绍
字符集在 OceanBase 数据库中无法单独存在,一定是与字符序一起出现的,指定字符集时同时也指定了字符序。
下表中给出了本次为 GB18030_2022 字符集配套实现的 7 种字符序,包含二进制、拼音、部首、笔画序以及相应的大小写敏感模式。MySQL 模式下,可以通过 weigh_string 函数查看字符的排序权重。字符序描述Oracle 模式下 nls_sort 名称 gb18030_2022_bin 二进制排序,Oracle 默认序 gb18030_2022_chinese_ci 拼音序, case insensitive,MySQL 默认序 gb18030_2022_chinese_cs 拼音序, case sensitiveSCHINESE_PINYIN2_M gb18030_2022_radical_ci部首笔画序, case insensitive gb18030_2022_radical_cs 部首笔画序, case sensitiveSCHINESE_RADICAL2_M gb18030_2022_stroke_cs 笔画序, case insensitive gb18030_2022_stroke_cs 笔画序, case sensitiveSCHINESE_STROKE2_M
4.1. MySQL 模式下使用 GB18030_2022 字符序
MySQL 模式下,如果需要查看一个字符串的排序权重,可以使用系统函数 weight_string
。对字符串的排序本质上就是对其排序权重的排序。
场景1:使用二进制序。可以看到,此时字符串的 weight_string 和底层编码相同,即,字符串按照二进制排序。
OceanBase(root@test)>create table t(a varchar(10) collate gb18030_2022_bin);
Query OK, 0 rows affected (0.470 sec)
OceanBase(root@test)>insert into t values ('a'),('A'),('b'),('B'),('曹操'),('曹丕'),('曹植'),('闯'),('闖');
Query OK, 9 rows affected (0.009 sec)
Records: 9 Duplicates: 0 Warnings: 0
OceanBase(root@test)>select a,hex(weight_string(a)) from t order by a;
+--------+-----------------------+
| a | hex(weight_string(a)) |
+--------+-----------------------+
| A | 41 |
| B | 42 |
| a | 61 |
| b | 62 |
| 曹操 | B2DCB2D9 |
| 曹植 | B2DCD6B2 |
| 曹丕 | B2DCD8A7 |
| 闯 | B4B3 |
| 闖 | EA4A |
+--------+-----------------------+
9 rows in set (0.002 sec)
场景2:使用拼音序。参考 Unicode CLDR - CLDR 42 Release Note 中 zh.xml 给定的拼音序。
以 gb18030_2022_chinese_ci
为例,一字节或双字节的非中文字符以其大写二进制编码为权重,四字节的非中文字符权重以 0xFF0 开头,中文字符权重以 0xFFA 开头(这里的非中文字符也包括 CLDR 中未给出排序的中文字符)。
值得注意的是,gb18030_2022_chinese_ci 与 gb18030_chinese_ci 的排序结果会有所不同,CLDR42 除了新增了部分字符的拼音,也订正了部分字符的拼音。如“亣”在 CLDR24 (gb18030 字符集参考的排序标准)中拼音标注为 ta
,排在 “门”后面,在 CLDR42 中订正为 da
,排在“门”前面。
OceanBase(root@test)>create table t(a varchar(10) collate gb18030_2022_chinese_ci);
Query OK, 0 rows affected (0.397 sec)
OceanBase(root@test)>insert into t values ('a'),('A'),('b'),('B'),('亣'),('大'),('门'),('ζ'),(_gb18030_2022 0x8335D531);
Query OK, 9 rows affected (0.063 sec)
Records: 9 Duplicates: 0 Warnings: 0
OceanBase(root@test)>select a,hex(weight_string(a)) from t order by a;
+------+-----------------------+
| a | hex(weight_string(a)) |
+------+-----------------------+
| a | 41 |
| A | 41 |
| b | 42 |
| B | 42 |
| ζ | A6A6 |
| 페 | FF007E55 |
| 大 | FFA014BA |
| 亣 | FFA014BB |
| 门 | FFA052DB |
+------+-----------------------+
9 rows in set (0.012 sec)
OceanBase(root@test)>drop table t;
Query OK, 0 rows affected (0.704 sec)
-- 使用 GB18030_chinese_ci 序后结果变化
OceanBase(root@test)>create table t(a varchar(10) collate gb18030_chinese_ci);
Query OK, 0 rows affected (0.411 sec)
OceanBase(root@test)>insert into t values ('a'),('A'),('b'),('B'),('亣'),('大'),('门'),('ζ');
Query OK, 8 rows affected (0.014 sec)
Records: 8 Duplicates: 0 Warnings: 0
OceanBase(root@test)>select a,hex(weight_string(a)) from t order by a;
+------+-----------------------+
| a | hex(weight_string(a)) |
+------+-----------------------+
| a | 41 |
| A | 41 |
| b | 42 |
| B | 42 |
| ζ | A6A6 |
| 大 | FFA01372 |
| 门 | FFA04D5F |
| 亣 | FFA06F15 |
+------+-----------------------+
场景3:使用笔画序。同样参考 Unicode CLDR - CLDR 42 Release Note 中 zh.xml 给定的笔画序。
OceanBase(root@test)>create table t(a varchar(10) collate gb18030_2022_stroke_cs);
Query OK, 0 rows affected (0.474 sec)
OceanBase(root@test)>insert into t values ('a'),('A'),('b'),('B'),('亣'),('大'),('门'),('ζ'),(_gb18030_2022 0x8335D531);
Query OK, 9 rows affected (0.048 sec)
Records: 9 Duplicates: 0 Warnings: 0
OceanBase(root@test)>select a,hex(weight_string(a)) from t order by a;
+------+-----------------------+
| a | hex(weight_string(a)) |
+------+-----------------------+
| A | 41 |
| B | 42 |
| a | 61 |
| b | 62 |
| ζ | A6C6 |
| 페 | FF007E55 |
| 大 | FFA000F6 |
| 门 | FFA0013A |
| 亣 | FFA00195 |
+------+-----------------------+
9 rows in set (0.021 sec)
场景4:使用部首笔画序。参考 UAX #38: Unicode Han Database (Unihan) 中的 Unicode 部首序 kRSUnicode 中的首个部首信息(对于相同部首,简体部首排在繁体部首前面,部首笔画数相同时按GB18030-2022编码排序)。在 kRSUnicode 中,部首按照繁体笔画数排序,如 "门" 按照“門”计算笔画,排在“酉”后面。
OceanBase(root@test)>create table t(a varchar(10) collate gb18030_2022_radical_ci);
Query OK, 0 rows affected (0.484 sec)
OceanBase(root@test)>insert into t values ('a'),('A'),('b'),('B'),('闖'),('闯'),('门'),('醜'),('锌'),('陆'),('ζ'),(_gb18030_2022 0x8335D531);
Query OK, 12 rows affected (0.163 sec)
Records: 12 Duplicates: 0 Warnings: 0
OceanBase(root@test)>select a,hex(weight_string(a)) from t order by a;
+------+-----------------------+
| a | hex(weight_string(a)) |
+------+-----------------------+
| a | 41 |
| A | 41 |
| b | 42 |
| B | 42 |
| ζ | A6A6 |
| 페 | FF007E55 |
| 醜 | FFA13CA4 |
| 锌 | FFA13F14 |
| 门 | FFA148F0 |
| 闯 | FFA148F8 |
| 闖 | FFA14B24 |
| 陆 | FFA14C32 |
+------+-----------------------+
12 rows in set (0.035 sec)
4.2. Oracle 模式下使用 GB18030_2022 字符序
Oracle 模式下的默认排序方式为 bin
, 即根据字符串的二进制编码进行排序,但可以通过系统函数 nlssort
获取字符串在其他字符序下的排序权重,从而排序。 GB18030_2022 所实现的三种 NLSSORT 排序方式为 SCHINESE_PINYIN2_M
,SCHINESE_RADICAL2_M
,SCHINESE_STROKE2_M
,分别与 gb18030_2022_chinese_cs
, gb18030_2022_radical_cs
, gb18030_2022_stroke_cs
保持一致。
OceanBase(SYS@SYS)>create table t(a varchar(10));
Query OK, 0 rows affected (0.285 sec)
OceanBase(SYS@SYS)>insert into t values ('a'),('A'),('b'),('B'),('闖'),('闯'),('门'),('醜'),('锌'),('陆'),('ζ');
Query OK, 11 rows affected (0.081 sec)
Records: 11 Duplicates: 0 Warnings: 0
OceanBase(SYS@SYS)>select a,NLSSORT(a, 'NLS_SORT=SCHINESE_PINYIN2_M' ) from t order by NLSSORT(a, 'NLS_SORT=SCHINESE_PINYIN2_M' );
+------+------------------------------------------+
| A | NLSSORT(A,'NLS_SORT=SCHINESE_PINYIN2_M') |
+------+------------------------------------------+
| A | 41 |
| B | 42 |
| a | 61 |
| b | 62 |
| ζ | A6C6 |
| 醜 | FFA01016 |
| 闯 | FFA01156 |
| 闖 | FFA0115E |
| 陆 | FFA04E35 |
| 门 | FFA052DB |
| 锌 | FFA08A0F |
+------+------------------------------------------+
11 rows in set (0.002 sec)
5. 总结
OceanBase 在 4.2 版本中实现了对 GB18030-2022 标准的支持,成功通过了全国信息技术标准化技术委员会的认证,成为 GB18030-2022 标准的首批测试通过产品之一,能够满足金融、政务等各种场景下对生僻字的处理。 在此基础上,我们也拓展了对多种汉字排序方法的支持,包括拼音、部首、笔画排序。
后续我们会逐步提升 OceanBase 对各种字符集的支持能力,以满足更多用户需求。