30.4 locale、collation 与索引风险
collation 决定字符串怎样比较和排序。B-tree 在创建时把这种比较结果固化成页面内顺序; UNIQUE constraint 还依赖它判断两个字符串是否相等。操作系统的 libc、ICU 或 PostgreSQL builtin provider 发生变化后,heap 里的文本没有改变,旧索引却可能已经不再符合新比较 规则。
这是一个典型的“服务能启动、查询也能执行,但可能返回错结果”的升级风险。
30.4.1 libc、ICU 与排序规则版本
locale 是环境选择,collation 是数据库对象
需要分别记录:
database encoding
database default locale provider and locale
database recorded/actual collation version
column/expression-level COLLATE
user-defined pg_collation objects
libc / ICU / builtin provider and version
operating-system image and architecture在每个 database 中:
SELECT c.oid::regcollation AS collation,
c.collprovider,
c.collisdeterministic,
c.collcollate,
c.collctype,
c.colllocale,
c.collversion AS recorded_version,
pg_collation_actual_version(c.oid) AS actual_version
FROM pg_collation AS c
WHERE c.collversion IS DISTINCT FROM
pg_collation_actual_version(c.oid)
ORDER BY 1;对数据库默认 collation,还要检查 pg_database.datcollversion 与
pg_database_collation_actual_version(oid)。一个 cluster 有多个 database,逐库检查
不能省略。
provider 特征:
| provider | 版本来源与边界 |
|---|---|
| libc | 依赖 OS C library/locale data;版本号有时只是近似代理 |
| ICU | ICU 提供版本,跨平台能力强,但升级 ICU 仍可改变规则 |
| builtin | PostgreSQL 内建规则,减少 OS 漂移,但只覆盖其支持的 locale |
| default | 继承 database 默认 provider |
版本字符串相同也不是数学证明。发行版可能 backport locale 数据而没有按预期改变外层 版本;因此还应保留一组关键字符串排序与相等性 corpus。
warning 是门禁,不是自动修复
PostgreSQL 使用 collation 时会比较 recorded 与 actual version。若不一致,会提示:
rebuild affected objects
then REFRESH VERSION它不会自动知道所有外部语义,也不会替你安排锁和空间。OS patch、容器基础镜像、
跨发行版迁移与 pg_upgrade 都可能触发这条门禁,所以 collation 检查不应只在
PostgreSQL major upgrade 执行。
30.4.2 排序变化对唯一性和索引顺序的影响
B-tree 的正确性依赖比较函数稳定
假设旧规则认为:
a < ä < b新规则变成:
a < b < ä旧 index page 仍按第一种顺序排列。binary search 使用新 comparator 在旧顺序上导航,
可能漏行、返回错误 range,ORDER BY 也可能错误地相信 index 已有正确顺序。amcheck
文档明确把“索引 tuple 是否按 collation 逻辑顺序”列为 B-tree invariant。
UNIQUE 更危险。若旧规则认为两个值不同,新规则认为相等:
旧索引允许两行
-> 新规则下 REINDEX UNIQUE
-> duplicate key,重建失败此时不能先刷新版本并继续。需要业务 owner 决定:
- 规范化并合并重复值;
- 改为 deterministic collation;
- 改唯一键设计,加入稳定业务 ID;
- 保留旧 provider/locale,推迟环境变化。
若新规则只改变顺序、不改变相等性,仍需重建所有依赖排序的结构。
受影响的不只普通文本索引
盘点:
B-tree index and UNIQUE/PRIMARY constraints with collatable keys
expression index using collation-sensitive functions/operators
partition bounds and partitioned indexes
materialized views with ordered/normalized derived data
full-text and pg_trgm indexes called out by release notes
application pagination/bookmark built on locale ordering
cached sorted projections outside PostgreSQLhash index 不保存排序,但 collation-sensitive equality 和业务去重仍需按实际 operator 语义验证。不能把“只重建所有 text B-tree”当成对任意扩展 operator class 的完整规则。
query corpus 要覆盖等价类
建议保存:
SELECT value
FROM upgrade_collation_corpus
ORDER BY value COLLATE app.customer_name, corpus_id;
SELECT left_value,
right_value,
left_value = right_value COLLATE app.customer_name AS equal,
left_value < right_value COLLATE app.customer_name AS less
FROM upgrade_collation_pairs
ORDER BY pair_id;语料包含 case、accent、组合字符、数字字符串、标点、emoji、各业务语言和空白。比较 旧/新结果时,先区分“批准的排序变化”与“会破坏唯一性/分页的变化”。
30.4.3 识别受影响对象并规划重建
先列依赖,再排动作
官方给出的通用依赖查询:
SELECT pg_describe_object(
d.refclassid, d.refobjid, d.refobjsubid
) AS collation,
pg_describe_object(
d.classid, d.objid, d.objsubid
) AS dependent_object
FROM pg_depend AS d
JOIN pg_collation AS c
ON d.refclassid = 'pg_collation'::regclass
AND d.refobjid = c.oid
WHERE c.collversion <>
pg_collation_actual_version(c.oid)
ORDER BY 1, 2;版本字段可能为 NULL,因此生产查询通常同时使用 IS DISTINCT FROM,再根据 provider
判断哪些 NULL 是预期。对 index 可进一步从 pg_index.indcollation 精确列出:
SELECT i.indexrelid::regclass AS index_name,
i.indisunique,
pg_relation_size(i.indexrelid) AS bytes
FROM pg_index AS i
JOIN pg_collation AS c
ON c.oid = ANY(i.indcollation)
WHERE c.oid = 'app.en_numeric'::regcollation
ORDER BY bytes DESC;计划每个对象的:
rebuild command and whether CONCURRENTLY is supported
lock and blocking behavior
temporary and final disk headroom
WAL volume and replica lag
unique collision handling
estimated duration from clone rehearsal
validation query and owner顺序不能颠倒
正确顺序:
identify all affected objects
-> validate new equality/sort semantics
-> resolve unique conflicts
-> rebuild every affected object
-> run amcheck / query corpus
-> ALTER COLLATION ... REFRESH VERSION
-> verify mismatch set is emptyREFRESH VERSION 只把 catalog 中的 recorded version 更新为当前 provider version;
官方明确说明它不会检查对象是否已正确重建。若先 refresh,warning 消失了,旧索引
却还在,反而销毁了最显眼的故障信号。
数据库默认 collation 使用:
ALTER DATABASE app REFRESH COLLATION VERSION;也同样必须在所有依赖对象重建后执行。
正式实验的反例
本章 runner 在一次性 PG17 cluster 中精确把:
app.en_numeric.collversion = pg36-injected-stale
actual ICU version = 153.121
affected index = app.orders_order_code_key门禁立即变成 blocked。runner 只允许:
REINDEX INDEX app.orders_order_code_key;
ALTER COLLATION app.en_numeric REFRESH VERSION;完成后 recorded/actual 都为 153.121,mismatch 回到 false,且 10,000 行 logical
manifest 未改变。catalog update 是为了在可丢弃 fixture 中制造现象,生产中严禁手工
伪造 collversion;真实 mismatch 来自 provider/OS 变化。
上一节:扩展与依赖升级 · 返回本章目录 · 下一节:升级前检查与业务验证 · 查看全书目录 · 查看索引中心