十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

TiDB 字符集框架设计深度解读:从 GBK/GB18030 扩展看字符集与排序规则工程化

TiDB 字符集框架设计深度解读:从 GBK/GB18030 扩展看字符集与排序规则工程化 TiDB 字符集框架设计深度解读从 GBK/GB18030 扩展看字符集与排序规则工程化【免费下载链接】tidbTiDB is built for agentic workloads that grow unpredictably, with ACID guarantees and native support for transactions, analytics, and vector search. No data silos. No noisy neighbors. No infrastructure ceiling.项目地址: https://gitcode.com/GitHub_Trending/ti/tidb导读TiDB 在很长一段时间内仅原生支持ascii、binary、latin1、utf8、utf8mb4五种字符集面对 MySQL 生态中广泛使用的gbk、gb18030等中文字符集时束手无策。本文基于 TiDB 仓库中的设计文档 2021-08-18-charsets.md完整剖析 TiDB 如何设计并实现可插拔的字符集框架——以统一转为 UTF-8 处理、对外按原字符集读写为核心思路以 GBK 支持为切入点并延伸到排序规则Collation、解析器、优化器、下推计算与周边组件兼容等全链路改造方案。读完本文你将掌握 TiDB 字符集框架的总体架构、各模块改造要点、Encoding与Collator两个核心接口的契约以及这些设计在当前仓库源码中如何真实落地。一、背景与动机TiDB 为什么需要字符集框架1.1 基础概念字符、字符集与字符编码在展开设计之前文档先厘清了三个容易混淆的概念字符Character各种字母和符号的统称可以是汉字、英文字母、阿拉伯数字、标点、图形符号或控制符号等。字符集Charset多个字符的集合如 ASCII、Unicode、GBK 字符集。字符编码Character Encoding一种映射规则依据该规则把字符映射为适合计算机存储与传输的数据形态。每个字符集都有对应的编码规则如 UTF-8 编码、GBK 编码。三者是集合—成员—映射的递进关系字符集圈定字符范围字符编码定义字符与字节序列之间的映射方式。1.2 现实痛点5 个字符集远不足以覆盖生产场景MySQL 支持utf8mb4、gbk、gb18030等大量字符集而当时的 TiDB 只支持 5 个字符集并且已有的字符集本身也存在缺陷文档援引了两个典型 issuelatin1 字符集的错误编码问题charset: incorrect encoding for latin1扩展拉丁字符集不支持的问题。更关键的是当时 TiDB 新增一个字符集的成本极高缺乏统一的扩展入口。而在中国许多传统行业政务、金融、制造等的存量系统中gbk/gb18030仍是事实标准数据库必须能直接读写这类数据因此设计文档选择GBK 字符集支持作为整套框架的验证案例——先搭好框架再让 GBK 成为第一个吃螃蟹的非 UTF-8 字符集。二、功能需求与设计目标文档为 GBK 字符集支持明确了如下功能需求Functional Requirements完整覆盖 GBK 1.0 标准定义的全部码点支持与其他字符集的转换依据 collation coercibility排序规则强制力规则支持隐式转换支持使用CAST、CONVERT等函数显式转换 GBK 字符支持相关 SQL 语句如SET CHARACTER SET GBK、SET NAMES GBK、SHOW CHARSET等支持 GBK 字符集下字符串之间的比较非法字符处理传入非法字符时返回错误或警告——在CONVERT中使用非法字符返回错误其他场景返回警告与其他组件兼容包括数据导入、迁移等工具链。2.1 需要支持的排序规则Supported Collations排序规则类型说明gbk_bin二进制排序按 GBK 字节序二进制比较gbk_chinese_ci拼音排序默认大小写不敏感按拼音规则排序gbk_utf8mb4_bin基于 utf8mb4 编码的二进制排序面向性能优化的变体设计上gbk_chinese_ci被选为 GBK 的默认排序规则因为它符合中文字符串按拼音排序的业务直觉。三、总体设计一切以 UTF-8 为中转枢纽整个方案的核心思路非常清晰——收到非 UTF-8 字符集请求后先在入口统一转换为 UTF-8在 TiDB 运行时层与存储层的计算、存储过程中一律按 UTF-8 处理最终再把结果转换回原非 UTF-8 字符集返回。这套内 UTF-8、外原字符集的架构使改动得以收敛存储层TiKV与计算内核无需理解 GBK 等编码细节只需处理一种规范编码字符集差异被隔离在连接层客户端协议收发与边界转换处后续新增字符集时只需要新增一对UTF-8 编解码器 排序规则而不需要改动核心执行引擎。与之相对的备选方案内部也直接用 GBK 存储与计算则因改动面过大、风险不可控而被否决详见后文备选方案取舍一节。四、模块级改造设计4.1 Encoder/Decoder编解码器封装字符集的读写能力被封装为独立的编解码组件实现上复用golang.org/x/text/encoding包提供的Transform函数文档同时强调各组件间尽量使用相同版本的该依赖避免转码结果不一致。4.2 Parser让解析器感知字符集解析器需要记录当前解析器所用的编码该编码由系统变量character_set_client决定执行Restore把 AST 还原为 SQL 文本时依据该编码输出 Restore 后的 SQL。同时文档要求审计所有ParseOneStmt/Parse调用点分两类处理对需要临时保存的 SQL 字符串必须附带字符集信息例如BindSQLSQL Binding 中的原始 SQL、View 的Select Stmt等对内部执行的 SQL 语句由于它们本来就是 UTF-8无需特殊处理例如perfschema包中创建系统表的语句。4.3 Runtime表达式层自动转码与排序规则强制力在collationInfo中新增repertoire字汇字段用于表达式求值时的自动字符集转换判断从而规避大量 illegal mix of collations 类错误。文档给出的类型示意如下type Repertoire int const ( // RepertoireASCII is pure ASCII and its Unicode range: U0000..U007F RepertoireASCII Repertoire 1 // RepertoireExtended is Extended characters and its Unicode range: U0080..UFFFF RepertoireExtended Repertoire 1 1 // RepertoireUnicode consists ASCII and EXTENDED, and its Unicode range: U0000..UFFFF RepertoireUnicode Repertoire ASCII | EXTENDED )注上述代码为设计文档中的示意文档原处ASCII | EXTENDED未加包名前缀实际工程实现中需使用常量名本身。部分与字符串计算相关的内置函数在 utf-8 与目标字符集相互转换后需要特殊处理文档引用 MySQL 8.0 string functions 手册作为对照并需在CoprocessorTiKV 下推执行器中同步处理对应函数审计并处理若干字符串相关的内部函数检查其是否需要对字符集做适配文档点名了DatumsToString、strToInt等。4.4 Optimizer统计信息与 Range 计算的字符集感知统计信息模块可能需要基于字符集做特殊处理的函数AvgColSizeDataInDiskByRows、GetFixedLen、BucketToString、DecodedString等涉及按字节/字符估算列宽时不同字符集下的结论截然不同Ranger 模块前缀长度prefix len的计算需要考虑字符集同一字符在不同字符集下字节数不同BuildFromPatternLike、checkLikeFunc等函数可能需要感知字符集保证LIKE通配符匹配在等宽/变宽字符集下语义一致。4.5 Collation三种排序规则的实现策略为 GBK 新增gbk_chinese_ci与gbk_bin排序规则同时出于性能考虑引入基于 utf8mb4 的gbk_utf8mb4_bin。要点如下开关依赖完整支持gbk_chinese_ci与gbk_bin需要开启new_collations_enabled_on_first_bootstrapTiDB 4.0 引入的新排序规则框架开关若该开关关闭则只支持gbk_utf8mb4_bin——因为它无需在比较前把数据转换为 GBK仍可按 utf8mb4 直接生成排序键兼容旧的行为路径接口实现为每种排序规则实现Collator与WildcardPattern两个接口gbk_chinese_ci、gbk_bin需要先把 UTF-8 字符串转为 GBK 编码再生成排序键下推同步TiKV/TiFlash 的 Coprocessor 中实现对应的比较与通配符匹配函数。4.6 DDL建库建表与在线变更DDL 需要支持在创建数据库、创建表、添加列时为库/表/列指定字符集并支持通过ALTER 语句修改列等对象的字符集该能力排入开发计划的第三阶段。4.7 其他配套行为可能需要修改TiKVMinVersion信息由于涉及存储/下推协议变更旧版本 TiKV 不兼容支持字符集设置类语句set character set gbk、set names gbk、set character_set_client gbk等支持字符集的正确展示show charset、show collationLOAD DATA导入路径中的LoadDataInfo.getLine需要按字符集正确处理行切分。五、兼容性设计5.1 TiDB 版本之间的兼容升级兼容滚动升级过程中执行相关操作可能存在兼容性问题升级完成后新版本集群读取旧数据预期无兼容问题降级兼容不支持降级。原因在于使用gbk_bin/gbk_chinese_ci的表其索引键是基于 GBK 语义生成的低版本 TiDB 解码这些索引键会出错若要降级必须先完成转码。5.2 与 MySQL 的兼容非法字符处理由于 TiDB 内部统一将非 UTF-8 编码转为 UTF-8 处理非法字符的表现与 MySQL 在部分场景下不完全一致TiDB 通过sql_mode控制行为排序规则仅在new_collations_enabled_on_first_bootstrap开启时完整支持gbk_bin与gbk_chinese_ci否则仅支持gbk_utf8mb4_bin。5.3 与外部组件的兼容矩阵文档对数据生态中的每个组件做了逐一分析数据库一旦存有 GBK 数据就必须使用对应版本或更高版本的组件组件影响与处理TiKVCoprocessor 内置函数与 collation 相关函数需处理/实现TiCDC按 TiCDC Open Protocol 输出时可能需要转换为 GBK 编码TiFlash内置函数与 collation 相关函数需处理/实现BR直接操作 SST 做备份恢复只重写 key 中的 table ID/index ID解码复用 TiDB codec 接口仅需更新 TiDB 依赖无兼容问题Lightning自行实现 SQL 解析为 KV 的逻辑可能需要特殊处理DM连接 TiDB 时当前固定指定 utf8mb4需处理同步 DDL 时用 TiDB-parser 解析当前 charset/collation 传空应改为从 binlog DDL event header 取 charset 传入——TiDB-parser 支持则直接处理不支持则回退空 charset/collation 保持旧行为Dumpling通过执行 SQL 获取表结构与数据无兼容问题TiDB-binlog输出仍为 utf8 编码无需特殊处理六、测试设计6.1 功能测试单元 集成覆盖支持的内置字符串函数在非 UTF-8 字符集下的行为含不同字符集间的转换测试TiDB/TiKV/TiFlash 的Coprocessor 下推测试覆盖不同字符集与排序规则范围的读写测试SET/展示 GBK 相关语句的测试从 mysql-tests 移植 gbk 字符集与相关排序规则测试。6.2 场景测试验证使用不同字符集进行混合排序的可行性。6.3 兼容性测试与已有功能的兼容SQL Binding、SQL Hints、聚簇索引clustered index、视图、表达式索引、statements_summary、slow_query、EXPLAIN ANALYZE等与外部组件的兼容确认各组件调用 utf-8/gbk 转码所依赖的库版本一致各组件下 GBK 相关操作可正常运行升级兼容低于 4.0 的版本不支持 gbk4.0 及以上滚动升级期间不支持 gbk 操作升级完成后支持降级兼容对使用 gbk 编码的表进行降级会存在不兼容问题。6.4 基准测试相同数据量下 utf-8 与 gbk 的读写性能对比gbk 下相关排序规则的性能测试。七、备选方案调研与取舍MySQL 的做法运行时与存储都用 gbk。服务器与客户端、连接与结果集之间字符集可能不同需要做字符集转换每种字符集实现专属接口以保证功能GBK 参考实现为 MySQL 8.0 的strings/ctype-gbk.ccCockroachDB当时不支持额外字符集无可借鉴被否决的 TiDB 备选方案收到 gbk 请求后转为 gbk 编码处理即运行时与存储层都按 gbk 计算/存储。否决原因在于要处理的点更复杂且不可控Datum等类型的部分 String 方法需要特殊处理ConvertToString、String()绝大多数 TiDB 字符串内置函数及字符串处理方法都要处理主要是大量函数签名与调用方式需要修改涉及stringutil包TiDB 依赖 Go 标准库stringsToUpper、ToLower、HasPrefix等与strconvAppendQuote、ParseInt、ParseFloat等这些库本身假设单字符长度固定改造代价大还需要ToGBKString这类全链路辅助函数。两相对比UTF-8 中转方案把复杂编码逻辑隔离在边界处改动面与风险显著更小。八、分阶段开发计划第一阶段TiDB 侧主体支持使用 gbk 的正常读写支持set charset gbk及展示 gbk 信息的语句支持常用字符串函数处理 gbk 编码字符支持字符集转换CAST/CONVERT等支持gbk_bin排序规则。第二阶段完成 TiKV、TiFlash 相关操作兼容 TiCDC、BR、Lightning、DM、Dumpling、TiDB-binlog 等组件支持gbk_chinese_ci排序规则。第三阶段基本覆盖 TiDB 已支持的全部字符串相关函数支持通过 ALTER 语句修改列字符集。九、新增字符集的工程指南接口级定义设计文档最后给出了一套新增字符集的操作手册为后续gb18030乃至更多字符集的接入铺路。9.1 定义编码实现 Encoding 接口新增字符集的第一件事是定义其编码/解码方式即实现Encoding接口。该接口在文档中以parser/charset/encoding.go中的设计为准当前仓库中已落地于 pkg/parser/charset/encoding.go关键契约如下// Encoding provide encode/decode functions for a string with a specific charset. type Encoding interface { // Name is the name of the encoding. Name() string // Tp is the type of the encoding. Tp() EncodingTp // Peek returns the next char. Peek(src []byte) []byte // MbLen returns multiple byte length, if the next character is single byte, return 0. MbLen(string) int // IsValid checks whether the utf-8 bytes can be convert to valid string in current encoding. IsValid(src []byte) bool // Foreach iterates the characters in current encoding. Foreach(src []byte, op Op, fn func(from, to []byte, ok bool) bool) // Transform map the bytes in src to dest according to Op. Transform(dest *bytes.Buffer, src []byte, op Op) ([]byte, error) // ToUpper change a string to uppercase. ToUpper(src string) string // ToLower change a string to lowercase. ToLower(src string) string }接口方法各司其职Peek用于按编码规则切出下一个字符MbLen判定双字节字符IsValid校验 UTF-8 字节能否无损转换到目标编码Transform完成 UTF-8 与目标编码间的批量双向转换Op控制方向与截断/替换策略ToUpper/ToLower则按目标字符集的大小写映射规则处理例如 GBK 对 0x80 字节、扩展拉丁字符有自己的大小写语义。9.2 定义排序规则实现 Collator 接口字符集确定后还必须为其挂上排序规则。文档给出的Collator接口设计如下当前仓库实现于 pkg/util/collate/collate.go接口在后缀版本中又扩展了ImmutableKey、KeyWithoutTrimRightSpace、Clone、MaxKeyLen等方法// Collator provides functionality for comparing strings for a given // collation order. type Collator interface { // Compare returns an integer comparing the two strings. The result will be 0 if a b, -1 if a b, and 1 if a b. Compare(a, b string) int // Key returns the collate key for str. If the collation is padding, make sure the PadLen len(rune[]str) in opt. Key(str string) []byte // Pattern get a collation-aware WildcardPattern. Pattern() WildcardPattern }其中Key把任意字符串映射为可字节序比较的排序键collation key——这正是索引能够按照拼音/二进制语义排序的关键Pattern返回字符集/排序规则感知的通配符匹配器供LIKE下推与执行使用。9.3 注册并接入支持列表完成接口实现后把新字符集与其排序规则加入支持列表TiDB 才能识别。以gb18030字符集为例设计文档展示了其 MySQL 兼容的 3 个排序规则在 pkg/parser/charset/charset.go 中可找到对应登记| gb18030_bin | gb18030 | 249 | | Yes | 1 | PAD SPACE | | gb18030_chinese_ci | gb18030 | 248 | Yes | Yes | 2 | PAD SPACE | | gb18030_unicode_520_ci | gb18030 | 250 | | Yes | 8 | PAD SPACE |设计文档建议至少实现gb18030_bin与gb18030_chinese_ci若未启用新排序规则框架默认排序规则应为gb18030_bin启用后则默认gb18030_chinese_ci。9.4 TiKV 侧同步由于大量表达式已下推至 TiKVTiKV 的components/tidb_query_datatype/src/codec/collation/mod.rs也需同步支持新字符集与排序规则保证下推路径上的比较与匹配结果与 TiDB 侧一致。十、从设计到落地当前仓库中的实现印证设计文档写于 2021 年提出的框架与接口在当前 TiDB 仓库中已经完整落地并扩展到了 GB18030。以下源码路径可帮助读者对照验证字符集编码注册表pkg/parser/charset/encoding.go 中的encodingMap已登记utf8mb4、utf8、gbk、latin1、bin、ascii、gb18030七种编码实现GBK 与 GB18030 均在IsSupportedEncoding覆盖范围内GBK 编解码实现pkg/parser/charset/encoding_gbk.go 内部基于golang.org/x/text/encoding/simplifiedchinese并通过customGBKDecoder对 0x80 字节做了特殊处理对应 issue #30581 的边界修复Peek/MbLen按 GBK 双字节规则首字节 0x81–0xFE、次字节 0x40–0xFE切分字符GBKCase定义了 GBK 特有的大小写映射字符集元信息表pkg/parser/charset/charset.go 的CharacterSetInfos记录了gbkMaxlen: 2默认排序gbk_chinese_ci与gb18030Maxlen: 4等字符集排序规则表同时登记了gbk_chinese_ciID 28见 pkg/parser/mysql/charset.go与gb18030系列ID 248/249/250GBK 排序规则实现gbk_binpkg/util/collate/gbk_bin.go 将字符串逐字符经 GBK 编码器转码后按字节比较生成排序键gbk_chinese_cipkg/util/collate/gbk_chinese_ci.go 借助gbkChineseCISortKey查表把 rune 映射为拼音排序键同时实现了字符集感知的WildcardPatterngbkChineseCIPatternGB18030 对应实现为 pkg/util/collate/gb18030_bin.go 与 pkg/util/collate/gb18030_chinese_ci.goCollator 注册pkg/util/collate/collate.go 的init()中通过newCollatorMap注册了gbk_bin、gbk_chinese_ci、gb18030_bin、gb18030_chinese_ci等并由GetCollator/GetCollatorWithCollate依据new_collations_enabled_on_first_bootstrap的开关状态分发到新排序规则实现或二进制排序回退实现RewriteNewCollationIDIfNeeded机制开启新排序规则时将 collation ID 取负正是为了让 TiKV 等组件无需改动协议即可感知新排序规则。由此可见本文所述的框架设计不仅是一份纸上提案——它如今已成为 TiDB 实际支持 GBK、GB18030 等字符集的基础架构。结语从 5 个字符集到可插拔的字符集框架TiDB 通过边界转码为 UTF-8 排序规则接口化的设计以可控的改动面换来了对 GBK、GB18030 等中文编码的原生支持并沉淀出一套可复用的扩展方法论新增字符集 实现Encoding编解码 实现Collator/WildcardPattern比较与匹配 注册到字符集/排序规则表 TiKV/TiFlash 下推同步。对于需要兼容中文存量系统、或未来可能引入更多非 UTF-8 字符集的用户与开发者而言理解这套框架的接口契约与改造边界比记住某个具体的字符集支持清单更有价值。【免费下载链接】tidbTiDB is built for agentic workloads that grow unpredictably, with ACID guarantees and native support for transactions, analytics, and vector search. No data silos. No noisy neighbors. No infrastructure ceiling.项目地址: https://gitcode.com/GitHub_Trending/ti/tidb创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表