前言

做过国际化项目的开发者大概都有这样的经历——系统上线后,用户反馈"数据全变成问号了"。字符编码这东西,平时没人关心,可一旦出问题,影响的是整条数据链路。轻则页面乱码,重则排序错乱、数据截断,甚至索引直接失效。

这篇文章就来聊聊金仓数据库在字符集与国际化方面的完整技术方案。从"选哪种编码"到"怎么配排序规则",再到"客户端和服务器编码不一致怎么办",把实际开发中容易踩的坑一次性讲清楚。

文章目录

一、先搞清楚几个基本概念

1.1 字符集和编码——它们不是一回事

简单说,字符集是一张"字符清单",规定了这个集合里有哪些符号;编码则是把这些符号变成计算机能存储的字节序列的规则。一个字符集可以对应好几种编码方式。拿 Unicode 来说,它就有 UTF-8、UTF-16 两种常见编码。

金仓数据库能处理两大类字符集:

  • 单字节字符集:一个字符占 1 个字节。像 LATIN1 ~ LATIN10 这类 ISO 8859 系列,主要服务西欧、中欧等语言环境,简单高效。
  • 多字节字符集:一个字符占 1~4 个字节。UTF-8、GBK、GB18030、EUC 系列都属于这一类,专门应对中日韩等"字多"的语言。

在金仓的文档和日常交流中,"字符集"和"编码"经常混着用,指的其实是同一件事。

1.2 字符集该怎么选

字符集在数据库初始化(initdb)的时候就定下来了,后面所有新建的数据库都会继承这个默认值。选的时候想清楚四件事就够了:

  1. 你的数据要说几种语言? 只有中文和英文,GBK 就够用。要是还得塞进日文、阿拉伯文、俄文……那 UTF-8 是唯一靠谱的选择。
  2. 存储空间敏感吗? UTF-8 存一个中文字符占 3 字节,GBK 只要 2 字节。纯中文环境下 GBK 确实省空间。
  3. 对接的系统和工具多不多? UTF-8 几乎是"万能钥匙",所有主流编程语言和中间件都原生支持,省心。
  4. 排序和区域有没有特殊要求? 字符集得跟数据库的 LC_CTYPE(字符分类)和 LC_COLLATE(排序规则)兼容,否则后患无穷。

一句话建议:没有特殊约束就选 UTF-8,一劳永逸。

二、手把手配置字符集

2.1 初始化时把基调定好

initdb 初始化数据库集簇时,加个 -E 参数就能指定默认字符集:

# 指定 UTF-8 作为默认编码
initdb -D /data/kingbase -E UTF8

# 也可以写完整选项名
initdb -D /data/kingbase --encoding=UTF8

# 同时指定中文区域,让排序和分类也一步到位
initdb -D /data/kingbase -E UTF8 --locale=zh_CN.UTF-8

如果什么都不指定,initdb 会去读操作系统的区域设置,自动猜一个编码。比如系统区域是 zh_CN.UTF-8,它就会默认用 UTF8。大多数情况下这个"自动猜"是靠谱的,但生产环境建议显式指定,别给未来埋雷。

2.2 单个数据库也可以"另起炉灶"

有时候整个集簇用 UTF-8,但某个数据库需要用别的编码,这没问题。关键是两条:编码得和区域设置兼容,并且必须从 template0 模板创建。

-- 创建一个韩文数据库,编码用 EUC_KR
CREATE DATABASE korean
  WITH ENCODING 'EUC_KR'
  LC_COLLATE='ko_KR.euckr'
  LC_CTYPE='ko_KR.euckr'
  TEMPLATE=template0;

命令行也一样:

createdb -E EUC_KR -T template0 \
  --lc-collate=ko_KR.euckr \
  --lc-ctype=ko_KR.euckr korean

一条铁律:如果模板不是 template0,编码和区域就不能改。强行改的话,数据很可能就毁了。

2.3 怎么查看当前编码

进了数据库,随时可以查:

-- 方法一:ksql 里的快捷命令
\l

