TiDB 中文拼音排序方案设计解读:utf8mb4_zh_pinyin_tidb_as_cs 校对规则的原理与落地
【免费下载链接】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 仓库中的设计文档 2020-09-12-utf8mb4-pinyin-order.md 展开,系统讲解 TiDB 为utf8mb4字符集引入中文拼音排序所设计的全新校对规则(collation)utf8mb4_zh_pinyin_tidb_as_cs。文章将带你理解该规则的命名语义、底层排序权重的编码规则、Collation ID 2048 的选取理由、与既有排序规则的兼容矩阵,并对照当前仓库源码(parser 注册表、collator 映射、测试用例)梳理该特性从设计到代码的真实落地状态。读完你可以掌握:中文按拼音 ORDER BY 的问题根源、TiDB 自定义排序规则的完整设计套路,以及在当前版本中该 collation 已注册到哪一层、实现到什么程度。
背景:为什么 TiDB 需要"中文拼音排序"
在关系型数据库中,字符串ORDER BY的结果完全由列上声明的 collation 决定。对于中文场景,用户常见的诉求之一是"按拼音排序"——例如姓名、城市名、商品名等希望以拼音字母(a–z)为序。设计文档指出,彼时的 TiDB 无法基于拼音对一列汉字做排序。文档给出了一个具体示例:
create table t( a varchar(100) ) charset = 'utf8mb4' collate = 'utf8mb4_zh_0900_as_cs'; # insert some data: insert into t values ("中文"), ("啊中文"); # a query requires to order by column a in its pinyin order: select * from t order by a; +-----------+ | a | +-----------+ | 啊中文 | | 中文 | +-----------+ 2 rows in set (0.00 sec)utf8mb4_zh_0900_as_cs是 MySQL 提供的 UCA 9.0.0 中文排序规则(zh即 Chinese、as_cs即 accent-sensitive / case-sensitive)。文档引用该示例的用意在于说明:当"中文排序"的语义无法被正确、可预期地实现时,仅靠直接ORDER BY得到的结果并不能稳定满足业务对拼音序的预期。要真正解决这类问题,需要数据库内核提供语义正确且可复现的拼音排序能力。
补充:本提案讨论的背景演进可参见设计文档末尾列出的 issue #19747 与 #10192。
方案总览:utf8mb4_zh_pinyin_tidb_as_cs是什么
本提案的核心产出是新增一个名为utf8mb4_zh_pinyin_tidb_as_cs的校对规则。命名本身就是一份完整的规格说明,拆解如下:
| 命名片段 | 含义 |
|---|---|
utf8mb4 | 适用的字符集为utf8mb4 |
zh | 面向中文(Chinese)语言 |
pinyin | 提供基于拼音(pinyin)的排序次序 |
tidb | 表示这是 TiDB 的特殊(custom)版本 |
as_cs | accent-sensitive 且 case-sensitive(区分重音与大小写) |
能力边界
- 该规则支持全部 Unicode 字符参与排序,且能根据 CLDR24 中
zh.xml文件定义的拼音 collation 次序,对中文字符进行正确排序; - 目前只支持
zh.xml中拥有拼音条目的汉字;对于"字形与汉字相同、但 Unicode 归类为 Symbol(符号)的 CJK 字符"以及"拼音字符本身"均不提供拼音排序支持。
为什么不自研utf8mb4_zh_0900_as_cs(Advantages)
文档明确给出了选择自研而非实现 MySQL 同款规则的理由:实现utf8mb4_zh_0900_as_cs工作量大,MySQL 的实现方式涉及权重重排(weight reorders)、魔法数字(magic numbers)与大量技巧,复杂且难维护。相比之下,utf8mb4_zh_pinyin_tidb_as_cs实现路径简单直接:它覆盖全部汉字、按拼音次序排序,对本提案的目标场景"足够好"。
代价(Disadvantages)
它不兼容 MySQL——MySQL 中并不存在名为utf8mb4_zh_pinyin_tidb_as_cs的校对规则。这一不兼容性是后续所有迁移与同步方案设计的出发点。
核心设计:排序权重(Compare / Key)如何编码
排序的本质是比较"排序键(sort key / weight)"。设计文档给出了三条权值计算规则,它们决定了每个字符在utf8mb4_zh_pinyin_tidb_as_cs下的相对次序:
- 中文字符:凡在
zh.xml中按 gb18030 编码查到非零seq No.的汉字,其最终权重为0xFFA00000 + seq No.。 - 非中文的 gb18030 双字节字符 C:最终权重取
C本身。 - 非中文的 gb18030 四字节字符 C:最终权重为
0xFF000000 + diff(C)(其中diff通过算法求得)。
从这套编码可以读出几个设计意图(结合规则推演,属合理推断而非文档明示):
- 以gb18030 编码作为汉字与
zh.xml条目之间的桥梁,是因为 gb18030 覆盖整个 Unicode 码域(gb18030_chinese_ci.go 中即注释 "Unicode code points up to U+10FFFF can be encoded as GB18030"),任何 Unicode 字符都能先落到一个确定的 gb18030 字节序列,再映射为拼音序号; - 拼音序号被平移到
0xFFA00000这个高基地址上,与非中文双字节字符(权重即其自身,量级明显更小)天然拉开区间,从而"任意中文字符的拼音权重落在同一高位段、且严格按 seq 递增"; - 四字节非中文字符落在
0xFF000000基线之上,需要额外保证其diff值不会与汉字拼音区间(0xFFA00000起)产生重叠。按文档表述,这与双字节规则共同构成一个"非中文在前、中文按拼音在后"的整体次序模型。
整体看,这套方案的巧妙之处在于:不需要复刻 MySQL 那套繁复的 UCA 中文权重表,而是用"gb18030 编码 + zh.xml 拼音序号 + 区间化权值"三要素直接生成可比较的排序键。
Parser 侧落地:为 collation 挑选 ID 2048
一个可被 SQL 层识别的 collation,首先要在 parser 的字符集/排序规则注册表中拥有唯一 ID。提案选择 ID2048并写入 parser。
文档援引了 MySQL 官方的 ID 规划:MySQL 支持双字节 collation ID,其中1024–2047 区间预留给用户自定义排序规则(user-defined collations)。因此选择2048恰好落在该保留区间之外,可避免与 MySQL 用户自定义规则的 ID 空间冲突。
对照当前仓库源码,这条注册记录确实已经落地:pkg/parser/charset/charset.go 中存在{2048, "utf8mb4", "utf8mb4_zh_pinyin_tidb_as_cs", false, 1, PadNone},即字符集为utf8mb4、ID 为2048、padding 方式为PadNone。
与现有 collation 的兼容性矩阵
设计文档规定:utf8mb4_zh_pinyin_tidb_as_cs与utf8mb4_unicode_ci、utf8mb4_general_ci拥有相同优先级,三者彼此不兼容——即两个 collation 一旦混用(例如 JOIN 两表、比较不同 collation 的列),TiDB 不会自动将其视为可兼容并做隐式转换。
这个"兼容/不兼容"的判定在引擎侧有专门实现,可参考 collate.go 的CompatibleCollate:其中对general_ci家族、bin家族、unicode_ci家族分别做了"同族互认"的处理,其余情况严格按名字相等判定。中文拼音规则作为新成员不在任何既有家族内,自然只能与自身相等,与unicode_ci、general_ci均判定为不兼容,与文档描述一致。
与 MySQL 的兼容性及迁移建议
由于 MySQL 没有utf8mb4_zh_pinyin_tidb_as_cs这一 collation,文档给出的迁移指引很直接:
当用户需要把数据从 TiDB 复制(replicate)到 MySQL 时,应当对使用该 collation 的列做处理(如注释/改写 collation),避免下游 MySQL 无法解析。
换句话说,该规则是"TiDB 内可用、出 TiDB 需转换"的方言特性,适合在纯 TiDB 生态内部使用(排序、索引、主从均为 TiDB 的场景),一旦涉及 MySQL 下游同步就必须在 schema 层显式改写。
从设计到代码:该特性在当前仓库的落地现状
设计文档是 2020 年提出的方案,那么它在当前仓库中"落地到哪一步了"?结合源码可以给出精确答案。
1. 注册已就位:名字与 ID 双双进入映射表
除了 parser 侧的 ID 注册外,运行期的 collator 映射表也已登记:
- collate.go 同时将
"utf8mb4_zh_pinyin_tidb_as_cs"写入newCollatorMap与newCollatorIDMap,指向zhPinyinTiDBASCSCollator; - 由于
utf8mb4家族默认 collation 之外的名字统一经GetCollator/GetCollatorByID分发(见 collate.go),只要命中映射表即可取到对应 collator 实例。
2. 运行时实现仍是占位桩(stub)
打开 collator 本体文件 pinyin_tidb_as_cs.go,会发现结构体zhPinyinTiDBASCSCollator虽然实现了Collator接口的全部方法(Compare、Key、ImmutableKey、KeyWithoutTrimRightSpace、MaxKeyLen、Pattern、Clone),但所有方法体目前都是panic("implement me")占位。
这意味着:从当前仓库快照看,utf8mb4_zh_pinyin_tidb_as_cs的运行时排序逻辑尚未真正实现,仍处于开发中的状态。这一点与 collate.go 的注释相互印证:
// utf8mb4_zh_pinyin_tidb_as_cs is under developing, should not be shown to user. if name == "utf8mb4_zh_pinyin_tidb_as_cs" { continue }即GetSupportedCollations()(对应SHOW COLLATION)会显式过滤掉该 collation,不向用户展示。
3. 新排序规则开关与回退行为
TiDB 的"新排序规则(new collations)"是否启用(对应NewCollationEnabled()判定)会显著影响该 collation 的表现:
- new collations开启时,
GetCollator("utf8mb4_zh_pinyin_tidb_as_cs")与GetCollatorByID(2048)能命中上述 stub 实例; - new collations关闭时,GetCollatorWithCollate 与 GetCollatorByID 会直接回退到二进制(binary)collator;
- 另外,new collations 开启时引擎还会把 collation ID 编码为负数下发 TiKV(RewriteNewCollationIDIfNeeded),让存储侧感知自定义排序语义而无需改动协议。
上述两种开关下的行为都有测试覆盖:collate_test.go 分别在"新排序规则启用/关闭"两组用例中,用require.IsType断言GetCollator("utf8mb4_zh_pinyin_tidb_as_cs")与GetCollatorByID(2048)返回的分别是zhPinyinTiDBASCSCollator(启用时)与derivedBinCollator(回退时)。
4. 可参照的"同思路已完成"范本:gb18030 中文排序
提案中"汉字 → gb18030 编码 → 权值数据文件"的技术路线,在仓库中已有完成度很高的同族实现可供参照,即gb18030字符集的中文排序规则:
- gb18030_chinese_ci.go 通过
//go:embed gb18030_weight.data内嵌权重数据文件,运行期逐字符把 gb18030 序列换算为排序键后比较; - parser 侧对应注册了 ID 248/249/250 的
gb18030_chinese_ci、gb18030_bin、gb18030_unicode_520_ci(见 charset.go)。
可以推断,未来若完成utf8mb4_zh_pinyin_tidb_as_cs的运行时实现,最自然的路径就是参照gb18030_*系列:"预生成/内嵌一份基于 gb18030 码点与拼音 seq 的权值数据,用数据驱动替代手写算法",与设计文档中0xFFA00000 + seq No.的公式完全吻合。
延伸讨论:替代路线与后续工作
设计文档提到的替代路线是 MySQL 官方utf8mb4_zh_0900_as_cs——它是 MySQL 用于拼音序的语言专属 collation 之一。TiDB 之所以没有直接复刻,核心障碍已在前文说明:其实现依赖大量权重重排与魔法数值,维护成本与出错风险高。
而本提案的取舍是:用一个命名上明确标注tidb方言、实现上依赖 CLDR zh.xml 拼音序号 + gb18030 编码桥接的自研规则,换取实现简单与语义正确,代价则是与 MySQL 的不兼容,并在跨库同步时需要显式改写。
当前仓库的状态表明:该 collation 的规格与注册层已经定型(名字、ID 2048、collator 类型映射、兼容性定位、对外隐藏策略),而运行时比较逻辑仍待实现。后续工作可沿着两个方向推进:其一,为zhPinyinTiDBASCSCollator填充真实实现并配套中文拼音排序的单元/集成测试;其二,在实现完成后放开 GetSupportedCollations 中的过滤逻辑,使其对用户可见可用。设计文档末尾亦将 issue #19747、#10192 列为开放问题,供持续跟踪。
参考与延伸阅读
- 设计文档原文:docs/design/2020-09-12-utf8mb4-pinyin-order.md
- Parser 侧 collation 注册表(含 ID 2048):pkg/parser/charset/charset.go
- Collator 实例注册与展示过滤: pkg/util/collate/collate.go、collate.go
- 运行时占位实现: pkg/util/collate/pinyin_tidb_as_cs.go
- 排序规则兼容性判定与 collator 分发: pkg/util/collate/collate.go
- 行为测试(启用/关闭两种状态): pkg/util/collate/collate_test.go
- 同思路的 gb18030 中文排序实现(可作实现范本): pkg/util/collate/gb18030_chinese_ci.go、charset.go
【免费下载链接】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),仅供参考