云计算百科
云计算领域专业知识百科平台

MySQL utf8mb4 改造实战:字符集迁移与索引长度墙

个人主页:
> for_ever_love__ <(欢迎各位大佬莅临😊)

其他栏目:
> 大模型开发从0到1 <

其他栏目:
> iOS项目总结大全 <

其他栏目:
> 我想学python了 <

其他栏目:
> iOS UI <

文章目录

  • MySQL utf8mb4 改造实战:字符集迁移与索引长度墙
    • 一、MySQL 的 utf8 为什么是假的
    • 二、MySQL 字符集的四个层级
    • 三、先盘点:现状是什么样的
      • 3.1 全库字符集分布
      • 3.2 连接层是不是 utf8mb4
      • 3.3 排序规则是否一致
    • 四、索引长度墙:改造必踩的坑
    • 五、改造实施:三种做法选哪个
      • 5.1 在线改:ALTER TABLE … CONVERT TO
      • 5.2 双写过渡:新库 utf8mb4,老库保持
      • 5.3 工具化:pt-online-schema-change / gh-ost
    • 六、端到端配置清单
      • 6.1 服务端
      • 6.2 连接层驱动
      • 6.3 应用侧
    • 七、灰度与回滚
    • 八、验收清单
    • 九、小结

MySQL utf8mb4 改造实战:字符集迁移与索引长度墙

一张存用户昵称的表,插进一个 emoji 就报 Incorrect string value;一个改字符集的 ALTER,把索引长度顶爆 Specified key was too long;还有更冤的——历史库明明是 utf8,用户资料里的生僻字却变成了一串问号。这三个坑,都指向同一件事:MySQL 的 utf8 是个"假 utf8"。这篇文章把 utf8mb4 改造完整过一遍:为什么必须迁、四层字符集怎么设、索引长度墙怎么破、以及存量库灰度改造的标准动作。

一、MySQL 的 utf8 为什么是假的

MySQL 的 utf8 字符集最多只存 3 个字节(utf8mb3),它覆盖 BMP 基本多文种平面,但存不了 Emoji、生僻字、部分数学符号——这些在 UTF-8 里需要 4 字节。

CREATE TABLE t_demo (name VARCHAR(32)) CHARSET=utf8;

INSERT INTO t_demo VALUES ('😀'); — ERROR 1366 (HY000): Incorrect string value

想存这些,必须用 utf8mb4(真正的 UTF-8,1~4 字节)。

另一个坑是:万一客户端连接用的字符集和服务器不匹配,4 字节数据在链路上可能被截断成乱码或直接报错。所以 utf8mb4 改造不是"改个表Charset"就完了,它涉及库、表、列、连接四个层次,外加客户端驱动——少一层,改造就不完整。

二、MySQL 字符集的四个层级

改造前必须建立的全局观——字符集有四层,从粗到细:

| 层级 | 变量 / 位置 | 默认 | 说明 |

|—|—|—|—|

| 服务器级 | character_set_server | utf8mb4(8.0 起) | 新建库的默认 |

| 库级 | CREATE DATABASE … CHARSET | 继承服务器 | 新建表的默认 |

| 表级 | CREATE TABLE … CHARSET | 继承库 | 新建列的默认 |

| 列级 | 列定义里的 CHARSET | 继承表 | 最终生效值 |

外加一条独立的链路——连接字符集,它不属于上面任何一层,但决定了客户端和服务端之间怎么转换:

| 变量 | 作用 |

|—|—|

| character_set_client | 客户端发来的语句用什么编码 |

| character_set_connection | 语句被解析时的编码 |

| character_set_results | 返回给客户端的结果编码 |

改造要做的就是:库表列统一 utf8mb4,链路三个变量也统一 utf8mb4。 只改表不改连接,4 字节字符过不了链路;只改连接不改表,历史表依然存不了。

MySQL 8.0 的默认值已经全面 utf8mb4(character_set_server=utf8mb4、collation_server=utf8mb4_0900_ai_ci),新建库直接用默认即可,本文重点讲历史库改造。

三、先盘点:现状是什么样的

改造第一步不是动手,是把家底摸清。

3.1 全库字符集分布

— 每个库每个表的字符集分布

SELECT table_schema, table_name, column_name, character_set_name, collation_name, data_type

FROM information_schema.columns

WHERE table_schema NOT IN ('mysql','sys','information_schema','performance_schema')

ORDER BY table_schema, table_name, ordinal_position;