-- 方法二:直接查系统表
SELECT datname, pg_encoding_to_char(encoding) AS encoding,
       datcollate, datctype
FROM pg_database;

-- 方法三:查看当前数据库编码
SHOW server_encoding;

输出大概是这个样子:

  Name    | Encoding |  Collate   |   Ctype
----------+----------+------------+------------
 kingbase | UTF8     | zh_CN.UTF8 | zh_CN.UTF8
 korean   | EUC_KR   | ko_KR.euckr| ko_KR.euckr
 prod     | UTF8     | en_US.UTF8 | en_US.UTF8

三、区域(Locale)——不只是"语言"那么简单

3.1 区域到底管什么

很多人以为"区域"就是选个语言,其实远不止于此。区域决定了数据库对"文化习惯"的方方面面怎么响应——字符串怎么排、大小写怎么转、货币符号长什么样、日期格式用哪一套。

金仓数据库把区域拆成了六个类别:

类别 管什么
LC_COLLATE 字符串怎么排序
LC_CTYPE 哪些算大写、哪些算小写、哪些是数字
LC_MESSAGES 系统报错和提示用什么语言
LC_MONETARY 货币怎么显示
LC_NUMERIC 小数点用逗号还是句号
LC_TIME 日期格式怎么写

重点提醒LC_COLLATELC_CTYPE 这两个在数据库创建时就锁死了,创建之后改不了。原因是它们直接决定了索引的排序方式,中途修改的话索引会直接废掉。其他几个类别倒是可以在运行时调整。

3.2 配置区域——两种时机

时机一:初始化集簇时

# 全局设成中文区域
initdb -D /data/kingbase --locale=zh_CN.UTF-8

# 也可以"混搭":中文排序 + 美式货币
initdb -D /data/kingbase --locale=zh_CN.UTF-8 --lc-monetary=en_US.UTF-8

时机二:创建单个数据库时

CREATE DATABASE prod
  TEMPLATE template0
  LC_CTYPE='zh_CN.UTF-8'
  LC_COLLATE='zh_CN.UTF-8';

创建之后想确认配置对不对,一条命令就够:

SHOW lc_collate;    -- 应该输出 zh_CN.UTF-8
SHOW lc_ctype;      -- 应该输出 zh_CN.UTF-8
SHOW lc_messages;   -- 看看提示信息用的什么语言

3.3 区域会悄悄影响这些 SQL 行为

改了区域之后,你可能不会立刻感知到变化,但下面这些操作的结果确实不一样了:

  • ORDER BY 的排序结果
  • upper()lower() 这些大小写函数的行为
  • LIKE 和正则表达式的匹配规则
  • to_char() 格式化出来的日期、数字长什么样
  • LIKE 查询能不能走索引

一个性能相关的忠告:如果你用了非 C/POSIX 的区域,字符处理会变慢,LIKE 也用不上普通索引。如果业务允许,可以给需要 LIKE 查询的列单独建一个使用 C 排序规则的索引来弥补性能。

-- 给 name 列创建一个走 C 排序规则的索引,加速 LIKE 查询
CREATE INDEX idx_products_name_c ON products (name COLLATE "C");

四、金仓数据库支持哪些字符集

下面这张表覆盖了最常用的字符集,够绝大多数场景使用了:

字符集 一句话描述 适用语言 字节/字符 支持ICU
UTF8 万能编码,覆盖全部 Unicode 所有语言 1-4
GBK 简体中文国家标准扩展 简体中文 1-2
GB18030 中文国家标准(含生僻字) 中文 1-4
EUC_CN 中文扩展 Unix 编码 简体中文 1-3
EUC_JP 日文扩展 Unix 编码 日文 1-3
EUC_KR 韩文扩展 Unix 编码 韩文 1-3
LATIN1 西欧语言编码 西欧 1

4.1 一个特殊角色:SQL_ASCII

SQL_ASCII 不是一种真正的字符集。它做的事情很简单:只认 0~127 的 ASCII 字符,128 以上的字节统统不管,也不做任何编码转换。

