在 PostgreSQL 中,排序规则(Collation)往往是最容易被忽略,却最致命的性能选项之一。
很多人建表时习惯性写下:
text COLLATE "en_US.utf8"
然后就再也没管过它。
直到某一天你发现:
索引构建慢得离谱
ORDER BY CPU 飙升
数据量一上来,性能线性崩塌
你可能会怀疑:
👉 是数据量太大?
👉 是索引设计不合理?
👉 是 PostgreSQL 本身“不够快”?
但在 PostgreSQL 18 里,真相可能非常简单——你只是选错了排序规则。
本文将通过 500 万行真实压测数据,
从内核原理到实战结果,彻底回答三个关键问题:
为什么 OS Locale 会成为 CPU 杀手?
Built-in Collation 到底快在哪里?
PostgreSQL 18 下,排序规则该怎么选才不踩坑?
如果你正在使用 PG 18,这可能是你今年最值得花 10 分钟读完的一篇文章。
01
深度原理:为什么排序规则是
性能的“命门”?
在 PostgreSQL 中,Collation (排序规则) 决定了字符串如何进行比较(Compare)和排序(Sort)。
这不仅仅是“谁排在前、谁排在后”的问题,它直接决定了数据库底层调用哪套 C 语言函数库。
在 PG 18 中,我们主要面临三种选择,它们的底层机制天差地别:
1
COLLATE "C" (Raw / Memcmp)
底层机制
按字节逐位比较 (Byte-wise)。
核心函数
直接调用 C 标准库的 memcmp()。
原理详解
它完全忽略字符集、语言习惯和 Unicode 规则。
它只看二进制编码的大小。
例如,在 ASCII 码中 Z (90) 小于 a (97)。
性能特征
这是 CPU 指令级别的比较,没有任何逻辑开销,代表了物理硬件的极限速度。
但缺点是无法处理人类语言的复杂排序习惯(如中文拼音)。
2
COLLATE "en_US.utf8" (OS Libc / Glibc)
底层机制
依赖操作系统的语言学比较。
核心函数
调用操作系统库(如 Linux 的 glibc)的 strcoll()。
原理详解
这是最重的实现方式。
PG 进程必须将字符串传递给操作系统,操作系统需要进行复杂的计算:
a. 标准化 (Normalization):处理 Unicode 等价性(如字符 é 可以是单字符,也可以是 e + ´ 组合)。
b. 多级权重 (Weighting):查表处理主次权重(忽略大小写、处理重音、处理连字等)。
c. 上下文切换:从 PG 用户态到 OS 库函数的频繁调用。
性能特征
这是绝大多数生产环境的默认配置,也是CPU 杀手。
3
COLLATE "pg_c_utf8" (PG 18 Built-in)
底层机制
PG 内核内置实现 (Provider: b)。
核心函数
PG 源码中的 uni_strcoll() 等内部实现。
原理详解
这是 PostgreSQL 17/18 的革命性改进。
PG 不再把任务外包给操作系统,而是自己实现了一套高效的 UTF-8 排序逻辑。
1)去重
剔除了 glibc 中许多不常用的、沉重的语言学规则。
2)内联
减少了外部库调用的上下文切换开销。
3)不可变 (Immutable)
这是最大的架构优势。
OS 升级(如 glibc 版本变化)会导致索引损坏,而 Built-in 永远不变。
性能特征
试图在“正确处理 UTF-8”和“高性能”之间找到最佳平衡点。
02
实验环境与数据准备
我们构建了一张包含 500 万行数据的宽表,确保三列数据完全一致。
1
建表与数据灌入
03
实战 A —— 索引构建 (Index
Build) 三轮压测
这是 CPU 密集型操作的大考。
为了消除偶发波动,我们进行了三轮完整的测试。
1
实验背景:寻找 V18 的性能红利
在 PostgreSQL 18 中,社区引入了 Built-in Collation Provider,旨在摆脱操作系统 glibc 的性能束缚。
为了验证这一特性的真实威力,我们在同一环境下进行了三轮严苛的索引构建(Index Build)测试。
测试对象
500 万行随机文本数据。
测试动作
CREATE INDEX (CPU 密集型操作)。
对比选手:
a. Raw "C"
物理极限(仅比较字节)。
b. Built-in "pg_c_utf8"
PG 18 内置实现(UTF-8 感知)。
c. OS Libc "en_US.utf8"
操作系统默认实现(传统方式)。
2
实战数据:三轮测试全记录
第一轮测试 (冷启动/Warm-up)
环境状态:数据刚写入,可能部分未进缓存。
第二轮测试 (稳定期)
环境状态:缓存预热中。
第三轮测试 (热数据巅峰)
环境状态:完全热数据,性能释放。
我们对同一批数据进行了三次反复的索引构建,以消除缓存预热(Warm-up)和磁盘 I/O 的偶发影响。
数据汇总表