重点看三类:character_set_name = 'utf8'(假 utf8,要迁)、'latin1'(西欧,注释可能是乱码)、'gbk'(要评估,不是都要迁)。

3.2 连接层是不是 utf8mb4

SHOW VARIABLES LIKE 'character_set_%';

SHOW VARIABLES LIKE 'collation%';

如果 character_set_client/connection/results 是 utf8(mb3),即便表是 utf8mb4,客户端写入 Emoji 一样会在报错或截断在链路上。

3.3 排序规则是否一致

同是 utf8mb4,utf8mb4_general_ci 和 utf8mb4_0900_ai_ci 的排序行为不同:

  • general_ci:MySQL 5.7 时代的默认,精度粗,对中日韩字符的排序近似;

  • 0900_ai_ci:MySQL 8.0 默认,基于 Unicode 9.0,ai = 口音不敏感(é = e),ci = 大小写不敏感;

  • utf8mb4_0900_as_cs:口音敏感、大小写敏感,适合用户名精确匹配。

同一条 JOIN 或 UNION 两边的 collation 不一样,会报 Illegal mix of collations。 改造时要么全统一,要么对字段显式 COLLATE。

四、索引长度墙:改造必踩的坑

utf8mb4 每个字符最多 4 字节,同样 VARCHAR(255) 的索引,utf8 下占 765 字节,utf8mb4 下占 1020 字节——于是撞上了 InnoDB 的索引长度限制:

| 限制 | 值 | 说明 |

|—|—|—|

| 单列索引最大字节 | 767 字节 | 对应 antelope 文件格式下 VARCHAR(255) utf8mb3 |

| 大前缀后 | 3072 字节 | Barracuda + innodb_large_prefix,MySQL 5.7.7+ 默认开启 |

3072 字节 ÷ 4 = utf8mb4 下单列 VARCHAR 索引最长 768 字符。所以:

  • VARCHAR(255) + utf8mb4 + 普通索引 = 1020 字节,在没开大前缀的老实例上直接报错 Specified key was too long; max key length is 767 bytes;

  • 解决路径按优先级:

— 方案 1:确认实例已支持大前缀(5.7.7+ 默认,8.0 无此变量,恒支持)

SHOW VARIABLES LIKE 'innodb_large_prefix'; — 8.0 已移除,永远支持 3072

SHOW VARIABLES LIKE 'innodb_file_format'; — 应为 Barracuda

— 方案 2:改前缀索引(保留语义的最优解)

ALTER TABLE t_user ADD INDEX idx_name (name(191)); — 191 × 4 = 764 < 767

— 方案 3:改列长度

ALTER TABLE t_user MODIFY name VARCHAR(191) ...;

191 这个魔法数字就是从 767 字节反推的(767 ÷ 4 = 191.75,取整 191),大量 MyBatis-Plus、Spring Boot 老项目里 varchar(191) 的写法就是为绕这个限制。新库如果直接 utf8mb4 + 3072 上限,可以用到 768。

还有个连带坑:ROW_FORMAT 也受影响。改 utf8mb4 前建议表已经是 DYNAMIC(8.0 默认);老库如果是 COMPACT,先 ALTER TABLE … ROW_FORMAT=DYNAMIC 再改字符集,两次 ALTER 不如合并成一次写。

五、改造实施:三种做法选哪个

存量库改造有三种路线,按数据量和停机窗口选。

5.1 在线改:ALTER TABLE … CONVERT TO

ALTER TABLE t_user CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;