-- 除非你 100% 确定只有纯 ASCII 数据,否则别这么干
CREATE DATABASE mydb WITH ENCODING 'SQL_ASCII';

听起来"不管不顾"好像很自由,实际上是个大坑。数据库既不校验也不转换非 ASCII 字符,出了问题还很难排查。只有纯英文环境才适合用它,其他场景一律避开。

五、客户端和服务器编码不一样怎么办

5.1 数据库帮你自动转

金仓数据库内置了客户端/服务器之间的自动编码转换。你的数据库用 UTF-8,但某个老系统只能吐 GBK 数据?没关系,数据库会自动完成 GBK → UTF-8 的转换;查询结果也会自动从 UTF-8 转回 GBK 返回给客户端。

转换规则全部记录在系统目录 sys_conversion 里,查一查就知道当前支持哪些转换组合:

-- 查看系统内置的编码转换规则
SELECT conname, conforencoding, contoencoding
FROM pg_conversion;

5.2 告诉数据库"我用的什么编码"

自动转换有个前提:你得告诉数据库你的客户端在用什么编码。四种方式,按需选择:

在命令行工具里临时改:

\encoding UTF8

用 SQL 语句设置:

-- 方式一
SET client_encoding TO 'UTF8';

-- 方式二(标准 SQL 写法)
SET NAMES 'UTF8';

-- 查看当前客户端编码
SHOW client_encoding;

-- 恢复默认
RESET client_encoding;

通过环境变量(适合批量配置):

# 设了这个变量,连接后自动生效
export KINGBASE_CLIENTENCODING=UTF8

在 JDBC 连接字符串里指定(Java 应用最常用):

String url = "jdbc:kingbase8://localhost:54321/mydb"
           + "?characterEncoding=UTF8";
Connection conn = DriverManager.getConnection(url, "user", "password");

5.3 转换失败了会怎样

并不是所有字符都能从一个编码"翻译"到另一个编码。比如数据库编码是 EUC_JP(日文),客户端用的是 LATIN1(西欧),那些日文独有的字符根本没法用 LATIN1 表示,数据库就会直接报错。

遇到这种情况怎么办?三条对策:

  1. 最省事:服务器和客户端都用 UTF-8,从根本上消除转换问题。
  2. 退而求其次:确保源字符集是目标字符集的子集。比如从 GBK 转到 UTF-8 永远没问题(UTF-8 是 GBK 的超集),反过来就不一定了。
  3. 千万别踩的坑:别把客户端编码设成 SQL_ASCII,这会直接关掉自动转换功能,等于所有字符都"裸奔"。

5.4 内置转换不够用?自己造一个

如果系统内置的转换规则覆盖不了你的场景,可以写一个自定义转换函数然后注册:

-- 先创建转换函数(PL/pgSQL 示例)
CREATE OR REPLACE FUNCTION utf8_to_gbk_custom(INTEGER, INTEGER, CSTRING, INTERNAL, INTEGER)
RETURNS VOID AS $$
BEGIN
  -- 这里实现具体的编码转换逻辑
  -- 通常调用数据库内部的转换 API
END;
$$ LANGUAGE plpgsql;

-- 注册为可用的编码转换
CREATE CONVERSION my_utf8_to_gbk
  FOR 'UTF8' TO 'GBK'
  FROM utf8_to_gbk_custom;

六、排序规则——让字符串"按你的想法"排

6.1 为什么排序还需要"规则"

不同语言对字符串排序的理解不一样。中文可以按拼音排,也可以按笔画排。德语里 ä 有人觉得排在 a 后面,有人觉得它等于 ae。这些差异就是"排序规则"(Collation)要解决的问题。

在金仓数据库里,排序规则本质上是一个 SQL 对象,它把一个名字映射到操作系统或 ICU 库提供的排序行为。

6.2 两种排序引擎:libc 和 ICU