3
深度技术剖析
OS Libc 的全面溃败 (平均 13.47s)
这是本实验最震撼的发现。使用操作系统默认的 en_US.utf8,索引构建速度平均约为 13.5秒。
原因
每次字符串比较,PG 都要切换上下文调用操作系统的 strcoll(),并进行繁重的语言学权重计算(Weighting)。
代价
相比 Built-in,它慢了接近 3 倍。
这意味着如果你的生产库使用默认配置,浪费了 65% 的 CPU 资源在无意义的排序逻辑上。
Built-in 的惊人表现 (平均 4.71s)
无限逼近物理极限
在第三轮测试中,Built-in (3.5s) 甚至跑赢了 Raw C (3.8s)。
虽然这是系统调度波动,但足以证明 Built-in 的开销已经低到可以忽略不计。
技术红利
PG 18 通过内置实现,剔除了 OS 库中冗余的逻辑,实现了与 memcmp (C) 几乎同级的效率,同时还保证了 UTF-8 的正确性。
Raw "C" 的基准地位 (平均 4.17s)
它依然是最稳的基准线。
但在 V18 时代,Built-in 已经追平了它。
考虑到 Built-in 能处理多字节字符的边界问题,"C" 的唯一优势只剩下处理非 UTF-8 数据的纯字节流场景。
4
为什么第三轮 Built-in 会反超 C?
在第三轮中,Built-in (3.54s) 竟然比 C (3.83s) 还快,这看似不科学(逻辑比较怎么可能快过字节比较?),但在高并发或热数据场景下是有可能的:
CPU 流水线与分支预测
PG 18 的 Built-in 实现经过高度优化,代码路径可能更利于现代 CPU 的分支预测。
内存对齐
在特定的内存页分布下,Built-in 处理的数据可能恰好命中更多的 L1/L2 Cache。
结论
这说明两者处于同一重量级。
在 V18 中,Built-in 就是带了 UTF-8 校验功能的 "C"。
04
实战 B —— 查询与排序
(Select & Sort)
我们继续对“读性能”进行压测,分为 走索引 和 不走索引 两个极端场景。
1
场景 1:索引扫描 (Index Scan) ——
毫秒级较量
实验背景:微秒级的极致响应
本次测试我们将视角转向数据库最常见的 OLTP 场景:利用 B-Tree 索引进行的极速点查。
我们将环境锁定为 全内存热数据 (Hot Cache),确保索引页(Index Pages)完全驻留在内存中。
数据量:500 万行。
内存配置:shared_buffers = 1GB (确保 Heap 和 Index 全部热加载进内存,无物理 I/O)。
测试场景:ORDER BY ... LIMIT 1 (利用 B-Tree 索引直接定位最小值)。
核心看点:
当算法复杂度从 O(N)O(N) 降维打击到 O(logN)O(logN) 时,查询响应时间被压缩到了 微秒级 (0.x ms)。
在这种极致的低延迟下,Collation 函数调用的启动开销 (Overhead) 和CPU 流水线效率将成为决胜的关键。我们试图捕捉那 0.1 毫秒的微小差异。
实验过程:索引扫描场景
实验结论:三轮实测数据汇总 (单位: 毫秒)

深度结论分析
a. 微秒级的战争 (0.2ms - 0.4ms)
在热数据下,三个选手的差距已经缩小到了 0.1毫秒 级别。
这说明当使用了 B-Tree 索引后,PG 只需要读取极少量的内存页(Page),排序规则的 CPU 开销被最大程度摊薄了。
b. Built-in 的惊人反超 (0.252 ms)
在第二轮和第三轮中,Built-in 居然跑出了全场最快的数据,甚至快于 Raw C。
原因推测
虽然理论上 C 最快,但在微秒尺度下,CPU 缓存命中率(Cache Locality)和指令流水线优化起主导作用。PG 18 的 Built-in 实现代码可能更加“现代”和紧凑,导致其在特定场景下的分支预测极其高效。
核心意义
这证明了 Built-in Collation 没有任何运行时负担。
它就是“带了 UTF-8 校验功能的 C”。
2
场景 2:全表扫描 + 内存排序 (Seq Scan +
Sort) —— CPU 算力大考
实验背景:纯粹的 CPU 算力大考
在之前的测试中,我们分别看到了 IO 瓶颈(冷启动)和全量排序(Sort)的表现。 本次测试我们将环境锁定为:内存热数据 (Hot Cache)。
数据量
500 万行。
内存配置
shared_buffers = 1GB (足以容纳全表)。
测试场景
ORDER BY ... LIMIT 1(全表扫描 + Top-1 堆排序)。
核心看点
当磁盘 I/O 不再是瓶颈时,三种排序规则在 CPU 指令层面的微小差异将被放大 500 万倍。
实验过程:内存热数据排序压测
为了排除磁盘 I/O 干扰,我们利用刚才跑过的数据(已在内存中),测试纯 CPU 性能。
实战数据:三轮热数据测试全记录
为了确保数据准确,我们连续进行了三轮测试,结果极其稳定。
数据汇总表 (单位: ms)