它会把表和所有字符列一起转。三个注意点:

  • 十有八九要重建表——大表上这是一次 table rebuild,期间锁表(5.6 之前全程 DML 阻塞,5.6+ 有 online DDL,但转换字符集这个操作在部分版本会 fallback 到 copy)。数据量大的表执行时间几十分钟到几小时。

  • 只改表不改列用 MODIFY,只改列 charset 用 ALTER TABLE t MODIFY name VARCHAR(255) CHARACTER SET utf8mb4。CONVERT TO 是全表扫描转换,MODIFY DEFAULT CHARACTER SET 只改默认值不影响存量列——看语义选。

  • MODIFY 列时必须把完整列定义重写一遍(类型、是否可空、默认值),MySQL 不允许只改 charset 一个属性。

  • 5.2 双写过渡:新库 utf8mb4,老库保持

    新业务全部建 utf8mb4;老库维持现状,写代码时对 emoji 做容错(拒绝或替换)。这是最低风险路线,代价是老库永远带着技术债。

    5.3 工具化:pt-online-schema-change / gh-ost

    大表改造的标准姿势:

    pt-online-schema-change \\

    –alter "CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci" \\

    D=bizdb,t=t_user \\

    –execute \\

    –chunk-size 2000

    pt-osc 走"影子表 + 触发器同步增量"的老路,gh-ost 走 binlog 增量更新的新路。两者共同点:长事务期间对主库压力可控,从库延迟可观测,可随时中止。各有坑(pt-osc 触发器对写入有开销;gh-ost 需要 row 格式 binlog),使用前读官方文档,先在从库演练。

    六、端到端配置清单

    改造不止数据库,全链路都要 utf8mb4:

    6.1 服务端

    [mysqld]

    character-set-server = utf8mb4

    collation-server = utf8mb4_0900_ai_ci

    init_connect = 'SET NAMES utf8mb4' # 老版本兜底,8.0 可省

    对 5.7 老实例,init_connect 给没显式 SET NAMES 的老客户端兜底(注意它对 SUPER 用户不执行,这是设计使然)。

    6.2 连接层驱动

    | 语言/驱动 | 配置 |

    |—|—|

    | JDBC | URL 加 characterEncoding=utf8mb4&useUnicode=true(新驱动可省) |

    | PyMySQL | charset='utf8mb4',连接建立时自动 SET NAMES |

    | Go | collation=utf8mb4_unicode_ci + loc |

    | PHP mysqli/PDO | SET NAMES utf8mb4 或 DSN 加 charset=utf8mb4 |

    | Node mysql | charset: 'utf8mb4' |

    验证方法:连接后执行 SHOW VARIABLES LIKE 'character_set_connection',三个变量都得是 utf8mb4。很多"明明改了驱动却还报错"的案子,最后发现是连接池里的老连接没重建。

    6.3 应用侧

    • HTML 页面 <meta charset="utf-8">;

    • HTTP 响应 Content-Type: …; charset=utf-8;

    • JSON 序列化 UTF-8;

    • 老数据从 GBK/latin1 导出再导入时,先用 iconv 转 UTF-8,不要直接灌(会把乱码固化进 utf8mb4)。

    七、灰度与回滚

    改造的标准节奏:

  • 预发环境演练:找数据量最大的表走一遍计时,测读写、测备份恢复;

  • 索引长度预检:改造前跑一遍"所有索引的字节长度 / 字符长度",确认全部 < 3072 字节(或 191 字符前缀);

  • 备份先行:全备 + binlog 位点记录;

  • 从库先行:先在从库执行,确认无延迟、无报错后做主库;

  • 窗口期执行:业务低峰,precheck → ALTER → 校验行数/校验和;

  • 连接层滚动:应用发版切换驱动配置,观察连接报错;

  • 验证回归:插一条带 emoji 的数据,查回来比对;跑一遍核心读写;

  • 观察期:一周内盯慢日志里的字符集转换告警、乱码投诉。

  • 回滚预案:表级回滚就是反向 ALTER … CONVERT TO CHARACTER SET utf8——但已被截断的数据救不回来,比如从 latin1 迁到 utf8mb4 时如果源数据本身就是"问号乱码",迁过去还是乱码。所以改造前要先确认源数据是干净的 latin1 真数据,而不是二次损坏的产物。

    八、验收清单

    • SHOW VARIABLES 确认 server/database/connection/results 全 utf8mb4;

    • information_schema.columns 无残留 utf8(mb3)/ latin1 业务列;

    • 全库索引长度 < 3072 字节,超长的已改前缀索引;

    • 驱动/ORM 连接串全部带 utf8mb4;

    • 插入 emoji 与生僻字回归通过;

    • 无 Illegal mix of collations 报错;

    • 老系统(Excel 导入、CSV 导出)抽查无乱码。

    九、小结

    utf8mb4 改造的三条主线:

    • 为什么迁:MySQL 的 utf8 是 3 字节假货,emoji 和生僻字存不了,必须迁到 utf8mb4;

    • 改什么:库、表、列、连接四层全改,CONVERT TO 或 pt-osc/gh-ost 执行,索引 3072 字节墙用 191/768 前缀绕;

    • 怎么稳:从库先行、窗口期执行、连接层滚动、回滚预案先写好,验收用"插一条 emoji"做黄金判据。

    至此 ms5-7 整册 6 篇完成。接下来的 ms5-8 会进入生产运维深水区:慢日志分析与优化、监控告警体系、以及常用性能诊断场景。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » MySQL utf8mb4 改造实战:字符集迁移与索引长度墙
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!