金仓数据库支持两种排序规则的提供者:

  • libc:直接用操作系统自带的 C 库。好处是开箱即用,坏处是不同操作系统的 libc 版本行为可能略有差异,同一套数据在 Linux 和 Windows 上排序结果可能不一样。另外,libc 排序规则和字符集编码是绑定的。
  • icu:用外部 ICU 库(International Components for Unicode)。排序行为跨平台一致,而且跟编码无关。不过需要在编译数据库时启用 ICU 支持。

如果追求排序结果的一致性(比如多地部署的系统),优先选 ICU。

6.3 开箱即用的标准排序规则

不管你用什么平台,这三个排序规则一定有:

名称 怎么排
default 数据库创建时指定的 LC_COLLATE 和 LC_CTYPE
C / POSIX 只认 ASCII 的 A~Z,按字节值硬排,速度快
ucs_basic 仅限 UTF8 编码,按 Unicode 编码点排序

6.4 一张表里也能"各排各的"

排序规则可以精确到列级别,甚至单个表达式上也能临时指定。这在多语言混合的业务场景里特别好用:

-- 同一张表,不同列用不同排序规则
CREATE TABLE international_products (
    id          INTEGER PRIMARY KEY,
    name_cn     TEXT COLLATE "zh_CN",       -- 中文按拼音排
    name_de     TEXT COLLATE "de_DE",       -- 德语规则
    name_ja     TEXT COLLATE "ja_JP",       -- 日语规则
    description TEXT                         -- 跟随数据库默认
);

查询时也能临时"切换"排序规则:

-- 按德语规则排序
SELECT * FROM international_products
ORDER BY name_de COLLATE "de_DE";

-- 比较时指定用中文排序规则
SELECT * FROM international_products
WHERE name_cn COLLATE "zh_CN" > '苹果';

一个容易犯的错:当两列有不同的排序规则时,直接比较会报错:

-- 报错!两列排序规则冲突,数据库不知道听谁的
SELECT * FROM t1, t2 WHERE t1.name = t2.name;

-- 修好它:显式告诉数据库用哪个规则
SELECT * FROM t1, t2
WHERE t1.name COLLATE "zh_CN" = t2.name;

6.5 内置的不够用?自己创建排序规则

-- 基于 libc 创建中文排序规则
CREATE COLLATION chinese
  (provider = libc, locale = 'zh_CN.utf8');

-- 基于 ICU 创建德语电话簿风格排序
CREATE COLLATION "de-u-co-phonebk-x-icu"
  (provider = icu, locale = 'de-u-co-phonebk');

-- 数字自然排序:A-21 排在 A-123 前面
CREATE COLLATION numeric_sort
  (provider = icu, locale = 'en-u-kn-true');

-- 先排大写再排小写(默认反过来)
CREATE COLLATION upper_first
  (provider = icu, locale = 'en-u-kf-upper');

-- 把数字排到字母后面(默认数字在前)
CREATE COLLATION digits_last
  (provider = icu, locale = 'en-u-kr-latn-digit');

-- 从已有排序规则复制一个新名字(方便跨平台统一)
CREATE COLLATION german FROM "de_DE";

6.6 不区分大小写的排序——"非确定性"排序规则

默认情况下,只有字节完全一样的字符串才算"相等"。但实际业务中经常需要"模糊"比较——比如忽略大小写、忽略重音符号。

这时就要用到"非确定性排序规则"了,关键是那个 deterministic = false

-- 创建不区分大小写的排序规则
CREATE COLLATION case_insensitive
  (provider = icu, locale = 'und-u-ks-level2', deterministic = false);

-- 测试一下:'Hello' 和 'hello' 被视为相等
SELECT 'Hello' COLLATE case_insensitive = 'hello';
-- 输出:t(true)

-- 创建不区分重音的排序规则
CREATE COLLATION ignore_accents
  (provider = icu, locale = 'und-u-ks-level1-kc-true', deterministic = false);

SELECT 'café' COLLATE ignore_accents = 'cafe';
-- 输出:t(true)

注意:非确定性排序规则有两个代价——性能会比普通排序慢一些,而且不支持 LIKE、正则这类模式匹配操作。所以只在确实需要模糊比较的场景下使用。

