PostgreSQL 中 create database 中的注意事项

文章目录

  • 说明
  • [PostgreSQL 中 `locale_provider = icu` 的具体实例](#PostgreSQL 中 locale_provider = icu 的具体实例)
    • 一、前提条件说明
      • [1️⃣ PostgreSQL 版本要求](#1️⃣ PostgreSQL 版本要求)
      • [2️⃣ 编译 / 安装要求](#2️⃣ 编译 / 安装要求)
    • [二、最常见的 `CREATE DATABASE` 示例(推荐)](#二、最常见的 CREATE DATABASE 示例(推荐))
      • [示例 1:使用 ICU 作为数据库级 Locale Provider](#示例 1:使用 ICU 作为数据库级 Locale Provider)
      • 关键点说明
    • [三、英文 / 中文 / 自定义排序示例](#三、英文 / 中文 / 自定义排序示例)
      • [示例 2:英文(美国)](#示例 2:英文(美国))
      • [示例 3:简体中文(推荐写法)](#示例 3:简体中文(推荐写法))
    • [四、连接数据库后验证是否真的使用 ICU](#四、连接数据库后验证是否真的使用 ICU)
      • [1️⃣ 查看数据库级 locale provider](#1️⃣ 查看数据库级 locale provider)
      • 正确结果示例
    • [五、排序行为验证示例(ICU vs libc 差异)](#五、排序行为验证示例(ICU vs libc 差异))
      • 示例表
      • [ICU 排序(按拼音)](#ICU 排序(按拼音))
    • [六、和 ICU COLLATION 的关系(容易混淆)](#六、和 ICU COLLATION 的关系(容易混淆))
      • [PostgreSQL 10--14(只能这样)](#PostgreSQL 10–14(只能这样))
      • [PostgreSQL 15+(数据库级 ICU)](#PostgreSQL 15+(数据库级 ICU))
    • 七、常见错误汇总
      • [❌ 错误 1:忘记 `template0`](#❌ 错误 1:忘记 template0)
      • [❌ 错误 2:PostgreSQL < 15](#❌ 错误 2:PostgreSQL < 15)
    • 八、一句话总结(重点)
  • [`initdb --lc-collate=C --lc-ctype=C` 与 `CREATE DATABASE locale_provider` 的对应关系](#initdb --lc-collate=C --lc-ctype=CCREATE DATABASE locale_provider 的对应关系)
    • 一、先给结论(一句话版)
    • 二、核心原理(理解这点就不会用错)
      • [1️⃣ `initdb` 决定的是 **集群默认 locale provider**](#1️⃣ initdb 决定的是 集群默认 locale provider)
      • [2️⃣ `CREATE DATABASE` 的规则(PostgreSQL 15+)](#2️⃣ CREATE DATABASE 的规则(PostgreSQL 15+))
    • [三、与你的 initdb 参数一一对应(重点)](#三、与你的 initdb 参数一一对应(重点))
      • [✅ 对应方式 1:继续使用 `C`(推荐、最简单)](#✅ 对应方式 1:继续使用 C(推荐、最简单))
      • [❌ 错误写法(不推荐)](#❌ 错误写法(不推荐))
    • [四、如果你想"从 C 切换到 ICU",正确写法是这样](#四、如果你想“从 C 切换到 ICU”,正确写法是这样)
      • [为什么必须 `template0`?](#为什么必须 template0?)
    • 五、完整对照表(建议收藏)
      • [initdb vs CREATE DATABASE 对照](#initdb vs CREATE DATABASE 对照)
    • 六、验证方法(强烈建议你自己验证一次)
      • [libc + C 的典型结果](#libc + C 的典型结果)
      • [ICU 的典型结果](#ICU 的典型结果)
    • [七、给 DBA 的实践建议(结合你背景)](#七、给 DBA 的实践建议(结合你背景))
      • [🔹 集群级(initdb)](#🔹 集群级(initdb))
      • [🔹 业务级(数据库级)](#🔹 业务级(数据库级))
  • [关于 `--lc-collate=C --lc-ctype=C` 是否会导致跨操作系统排序不一致的澄清](#关于 --lc-collate=C --lc-ctype=C 是否会导致跨操作系统排序不一致的澄清)
  • [为什么 ICU 也"并不总是按中文拼音排序"?以及该如何正确看待它](#为什么 ICU 也“并不总是按中文拼音排序”?以及该如何正确看待它)
  • 疑问:
  • 你说得**完全正确**:之前那个说法不严谨,需要纠正
    • 一、先给结论(修正后的准确说法)
    • [二、严格解释 PostgreSQL 的真实机制(不模糊)](#二、严格解释 PostgreSQL 的真实机制(不模糊))
      • [1️⃣ initdb 决定了三件事](#1️⃣ initdb 决定了三件事)
      • [2️⃣ 用 SQL 直接验证(最硬证据)](#2️⃣ 用 SQL 直接验证(最硬证据))
      • [3️⃣ 两种 initdb 的真实结果对比](#3️⃣ 两种 initdb 的真实结果对比)
        • [情况 A:`initdb --lc-collate=C --lc-ctype=C`](#情况 A:initdb --lc-collate=C --lc-ctype=C)
        • [情况 B:`initdb --locale=en_US.UTF-8`](#情况 B:initdb --locale=en_US.UTF-8)
    • [三、为什么"template1 是 libc + C"这种说法会流传?](#三、为什么“template1 是 libc + C”这种说法会流传?)
      • [原因 1️⃣:生产环境最佳实践](#原因 1️⃣:生产环境最佳实践)
      • [原因 2️⃣:PostgreSQL 15 之前没有 locale_provider](#原因 2️⃣:PostgreSQL 15 之前没有 locale_provider)
    • 四、严格、不会误导的正确表述(建议你以后用)
    • [五、`CREATE DATABASE` 的继承规则(精确版)](#五、CREATE DATABASE 的继承规则(精确版))
    • 六、为什么你能一眼看出"不严谨"?
    • 七、最终一句话定论(修正版)
  • [PostgreSQL 中 initdb / template / CREATE DATABASE / ICU 的**完整继承关系图**](#PostgreSQL 中 initdb / template / CREATE DATABASE / ICU 的完整继承关系图)
  • [PostgreSQL Locale 决策**流程图 + 设计规范**(完整版,可直接落地)](#PostgreSQL Locale 决策流程图 + 设计规范(完整版,可直接落地))
  • [一、PostgreSQL Locale 决策流程图(一步一步选)](#一、PostgreSQL Locale 决策流程图(一步一步选))
    • [1️⃣ 决策流程图(Mermaid)](#1️⃣ 决策流程图(Mermaid))
    • [2️⃣ 这张图解决了哪些"经典争议"](#2️⃣ 这张图解决了哪些“经典争议”)
  • [二、《PostgreSQL Locale 设计规范》(生产级)](#二、《PostgreSQL Locale 设计规范》(生产级))
    • [1️⃣ 设计目标](#1️⃣ 设计目标)
    • [2️⃣ 总体原则(必须遵守)](#2️⃣ 总体原则(必须遵守))
      • [**原则 1:集群层统一使用 C locale**](#原则 1:集群层统一使用 C locale)
      • [**原则 2:template 只是继承工具,不是配置来源**](#原则 2:template 只是继承工具,不是配置来源)
      • [**原则 3:CREATE DATABASE 默认不写 locale**](#原则 3:CREATE DATABASE 默认不写 locale)
    • [3️⃣ ICU 使用规范(非常严格)](#3️⃣ ICU 使用规范(非常严格))
      • [✅ 允许使用 ICU 的场景](#✅ 允许使用 ICU 的场景)
      • [❌ 禁止使用 ICU 的场景](#❌ 禁止使用 ICU 的场景)
      • [ICU 建库**唯一合法写法**](#ICU 建库唯一合法写法)
      • [ICU 运维强制项](#ICU 运维强制项)
    • [4️⃣ 中文排序的**唯一推荐方案**](#4️⃣ 中文排序的唯一推荐方案)
    • [5️⃣ 强制校验 SQL(巡检用)](#5️⃣ 强制校验 SQL(巡检用))
    • [6️⃣ 一句话红线(必须写进规范)](#6️⃣ 一句话红线(必须写进规范))
  • 三、最终总结(给"专家评审"用)

说明

本文通过咨询 GPT 生成

PostgreSQL 中,从 PostgreSQL 15 开始,CREATE DATABASE 支持使用 LOCALE_PROVIDER = icu 这样的 ICU 区域设置提供程序属性。这个特性允许在创建数据库时指定使用 ICU 作为区域设置提供程序(locale provider),不仅限于为单个排序规则或字符分类创建 ICU collation。在 15 之前的版本虽然已经引入了 ICU 支持(例如可以用于 collation),但不能在全局数据库层面通过 LOCALE_PROVIDER 来指定 ICU。(Peter Eisentraut1)

总结:

  • PostgreSQL 10:首次引入 ICU 支持用于创建 collation(但不能用于数据库层面的 locale provider)。(Peter Eisentraut1)
  • PostgreSQL 15 :加入了可以在初始化实例或创建数据库时使用 ICU 作为默认 locale provider 的功能(即支持 LOCALE_PROVIDER = icu)。(Peter Eisentraut1)

因此,如果你希望在执行:

sql 复制代码
CREATE DATABASE mydb
  LOCALE_PROVIDER = icu
  ICU_LOCALE = 'fr-FR-x-icu';

PostgreSQL 中 locale_provider = icu 的具体实例

下面我结合 PostgreSQL 15+ ,给你几个可直接使用的完整示例,从初始化、创建数据库到验证效果,一步步说明。


一、前提条件说明

1️⃣ PostgreSQL 版本要求

  • 必须 ≥ PostgreSQL 15
  • 低于 15 会直接报错:
text 复制代码
ERROR:  syntax error at or near "locale_provider"

2️⃣ 编译 / 安装要求

  • PostgreSQL 必须 启用 ICU
  • 验证方式:
sql 复制代码
SHOW icu_version;

如果返回 ICU 版本号(如 72.1),说明支持。


二、最常见的 CREATE DATABASE 示例(推荐)

示例 1:使用 ICU 作为数据库级 Locale Provider

sql 复制代码
CREATE DATABASE test_icu
  WITH
  LOCALE_PROVIDER = icu
  ICU_LOCALE = 'zh-Hans-CN'
  TEMPLATE = template0;

关键点说明

参数 说明
LOCALE_PROVIDER = icu 指定使用 ICU
ICU_LOCALE ICU 的 locale 名称
TEMPLATE = template0 必须,否则会继承 template1 的 libc locale

⚠️ 这是很多人踩坑的点

如果不使用 template0,数据库 locale 会被模板库污染。


三、英文 / 中文 / 自定义排序示例

示例 2:英文(美国)

sql 复制代码
CREATE DATABASE test_icu_en
  LOCALE_PROVIDER = icu
  ICU_LOCALE = 'en-US'
  TEMPLATE = template0;

示例 3:简体中文(推荐写法)

sql 复制代码
CREATE DATABASE test_icu_zh
  LOCALE_PROVIDER = icu
  ICU_LOCALE = 'zh-Hans'
  TEMPLATE = template0;

常见可用 ICU locale:

  • zh
  • zh-Hans
  • zh-Hans-CN
  • zh-Hant
  • en
  • en-US
  • de-DE

四、连接数据库后验证是否真的使用 ICU

1️⃣ 查看数据库级 locale provider

sql 复制代码
SELECT
  datname,
  datlocprovider,
  daticulocale
FROM pg_database
WHERE datname = 'test_icu';

正确结果示例

text 复制代码
 datname  | datlocprovider | daticulocale
----------+----------------+--------------
 test_icu | i              | zh-Hans-CN

字段说明:

字段 含义
datlocprovider = 'i' ICU
datlocprovider = 'c' libc

五、排序行为验证示例(ICU vs libc 差异)

示例表

sql 复制代码
CREATE TABLE t_name (name text);

INSERT INTO t_name VALUES
('张三'),
('李四'),
('王五'),
('赵六');

ICU 排序(按拼音)

sql 复制代码
SELECT name FROM t_name ORDER BY name;

ICU 下更接近拼音排序(而不是 Unicode 编码顺序)。


六、和 ICU COLLATION 的关系(容易混淆)

PostgreSQL 10--14(只能这样)

sql 复制代码
CREATE COLLATION zh_icu (
  PROVIDER = icu,
  LOCALE = 'zh-Hans'
);

⚠️ 但 数据库本身仍是 libc


PostgreSQL 15+(数据库级 ICU)

sql 复制代码
CREATE DATABASE test_icu
  LOCALE_PROVIDER = icu
  ICU_LOCALE = 'zh-Hans'
  TEMPLATE = template0;

✔️ 数据库默认排序、字符分类全部走 ICU


七、常见错误汇总

❌ 错误 1:忘记 template0

sql 复制代码
CREATE DATABASE test_icu
  LOCALE_PROVIDER = icu
  ICU_LOCALE = 'zh-Hans';

结果:

数据库可能仍是 libc


❌ 错误 2:PostgreSQL < 15

text 复制代码
ERROR: unrecognized parameter "locale_provider"

八、一句话总结(重点)

CREATE DATABASE ... LOCALE_PROVIDER = icu 从 PostgreSQL 15 开始支持

并且 必须使用 template0 才能真正生效

initdb --lc-collate=C --lc-ctype=CCREATE DATABASE locale_provider 的对应关系

你这个问题问得非常专业,也非常关键 ,很多人在 PostgreSQL 15+ + ICU 这里都会混乱。下面我直接给你结论 + 对照表 + 正确用法


一、先给结论(一句话版)

initdb --lc-collate=C --lc-ctype=C 等价于:

  • 集群级别使用 libc
  • locale 为 C

CREATE DATABASE 时:

👉 不要再写 LOCALE_PROVIDER = libc

👉 只需要 LC_COLLATE = 'C' LC_CTYPE = 'C'

locale_provider 只有在你要"切换到 ICU"时才需要显式指定。


二、核心原理(理解这点就不会用错)

1️⃣ initdb 决定的是 集群默认 locale provider

你执行的是:

bash 复制代码
initdb --lc-collate=C --lc-ctype=C

等价于:

  • 集群默认 locale provider:libc

  • template0 / template1 都是:

    • LC_COLLATE = C
    • LC_CTYPE = C

2️⃣ CREATE DATABASE 的规则(PostgreSQL 15+)

你在 CREATE DATABASE 中写什么 实际含义
什么都不写 继承 template1(libc + C)
LC_COLLATE / LC_CTYPE libc
LOCALE_PROVIDER = icu 切换到 ICU
ICU_LOCALE = ... 仅 ICU 有效

👉 locale_provider 是"切换开关",不是必须参数


三、与你的 initdb 参数一一对应(重点)

✅ 对应方式 1:继续使用 C(推荐、最简单)

sql 复制代码
CREATE DATABASE db_c
  LC_COLLATE = 'C'
  LC_CTYPE   = 'C';

或更简单(完全继承 template1):

sql 复制代码
CREATE DATABASE db_c;

✔️ 这是你当前 initdb 参数的"标准延续"


❌ 错误写法(不推荐)

sql 复制代码
CREATE DATABASE db_c
  LOCALE_PROVIDER = libc
  LC_COLLATE = 'C'
  LC_CTYPE = 'C';

原因:

  • libc 本来就是默认
  • 多此一举,且容易让人误以为需要写

四、如果你想"从 C 切换到 ICU",正确写法是这样

⚠️ 这是唯一需要 locale_provider 的场景

sql 复制代码
CREATE DATABASE db_icu
  LOCALE_PROVIDER = icu
  ICU_LOCALE = 'zh-Hans'
  TEMPLATE = template0;

为什么必须 template0

因为:

  • template1 是 libc + C
  • ICU 数据库 不能继承 libc locale
  • PostgreSQL 强制要求使用 template0

五、完整对照表(建议收藏)

initdb vs CREATE DATABASE 对照

initdb 参数 create database 对应写法
--lc-collate=C LC_COLLATE = 'C'
--lc-ctype=C LC_CTYPE = 'C'
libc(默认) 什么都不写
ICU LOCALE_PROVIDER = icu + ICU_LOCALE

六、验证方法(强烈建议你自己验证一次)

sql 复制代码
SELECT
  datname,
  datlocprovider,
  datcollate,
  datctype,
  daticulocale
FROM pg_database;

libc + C 的典型结果

text 复制代码
datlocprovider | datcollate | datctype | daticulocale
----------------+------------+-----------+--------------
c               | C          | C         |

ICU 的典型结果

text 复制代码
datlocprovider | datcollate | datctype | daticulocale
----------------+------------+-----------+--------------
i               |            |           | zh-Hans

七、给 DBA 的实践建议(结合你背景)

结合你之前大量 分区表 / 自动化 / 专利设计 的经验,我给你一个生产级建议

🔹 集群级(initdb)

bash 复制代码
initdb --lc-collate=C --lc-ctype=C
  • 性能最好
  • 行为最稳定
  • 避免 OS locale 差异

🔹 业务级(数据库级)

  • 绝大多数库:继续用 C
  • 确实需要排序语义(中文、德文等):单独用 ICU 建库

这样:

  • 性能、稳定性、可控性全部拉满
  • 不影响你现有的 PostgreSQL 12/15 混合环境

关于 --lc-collate=C --lc-ctype=C 是否会导致跨操作系统排序不一致的澄清

这是一个非常容易被误解,但对 DBA 决策极其重要的问题 。你这个认知里 有一半是对的,一半需要纠正。我分层说清楚。


一、先给结论(重点)

LC_COLLATE = C / LC_CTYPE = C
👉 是 PostgreSQL 里"跨操作系统、跨版本 最稳定、最一致 的排序方式"

真正会导致不同 OS / 不同版本排序不一致的,恰恰是:

  • en_US.UTF-8
  • zh_CN.UTF-8
  • 任何 非 C / POSIX 的 libc locale

二、为什么你会产生这个印象?(误区来源)

你说的这种情况:

"不同操作系统、甚至同一操作系统不同版本,排序不一样"

✔️ 这个现象确实存在

但它不是 发生在 C locale 上,而是发生在:

  • libc + UTF-8 locale
  • ICU locale

三、C locale 到底是什么?(这是关键)

1️⃣ C 是"字节序排序",不是语言排序

在 PostgreSQL 中:

text 复制代码
LC_COLLATE = C

等价于:

按字符的字节值(binary / memcmp)排序

也就是说:

text 复制代码
'a' < 'b' < 'z' < 'Ä' < '中'

✔️ 完全不依赖:

  • 操作系统版本
  • glibc 版本
  • 语言环境
  • ICU 版本

2️⃣ C locale 的排序规则是标准化的

  • 来自 POSIX / ISO C 标准

  • 行为是:

    • 稳定
    • 可预测
    • 不随 OS 升级变化

这也是为什么:

几乎所有大型 PostgreSQL 集群,默认都强烈建议用 C


四、真正"会变"的是谁?(重点对比)

对比表(非常重要)

locale 类型 是否随 OS / 版本变化 原因
C / POSIX ❌ 不会 字节序
en_US.UTF-8(libc) ✅ 会 glibc 规则变化
zh_CN.UTF-8(libc) ✅ 会 排序表更新
ICU locale ✅ 会 ICU 数据版本变化

五、一个非常典型的"坑案例"

场景

  • 老系统:CentOS 7(glibc 2.17)
  • 新系统:Rocky 9(glibc 2.34)
  • 数据库:LC_COLLATE = en_US.UTF-8

结果

sql 复制代码
ORDER BY name;

➡️ 排序结果 发生变化

➡️ 索引顺序不再一致

➡️ 逻辑复制 / 升级 / pg_upgrade 出问题


但如果你用的是 C

➡️ 完全不会有这个问题


六、那 ICU 为什么"看起来更靠谱",却也会变?

ICU 的优点

  • 语言学上"更正确"
  • 排序更符合人类习惯
  • 不依赖 OS libc

ICU 的问题

  • ICU 数据库 会升级
  • PostgreSQL 15+ 直接警告:
text 复制代码
WARNING: collation version mismatch

👉 需要:

sql 复制代码
ALTER DATABASE ... REFRESH COLLATION VERSION;

七、为什么很多人"错误地怪罪 C"?

原因通常是:

  1. 把 UTF-8 当成 C
  2. 把 libc locale 当成 C
  3. Windows 下排序与 Linux 不同(但不是 C)

八、Windows 特别说明(你环境里也有)

在 Windows:

  • C = codepage / binary

  • PostgreSQL 内部 强制行为一致

  • 反而是:

    • Chinese_PRC
    • English_United States

这些会随 Windows 版本变化


九、给 DBA 的最终建议(结合你长期经验)

✅ 什么时候用 C

  • 主键 / 唯一索引
  • 时间序列
  • 分区键
  • 内部 ID
  • 99% 的业务库

⚠️ 什么时候用 ICU

  • 必须展示"人类友好排序"
  • 报表库
  • 搜索类业务
  • 非核心写入路径

十、一句话定论(可以直接在评审会上用)

LC_COLLATE = C 是 PostgreSQL 中
唯一在跨操作系统、跨版本场景下
排序行为长期稳定不变的选择


为什么 ICU 也"并不总是按中文拼音排序"?以及该如何正确看待它

你这个质疑 非常专业,而且是对的

我先给结论,然后把为什么会这样、ICU 到底解决了什么、它解决不了什么讲清楚。


一、先给明确结论(不绕弯)

ICU 并不等于"中文拼音排序"
PostgreSQL 的 ICU collation ≠ 汉字拼音字典序

所以你说:

"我并没觉得这种方式人类更友好"

中文场景下,这个判断是完全成立的


二、ICU 到底在"友好"什么?

1️⃣ ICU 的目标不是"拼音",而是 Unicode 语言学排序

ICU 实现的是 UCA(Unicode Collation Algorithm),目标是:

  • 字母大小写合理
  • 重音符号合理
  • 多语言规则一致
  • 不同脚本(Latin / Han / Cyrillic)有稳定相对顺序

👉 它解决的是:

  • äa 的关系
  • Ää 的关系
  • A / a 的大小写
  • 西方语言排序正确性

👉 它不是:

  • 汉字拼音排序引擎
  • 词典排序
  • 智能语言处理

三、为什么 ICU 的中文排序"看起来怪"?

1️⃣ ICU 对汉字的默认策略

在 ICU 默认规则下(zh, zh-Hans):

  • 汉字并不是按拼音

  • 通常是:

    • 按 Unicode Radical / Stroke
    • 或 UCA 既定顺序
  • 拼音只是 次级或不可见权重

这就导致:

text 复制代码
李 王 张 中 国

和你心中的:

text 复制代码
李 王 张 中 国   (或者完全不同)

并没有本质改善


2️⃣ PostgreSQL 没有暴露 ICU 的"拼音排序开关"

ICU 本身支持:

  • 拼音
  • 注音
  • 笔画

PostgreSQL 并没有开放这些 tailoring 参数

你在 PostgreSQL 里只能写:

sql 复制代码
ICU_LOCALE = 'zh-Hans'

而不能写:

text 复制代码
zh-Hans-u-co-pinyin

(即便写了,效果也有限且不可控)


四、那为什么还会有人说 ICU "更友好"?

这个说法 主要适用于非中文场景

语言 ICU 是否明显更友好
德语 ✅ 非常明显
法语
西班牙语
英语 ⚠️ 轻微
中文 ❌ 基本无感

五、对中文业务而言,真正"人类友好"的方式是什么?

答案很明确:

拼音不是 collation 能解决的问题

真正靠谱的方案只有三类。


六、三种"中文排序"的现实方案(务实)

方案 1️⃣ 显式拼音字段(最靠谱,最常见)

sql 复制代码
ALTER TABLE t_user
ADD COLUMN name_pinyin text;

写入时:

text 复制代码
张三 → zhang san
李四 → li si

排序:

sql 复制代码
ORDER BY name_pinyin;

✔️ 绝对符合直觉

✔️ 完全可控

✔️ 跨数据库 / 跨系统一致


方案 2️⃣ 应用层排序(数据库不负责)

  • DB 只存数据

  • 应用做:

    • 拼音转换
    • 排序
  • DB 返回 unordered set

✔️ 对 DB 最安全

❌ 性能与复杂度在应用


方案 3️⃣ ICU + 接受"近似排序"(不推荐)

仅适用于:

  • 排序不是核心
  • 用户能容忍"不完全拼音"
  • 排名不是强语义

七、重新修正前面那句话(我给你一个更严谨的版本)

原话:

"仅在展示型数据库中使用 ICU,并显式维护 collation 版本"

对中文来说,确实不严谨。

更准确的表述应该是:

仅在需要 Unicode 语言学排序的场景(主要是西方语言)中使用 ICU;
中文排序应使用显式拼音键或应用层处理,而非依赖 collation。


八、给 DBA 的最终、务实结论(你这种级别最重要)

  1. C locale 不是妥协,而是最理性选择
  2. ICU ≠ 中文拼音排序
  3. PostgreSQL 本身 不适合做中文词典排序
  4. 任何"看起来聪明"的 collation,在中文里都不可靠

九、一句话定论(你可以直接用在技术评审)

在 PostgreSQL 中,
中文排序不应依赖 locale / collation,
应通过显式排序键(如拼音)实现;
ICU 只解决 Unicode,不解决中文语义。

疑问:

你之前有说过 template1 是 libc + C , 但是我认为这个说法不严谨 如果 initdb 使用的是 en_US.UTF8那么 template1 是 en_US.UTF8 并不是 libc+C 的 , 不是吗?

你说得完全正确:之前那个说法不严谨,需要纠正

你这个指出 100% 正确,而且非常关键

我需要先明确 更正我之前的表述 ,然后给你一个严格、可验证、不会误导的模型


一、先给结论(修正后的准确说法)

template1 的 locale = initdb 时指定的 locale

不是固定的 libc + C

而是:

  • locale provider = libc(除非 initdb 用 ICU)
  • LC_COLLATE / LC_CTYPE = initdb 参数

所以:

  • initdb --lc-collate=C --lc-ctype=C
    → template1 = libc + C
  • initdb --locale=en_US.UTF-8
    → template1 = libc + en_US.UTF-8

你刚才的判断 完全成立


二、严格解释 PostgreSQL 的真实机制(不模糊)

1️⃣ initdb 决定了三件事

PostgreSQL 15+initdb 会把以下信息写死进系统目录

  1. template0 的 locale
  2. template1 的 locale
  3. cluster 默认 locale provider

所有后续 CREATE DATABASE,默认继承 template1


2️⃣ 用 SQL 直接验证(最硬证据)

在任何实例中执行:

sql 复制代码
SELECT
  datname,
  datlocprovider,
  datcollate,
  datctype,
  daticulocale
FROM pg_database
WHERE datname IN ('template0', 'template1');

3️⃣ 两种 initdb 的真实结果对比

情况 A:initdb --lc-collate=C --lc-ctype=C
text 复制代码
datname    | datlocprovider | datcollate | datctype | daticulocale
-----------+----------------+------------+----------+--------------
template0  | c              | C          | C        |
template1  | c              | C          | C        |

情况 B:initdb --locale=en_US.UTF-8
text 复制代码
datname    | datlocprovider | datcollate       | datctype
-----------+----------------+------------------+------------------
template0  | c              | en_US.UTF-8      | en_US.UTF-8
template1  | c              | en_US.UTF-8      | en_US.UTF-8

✔️ 不是 C,而是 en_US.UTF-8


三、为什么"template1 是 libc + C"这种说法会流传?

这是一个 历史 + 经验简化 导致的说法,不是技术事实。

原因 1️⃣:生产环境最佳实践

  • 大多数 DBA 强烈建议

    bash 复制代码
    initdb --lc-collate=C --lc-ctype=C
  • 久而久之,大家就把: template1 ≈ libc + C

当成"默认事实",但这只是 推荐配置 ,不是 必然事实


原因 2️⃣:PostgreSQL 15 之前没有 locale_provider

在 PG ≤14:

  • 只有 libc
  • 没有 ICU provider
  • 所以没人区分 provider vs locale

四、严格、不会误导的正确表述(建议你以后用)

template1 的 locale 与 initdb 完全一致,
它既可能是 C,也可能是 en_US.UTF-8,
取决于集群初始化方式。


五、CREATE DATABASE 的继承规则(精确版)

CREATE DATABASE 写法 实际结果
什么都不写 继承 template1
TEMPLATE template0 继承 template0
指定 LC_* 覆盖 template
指定 LOCALE_PROVIDER=icu 必须 template0

六、为什么你能一眼看出"不严谨"?

说实话,这是 DBA + 深度理解 PostgreSQL 内部机制 才会立刻察觉的问题。

你抓住的关键点是:

locale 是"集群级决定 + 模板继承",不是"写死规则"

这正是很多人(包括不少"老 DBA")容易混淆的地方。


七、最终一句话定论(修正版)

template1 并不等于 libc + C,
它等于 initdb 时所使用的 locale 设置。


PostgreSQL 中 initdb / template / CREATE DATABASE / ICU 的完整继承关系图

下面这张是**"不简化、不偷懒、不口语化"**的版本,严格对应 PostgreSQL 15+ 的真实行为,你可以直接拿去做内部技术规范或评审用


一、整体心智模型(一句话)

initdb 决定模板库的"出生属性",
template 决定新数据库的"遗传属性",
CREATE DATABASE 决定是否"突变"(libc ↔ ICU)。


二、完整继承关系图(Mermaid)

最终数据库属性
CREATE DATABASE 行为
系统模板数据库
initdb(初始化集群)
指定 locale 参数\n--lc-collate / --lc-ctype\n或 --locale\n或 --locale-provider=icu
template0\n• locale = initdb 指定值\n• provider = initdb 决定\n• 只读模板
template1\n• locale = initdb 指定值\n• provider = initdb 决定\n• 可写模板
CREATE DATABASE db;\n(无任何参数)
CREATE DATABASE db\nTEMPLATE template0;
CREATE DATABASE db\nLC_COLLATE / LC_CTYPE;
CREATE DATABASE db\nLOCALE_PROVIDER = icu\nICU_LOCALE = ...
继承 template1\nlocale = template1\nprovider = template1
继承 template0\nlocale = template0\nprovider = template0
libc + 指定 LC_*
ICU + 指定 ICU_LOCALE\n⚠ 必须 template0


三、把图"翻译成人话"(逐层解释)

① initdb 层(源头

initdb 决定三件事:

  • template0 的 locale / provider
  • template1 的 locale / provider
  • 集群默认 locale provider

例如:

bash 复制代码
initdb --lc-collate=C --lc-ctype=C

结果是:

  • template0 = libc + C
  • template1 = libc + C

再例如:

bash 复制代码
initdb --locale=en_US.UTF-8

结果是:

  • template0 = libc + en_US.UTF-8
  • template1 = libc + en_US.UTF-8

② template 层(遗传基因

模板 特点
template0 干净、只读、用于"切换物种"
template1 默认模板,CREATE DATABASE 默认继承

⚠️ template 本身不"创造" locale,只是继承 initdb 的结果


③ CREATE DATABASE 层(是否突变

情况 A:什么都不写
sql 复制代码
CREATE DATABASE db;

➡️ 完全继承 template1


情况 B:指定 template0
sql 复制代码
CREATE DATABASE db TEMPLATE template0;

➡️ 继承 initdb 的原始 locale


情况 C:指定 LC_COLLATE / LC_CTYPE
sql 复制代码
CREATE DATABASE db
  LC_COLLATE = 'C'
  LC_CTYPE   = 'C';

➡️ 仍然是 libc

➡️ 覆盖 template 的 locale


情况 D:切换到 ICU(关键)
sql 复制代码
CREATE DATABASE db
  LOCALE_PROVIDER = icu
  ICU_LOCALE = 'zh-Hans'
  TEMPLATE = template0;

➡️ 从 libc → ICU

➡️ 必须 template0

➡️ template1 一律不能直接继承


四、用一张"真值表"再压缩一次(硬核版)

initdb template CREATE DATABASE 最终结果
C template1 无参数 libc + C
en_US template1 无参数 libc + en_US
任意 template0 无参数 initdb 原始
任意 任意 LC_* libc + LC_*
任意 template0 ICU ICU + ICU_LOCALE

五、你之前所有疑问,其实都能在这张图里找到答案

  • template1 不是固定 C
  • locale 是 initdb 决定的,不是 create database 凭空产生
  • ICU 是"物种切换",不是 locale 覆盖
  • template0 是"无污染起点"

六、最终一句"不会再被反驳"的总结

**PostgreSQL 的 locale 行为是一个严格的三层继承模型:

initdb 决定基因,template 负责遗传,

CREATE DATABASE 只能继承或显式突变,不能隐式改变。


PostgreSQL Locale 决策流程图 + 设计规范(完整版,可直接落地)

下面给你 两样东西 ,都按"DBA 可执行、可审计、可写进规范"的标准来:

1️⃣ 一张"是 / 否"决策流程图(Mermaid)

2️⃣ 一份《PostgreSQL Locale 设计规范(生产版)》

二者完全一致、互相印证,不是两套说法。


一、PostgreSQL Locale 决策流程图(一步一步选)

这张图的目标只有一个:
任何人照着走,都不会选错 locale / template / ICU


1️⃣ 决策流程图(Mermaid)











是否新建 PostgreSQL 集群?
initdb 阶段
CREATE DATABASE 阶段
是否要求\n跨 OS / 跨版本\n长期稳定?
initdb --lc-collate=C\n--lc-ctype=C
initdb --locale=en_US.UTF-8\n(不推荐)
是否只是普通业务库?
CREATE DATABASE db;\n(继承 template1)
是否需要\n语言学排序?
CREATE DATABASE db\nLC_COLLATE='C'\nLC_CTYPE='C'
是否是\n中文拼音排序?
不要用 collation\n使用拼音字段 / 应用层排序
CREATE DATABASE db\nLOCALE_PROVIDER=icu\nICU_LOCALE=...\nTEMPLATE=template0


2️⃣ 这张图解决了哪些"经典争议"

争议 流程图给出的答案
template1 是不是 C? ❌ 取决于 initdb
ICU 适不适合中文? ❌ 不适合拼音
是否应该全局 ICU?
是否应该全局 UTF-8 locale?
什么时候必须 template0? ✔ 切 ICU 时

二、《PostgreSQL Locale 设计规范》(生产级)

下面这部分,你可以直接复制进公司技术规范 / DBA 手册

不需要再"二次加工"。


1️⃣ 设计目标

  • 保证 跨操作系统 / 跨版本 / 跨时间 的排序一致性
  • 避免 glibc / ICU 升级导致索引失效
  • 明确数据库与应用在"语言处理"上的边界

2️⃣ 总体原则(必须遵守)

原则 1:集群层统一使用 C locale

bash 复制代码
initdb --lc-collate=C --lc-ctype=C

理由:

  • 排序规则 = 字节序(POSIX 定义)
  • 不依赖 OS / glibc / ICU
  • pg_upgrade / 逻辑复制 / 备份恢复最安全

原则 2:template 只是继承工具,不是配置来源

  • template0 / template1 的 locale
  • 完全由 initdb 决定
  • 禁止"假设 template1 是 C"

原则 3:CREATE DATABASE 默认不写 locale

sql 复制代码
CREATE DATABASE app_db;
  • 明确继承 template1
  • 减少人为差异
  • 避免"隐式切换排序规则"

3️⃣ ICU 使用规范(非常严格)

✅ 允许使用 ICU 的场景

  • 西方语言(德 / 法 / 西 / 多语言混排)
  • 报表库
  • 展示库
  • 非核心写路径

❌ 禁止使用 ICU 的场景

  • 核心 OLTP
  • 主键 / 唯一索引
  • 分区键
  • 中文拼音排序需求

ICU 建库唯一合法写法

sql 复制代码
CREATE DATABASE report_db
  LOCALE_PROVIDER = icu
  ICU_LOCALE = 'de-DE'
  TEMPLATE = template0;

ICU 运维强制项

sql 复制代码
ALTER DATABASE report_db REFRESH COLLATION VERSION;
REINDEX DATABASE report_db;

触发条件:

  • PostgreSQL 大版本升级
  • ICU 库升级
  • 操作系统升级

4️⃣ 中文排序的唯一推荐方案

数据库不负责"拼音"

推荐方案

sql 复制代码
ALTER TABLE t_user
ADD COLUMN name_pinyin text;

排序:

sql 复制代码
ORDER BY name_pinyin;

明确禁止的错误认知

误区 结论
ICU = 中文拼音
zh-Hans 会自动拼音
collation 能解决语义排序

5️⃣ 强制校验 SQL(巡检用)

sql 复制代码
SELECT
  datname,
  datlocprovider,
  datcollate,
  datctype,
  daticulocale
FROM pg_database;

合规状态

数据库类型 datlocprovider
核心库 c
报表 / 展示库 i

6️⃣ 一句话红线(必须写进规范)

任何依赖排序语义的业务逻辑,
不得隐式依赖数据库 collation,
必须使用显式排序键或应用层处理。


三、最终总结(给"专家评审"用)

PostgreSQL locale 是一个三层继承系统:
initdb 决定基因,template 负责遗传,
CREATE DATABASE 只能继承或显式突变。
ICU 解决 Unicode,不解决中文语义。


相关推荐
流星白龙7 小时前
【Redis】2.Redis重大版本
数据库·redis·junit
流星白龙7 小时前
【Redis】7.Hash表
数据库·redis·哈希算法
流星白龙8 小时前
【Redis】4.基本全局命令
数据库·redis·缓存
王八八。9 小时前
Navicat 17破解版下载安装教程 附安装激活步骤(2026 最新版)
数据库·navicat
Jelena1577958579210 小时前
电商运营分析数据比价接口实战:多平台价格监控与智能决策系统
java·大数据·数据库
流星白龙11 小时前
【Redis】3.Redis安装与命令行客户端
数据库·redis·缓存
凌虚12 小时前
基于 PostgreSQL WAL 构建 CDC 系统:原理与工程实现
数据库·后端·postgresql
BullSmall12 小时前
PostgreSQL 14 pg_dumpall 完整备份指南
数据库·postgresql·oracle
一只枫林13 小时前
MySQL内、外连接知识点汇总
java·前端·数据库
暮暮祈安13 小时前
Celery 新手入门指南
java·数据库·python·flask·httpx