MySQL CONVERT TO CHARACTER SET utf8mb4 会按4字节/字符预分配致空间翻倍;需先查字节分布,再选合适字符集与排序规则,并注意PG/SQLite的编码迁移陷阱。
MySQL 中 ALTER TABLE ... CONVERT TO CHARACTER SET 会悄悄膨胀数据
直接用 convert to character set utf8mb4 改字段,哪怕源数据全是 ascii,innodb 也会按最大可能字节(4 字节/字符)预分配空间,导致 varchar(255) 实际占用翻倍。这不是 bug,是 innodb 的行格式和排序规则决定的。
- 改之前先查真实字节分布:
SELECT MAX(LENGTH(column_name)), AVG(LENGTH(column_name)) FROM table_name; - 如果结果全 ≤127,说明实际只用了 ASCII,可安全切到
latin1或ascii - 若含中文但无 emoji,
utf8mb3(即 MySQL 的utf8)比utf8mb4节省 25% 存储(3 字节 vs 4 字节) - 别只改列字符集,必须同步指定
COLLATE,否则隐式转换可能引发索引失效
PostgreSQL 里 CONVERT 不生效?得用 USING 显式转码
PostgreSQL 没有 MySQL 那种一键 CONVERT 语法,ALTER COLUMN TYPE 默认只是逻辑类型变更,不触发声码转换。字段存的是二进制字节流,编码错位就直接变乱码。
- 安全做法是:
ALTER COLUMN col TYPE TEXT USING convert_from(col::bytea, 'GBK')::TEXT; - 常见坑:源编码猜错(比如把
gbk当gb2312),会导致部分中文变成 - 大表慎用:
USING会锁表 + 全量重写,建议在低峰期加CONCURRENTLY(仅限索引)或分批更新 - 验证是否成功:用
SELECT encode(col::bytea, 'escape')看原始字节,对比预期编码的十六进制值
前端输入框限制 UTF-8 字节数,而不是字符数
用户输一个 emoji(如 ?),JavaScript 的 .length 是 1,但 UTF-8 编码占 4 字节——后端若按字符数截断(如 SUBSTR(col, 1, 20)),入库时可能被截半,存成损坏字节。
- 前端校验用
new TextEncoder().encode(str).length获取真实字节数 - 后端入库前必须做字节级截断,MySQL 可用
LEFT(CAST(col AS BINARY), 20),PG 用left(col::bytea, 20)::text - 数据库字段长度定义应基于字节上限:比如
VARCHAR(60)存 emoji 主要内容,而非拍脑袋定VARCHAR(20) - ORM 层(如 Django 的
CharField)默认按字符计数,需手动覆盖db_collation或加 pre-save 钩子
SQLite 的 PRAGMA encoding 是全局开关,改完要重建表
SQLite 的编码设置不是 per-column 的,PRAGMA encoding = 'UTF-8' 只影响新创建的表;已存在的表仍按建表时的编码解析字节——所以改了 pragma 后读老数据,可能把原本 GBK 存的字节当 UTF-8 解,全变问号。
- 确认当前编码:
PRAGMA encoding; - 迁移步骤:导出为 SQL → 修改 dump 文件里的字符串字节(用 Python
open(..., encoding='gbk').read().encode('utf-8'))→ 清空 DB → 重新导入 - 没备份别硬改:
REINDEX和VACUUM都不改变已有数据的字节解释方式 - 嵌入式场景注意:不同版本 SQLite 对
encoding的兼容性差异大,3.8+ 才稳定支持 UTF-16