6.7 SQLServer 兼容模式下的排序规则

如果数据库运行在 SQLServer 兼容模式下,排序规则的后缀有特定含义:

后缀 含义
_CS 区分大小写(Case Sensitive)
_CI 不区分大小写(Case Insensitive)
_AS 区分重音(Accent Sensitive)
_AI 不区分重音(Accent Insensitive)
_KS 区分假名(日文片假名/平假名)
_WS 区分全角/半角
-- 创建一个按笔画排序、不区分大小写和重音的列
CREATE TABLE t3 (
    id   INT,
    name VARCHAR(20) COLLATE "Chinese_PRC_Stroke_CI_AI"
);

INSERT INTO t3 VALUES (1, '人'), (2, '大'), (3, '金'), (4, '仓');

-- 按笔画排序:人(2画) < 大(3画) < 仓(4画) < 金(8画)
SELECT * FROM t3 ORDER BY name;

七、国际化数据类型——给多语言留"后路"

7.1 NCHAR 和 NVARCHAR2 是干什么的

普通的 CHAR/VARCHAR2 用的是数据库字符集。但如果你用的数据库字符集是 GBK,突然要存一个日文字符怎么办?这时候就需要 NCHARNVARCHAR2 了——它们走的是独立的 Unicode 国际字符集(UTF-16 或 UTF-8),跟数据库本身的字符集互不干扰。

  • NCHAR:定长 Unicode 字符串,用国际字符集存储。
  • NVARCHAR2:变长 Unicode 字符串,用国际字符集存储。

数据在普通字符类型和国际字符类型之间赋值时,数据库会自动做字符集转换,不需要手动处理。

-- 一个典型的国际化表设计
CREATE TABLE multilingual_docs (
    id           INTEGER PRIMARY KEY,
    title        VARCHAR2(200),        -- 数据库字符集
    title_i18n   NVARCHAR2(200),       -- Unicode,什么语言都能存
    content      CLOB,                 -- 大文本,数据库字符集
    content_i18n NCLOB                 -- 大文本,Unicode
);

-- 正常插入中文
INSERT INTO multilingual_docs (id, title, title_i18n)
VALUES (1, '产品说明', '产品说明');

-- title_i18n 可以存任何语言的文字
INSERT INTO multilingual_docs (id, title, title_i18n)
VALUES (2, 'Product Guide', '製品ガイド');

-- NCHAR 类型的长度限制取决于国际字符集的编码方式
-- AL16UTF16 编码:最多 1000 个字符
-- UTF8 编码:最多 2000 个字符

7.2 “4 个字符"和"4 个字节”——差别大了

在多字节编码下,"字符"和"字节"完全是两码事。一个中文字符在 UTF-8 里占 3 个字节,在 GBK 里占 2 个字节。如果建表时写 VARCHAR2(4),到底是指 4 个字符还是 4 个字节?

金仓数据库用 NLS_LENGTH_SEMANTICS 参数来控制这个行为:

-- 模式一:按字符计算
SET NLS_LENGTH_SEMANTICS = 'CHAR';

CREATE TABLE test_char (col VARCHAR2(4));
INSERT INTO test_char VALUES ('一二三四');  -- 成功,正好4个字符
INSERT INTO test_char VALUES ('一二三四五'); -- 报错,超出4个字符

SELECT col, length(col) FROM test_char;
--  col     | length
-- ---------+--------
--  一二三四 |   4

-- 模式二:按字节计算
SET NLS_LENGTH_SEMANTICS = 'BYTE';

CREATE TABLE test_byte (col VARCHAR2(4));
INSERT INTO test_byte VALUES ('1234');     -- 成功,4字节
INSERT INTO test_byte VALUES ('一二三');    -- 报错!UTF-8下一个中文3字节,光"一二"就6字节了

INSERT INTO test_byte VALUES ('一');       -- 成功,3字节,没超过4
SELECT col, length(col) FROM test_byte;
-- col | length
-- ----+--------
-- 1234|   4
-- 一  |   1