深度技术剖析
1)Raw "C" 的回归 (374ms)
在排除了磁盘 I/O 的干扰后,COLLATE "C" 终于回到了它应有的王座。
性能基准
374ms 代表了遍历 500 万行数据并进行 500 万次 memcmp 操作的物理时间下限。
对比意义
它是所有其他算法想要追求的“光速”。
2)Built-in 的惊艳表现 (393ms)
这是本次实验最令人振奋的数据!
差距仅 5%
PG 18 的内置实现(393ms)仅比纯字节比较(374ms)慢了 19毫秒!
这意味着什么?
这意味着 PostgreSQL 18 的内核团队已经将 UTF-8 的处理逻辑优化到了极致。
它在保证了“UTF-8 字符正确性”的同时,性能几乎没有损耗。
技术红利
你可以放心地使用 Built-in 来处理中文、日文或 emoji,而不用担心比 Raw C 慢多少。
3)OS Libc 的严重滞后 (516ms)
落后 31%
相比 Built-in,操作系统默认的 glibc 实现慢了 123毫秒。
为什么慢?
在 500 万次比较中,每一次调用操作系统的 strcoll() 都要比调用 PG 内部函数多消耗一点点 CPU 周期(用于查权重表、处理语言学规则)。
积少成多,最终导致了 30% 的性能惩罚。
05
最终技术总结
通过这场涵盖 500万行数据 的全场景压测,PostgreSQL 18 的性能表现已无可争议:

基于以上三轮索引构建和三轮全表排序的详实数据,我们对 PostgreSQL 18 的性能表现得出最终结论:
Built-in (pg_c_utf8) 是通用文本的最佳选择
在索引构建中,它比 OS Libc 快 4 倍 (3.5s vs 15.9s);在全表排序中,它比 OS Libc 快 30%。它兼顾了高性能和 UTF-8 正确性。
Raw (C) 依然是物理极限
对于不需要 UTF-8 校验的内部编码(UUID/Hash),它配合索引能提供 2.9ms 的极致响应。
OS Libc (en_US.utf8) 慎重使用
无论是在写(索引构建)还是读(排序)场景下,它都是最慢的。
在 V18 中继续使用它是对硬件资源的极大浪费。
V18 最佳实践铁律
1)通用文本字段,无脑选 Built-in
实验数据证明,COLLATE "pg_c_utf8"在索引构建上比 OS 快 400%,在查询上甚至能反超 Raw C。
它是 V18 时代通用文本处理的唯一真神。
2)慎重使用 OS Locale
en_US.utf8 在所有测试中均垫底。它不仅慢,还存在升级导致索引损坏的风险。
3) Raw C 依然是底座
对于不需要 UTF-8 校验的内部编码(UUID/Hash),COLLATE "C" 依然是稳健的选择。
写在最后
这场覆盖 索引构建、索引扫描、全表排序 的完整实测,其实已经给 PostgreSQL 18 用户一个非常明确的答案:
排序规则,不再只是“语言习惯”的选择,而是纯粹的性能决策。
在 PG 18 之前,我们几乎别无选择,只能把排序逻辑外包给操作系统,忍受 glibc 带来的性能损耗和升级风险。
但在 PostgreSQL 18 之后,情况已经彻底改变:
Built-in Collation 用接近 memcmp 的速度,完成 UTF-8 正确排序
索引构建速度提升 3~4 倍
全表排序 CPU 成本直接下降 30%
还顺手解决了“OS 升级导致索引失效”的历史顽疾
对今天的 DBA 和架构师来说,继续无脑使用 en_US.utf8,已经不再是“稳妥”,而是一种浪费。
PostgreSQL 18 真正带来的改变,不是“又快了一点”,而是让我们终于可以:
👉 把性能关键路径,牢牢掌握在数据库内核本身。
如果说 PG 18 有什么“必用新特性”,Built-in Collation,毫无疑问就在第一梯队。
原文链接:网页链接