更推荐的做法是直接在列定义里写清楚,省得依赖全局参数:

CREATE TABLE products (
    name_char  VARCHAR2(20 CHAR),   -- 明确说:20个字符
    name_byte  VARCHAR2(60 BYTE),   -- 明确说:60个字节
    code       CHAR(10)             -- 不写的话跟随 NLS_LENGTH_SEMANTICS
);

7.3 常用字符处理函数速查

日常开发中这几个函数用得最多,收藏备用:

-- 按字符数计数
SELECT char_length('你好World');          -- 7(7个字符)

-- 按字节数计量
SELECT octet_length('你好World');          -- 9(UTF-8: 2×3+5=9字节)

-- 大小写转换(受 LC_CTYPE 区域影响)
SELECT upper('hello world');               -- 'HELLO WORLD'
SELECT lower('HELLO WORLD');               -- 'hello world'
SELECT initcap('hello world');             -- 'Hello World'

-- 截取子串(按字符)
SELECT substring('你好世界', 1, 2);        -- '你好'

-- 字符集转换:把 UTF-8 编码的"中国"转成 GBK 字节序列
SELECT UTL_RAW.CONVERT(
    UTL_RAW.CAST_TO_RAW('中国'),
    'ZHS16GBK',    -- 目标字符集
    'AL32UTF8'     -- 源字符集
);
-- 输出:D6D0B9FA("中国"的 GBK 编码)

-- 查看字符串在当前编码下的十六进制表示
SELECT encode(convert_to('中国', 'UTF8'), 'hex');
-- 输出:e4b8ade59bbd

八、实战:多语言场景下的完整方案

8.1 第一原则——全链路统一 UTF-8

如果你的项目还在技术选型阶段,把这一条钉在墙上:数据库、应用服务器、客户端工具、操作系统的默认编码全部统一成 UTF-8。 这样做的好处是:

  • 彻底消除编码转换带来的性能损耗和潜在错误
  • 全球所有语言的字符都能直接存储,不用操心"这个字能不能存"
  • 应用层的编码处理逻辑大幅简化
# 1. 初始化数据库
initdb -D /data/kingbase -E UTF8 --locale=zh_CN.UTF-8

# 2. 设置环境变量
export KINGBASE_CLIENTENCODING=UTF8
export LANG=zh_CN.UTF-8

# 3. 启动数据库
sys_ctl -D /data/kingbase -l logfile start

8.2 Java 应用的完整编码配置

import java.sql.*;

public class I18nDemo {
    public static void main(String[] args) {
        // 连接 URL 里直接指定编码
        String url = "jdbc:kingbase8://localhost:54321/mydb"
                   + "?characterEncoding=UTF8"
                   + "&clientEncoding=UTF8";

        try (Connection conn = DriverManager.getConnection(url, "system", "password")) {

            // 双重保险:连接后再显式设置一次
            try (Statement stmt = conn.createStatement()) {
                stmt.execute("SET client_encoding TO 'UTF8'");
            }

            // 建一张多语言表
            try (Statement stmt = conn.createStatement()) {
                stmt.execute(
                    "CREATE TABLE IF NOT EXISTS i18n_data (" +
                    "  id    SERIAL PRIMARY KEY," +
                    "  lang  VARCHAR(10)," +
                    "  text  VARCHAR(500)" +
                    ")"
                );
            }

            // 批量插入多语言数据
            String[] langs  = {"zh", "ja", "en", "de"};
            String[] texts  = {"你好世界", "こんにちは", "Hello World", "Hallo Welt"};

            try (PreparedStatement ps = conn.prepareStatement(
                    "INSERT INTO i18n_data (lang, text) VALUES (?, ?)")) {
                for (int i = 0; i < langs.length; i++) {
                    ps.setString(1, langs[i]);
                    ps.setString(2, texts[i]);
                    ps.addBatch();
                }
                ps.executeBatch();
            }

            // 查询并打印——中文不会乱码
            try (Statement stmt = conn.createStatement();
                 ResultSet rs = stmt.executeQuery("SELECT * FROM i18n_data ORDER BY id")) {
                while (rs.next()) {
                    System.out.printf("[%s] %s%n",
                        rs.getString("lang"),
                        rs.getString("text"));
                }
            }

        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

8.3 Python 应用的编码处理

# 使用 psycopg2 风格的驱动连接金仓数据库
import sys

def query_i18n_data():
    conn = None
    try:
        # 连接时确保客户端编码正确
        conn = connect(
            host="localhost",
            port=54321,
            database="mydb",
            user="system",
            password="password"
        )

        with conn.cursor() as cur:
            # 显式设置客户端编码
            cur.execute("SET client_encoding TO 'UTF8'")

            # 查询多语言数据
            cur.execute("SELECT lang, text FROM i18n_data ORDER BY id")
            for lang, text in cur.fetchall():
                # Python 3 默认使用 Unicode,直接输出即可
                print(f"[{lang}] {text}")

    except Exception as e:
        print(f"数据库操作失败: {e}", file=sys.stderr)
    finally:
        if conn:
            conn.close()

if __name__ == "__main__":
    query_i18n_data()

8.4 数据迁移——编码转换是最容易翻车的环节

从一个数据库搬到另一个数据库,编码兼容性必须提前确认:

-- 第一步:搞清楚源库用的什么编码
SELECT datname, pg_encoding_to_char(encoding) AS encoding
FROM pg_database;

-- 第二步:确保目标库的字符集是源库的超集
-- GBK → UTF-8:安全(UTF-8 包含 GBK 的所有字符)
-- UTF-8 → GBK:可能丢字符(GBK 不包含某些 Unicode 字符)

-- 第三步:用 COPY 命令导出,显式指定编码
COPY products TO '/tmp/products_utf8.csv'
  WITH (ENCODING 'UTF8', FORMAT csv, HEADER);

-- 第四步:导入时也可以指定编码,数据库自动转换
COPY products FROM '/tmp/products_gbk.csv'
  WITH (ENCODING 'GBK', FORMAT csv, HEADER);

如果数据量很大,建议先用小批量数据做一次试导入,确认没有乱码或截断后再全量迁移。

8.5 常见问题——排查清单

乱码了?按这个顺序查:

-- 1. 服务端用的什么编码
SHOW server_encoding;

-- 2. 客户端用的什么编码
SHOW client_encoding;

-- 3. 两个一样吗?不一样就统一
SET client_encoding TO 'UTF8';

中文排序不对?

-- 看看排序规则是不是中文的
SHOW lc_collate;

-- 临时用中文排序规则查一下
SELECT * FROM users ORDER BY name COLLATE "zh_CN";

插入中文报"值太长"?

-- 大概率是字节/字符语义搞混了
SHOW nls_length_semantics;

-- 改成按字符计算
SET nls_length_semantics = 'CHAR';

-- 或者建表时直接写明
CREATE TABLE t (name VARCHAR2(50 CHAR));  -- 50个字符,不是50个字节

LIKE 查询走不上索引?

-- 非C区域下 LIKE 默认不用索引,单独建一个 C 排序规则的索引
CREATE INDEX idx_name_c ON users (name COLLATE "C");

-- 然后查询时也指定 C 排序规则
SELECT * FROM users WHERE name COLLATE "C" LIKE '张%';

九、收尾:记住这五条就够了

字符编码这种事,提前花一小时规划好,能省掉后面无数个深夜排查乱码的小时。

  1. 统一 UTF-8:服务器、客户端、应用,全链路 UTF-8,省去 90% 的编码问题。
  2. 初始化时就定好区域LC_COLLATELC_CTYPE 创建后改不了,别等建完库才想起来。
  3. 灵活使用排序规则:列级别、表达式级别的 COLLATE 子句让不同语言各自排序,互不干扰。
  4. 分清"字符"和"字节":多字节编码下,建表时用 CHAR 限定符避免中文被意外截断。
  5. 迁移前先确认编码兼容性:目标字符集必须是源字符集的超集,否则可能丢数据。
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