登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  数据库 >  MySQL

MySQL 字符集迁移怎样避免索引长度变化

来源:17golang原创

时间:2026-10-14 09:11:39 156浏览 收藏

把旧表从 utf8mb3 或其他单字节字符集迁移到 utf8mb4 时,真正需要提前算的是索引占用的字节数,而不是字段声明里的字符数。utf8mb4 最多按每个字符 4 字节计算;单列索引可能仍然安全,但多个字符串列组成的联合索引、旧的 COMPACT 行格式或较小的 InnoDB 页大小,都可能让迁移在重建索引时失败。

官方资料:https://dev.mysql.com/doc/refman/8.4/en/charset.html

本文按“盘点字符列—估算索引—选择迁移方案—在隔离副本中复查”的顺序说明。重点不是把所有索引都缩短,而是先保住字符集语义,再只处理确实触碰容量边界的索引。

  • 先记住:VARCHAR(255) 表示最多 255 个字符,不等于 255 字节。
  • 再看限制:InnoDB DYNAMIC/COMPRESSED 行格式通常按 3072 字节索引键前缀上限判断,COMPACT/REDUNDANT 则是 767 字节;页大小变小后上限还会下降。
  • 最后迁移:在副本中完成结构变更、数据转换和索引复查,再安排生产表的变更窗口。

为什么字符集一换,索引就可能变长

字符集决定一个字符最多需要多少存储字节。MySQL 8.4 文档说明,utf8mb3 最多 3 字节,utf8mb4 最多 4 字节;后者还能表示补充平面字符。于是,VARCHAR(255) 的最坏估算会从 255×3=765 字节变成 255×4=1020 字节。这个估算还没有加上联合索引中其他列以及索引内部开销。

单列 VARCHAR(255) 在 3072 字节上限下通常不会因为字符集迁移就超限,但下面这个联合索引就应该先算:

-- 只做容量估算,不代表已经在生产库执行
CREATE TABLE customer (
  nickname VARCHAR(255) NOT NULL,
  tenant_code VARCHAR(100) NOT NULL,
  region_code VARCHAR(32) NOT NULL,
  KEY idx_customer_lookup (nickname, tenant_code, region_code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;

-- utf8mb4 的最坏字节数可按字符数乘以 4 粗估
-- 255×4 + 100×4 + 32×4 = 1548 字节,仍需结合行格式和页大小判断
utf8mb3、utf8mb4、VARCHAR字符数、联合索引与InnoDB索引字节上限的静态关系说明图
图1:索引字节数结构说明图,展示字符集最大字节数如何影响 VARCHAR 索引边界;这是静态说明图,不是运行截图。

需要特别注意的是,索引限制不只针对显式的前缀索引,也适用于完整列索引。InnoDB 使用 DYNAMIC 或 COMPRESSED 行格式时,索引键前缀上限是 3072 字节;COMPACT 或 REDUNDANT 行格式时是 767 字节。若实例使用 8KB 或 4KB 页,上限还会按页大小降低,因此不能只套用 3072 这个数字。

迁移前先把索引和存储边界列出来

字符集可能在服务器、数据库、表和列多个层级上分别设置。迁移前先确认目标表真正的引擎、行格式、默认字符集、每个字符串列的字符集,以及联合索引包含哪些列。下面的查询只用于盘点,不改变数据。

-- 先看表级默认字符集、排序规则和行格式
SHOW CREATE TABLE customerG
SHOW TABLE STATUS LIKE 'customer';

-- 再看每个字符列的声明长度和实际字符集
SELECT TABLE_NAME, COLUMN_NAME, COLUMN_TYPE,
       CHARACTER_SET_NAME, COLLATION_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
  AND TABLE_NAME = 'customer'
  AND CHARACTER_SET_NAME IS NOT NULL;

-- 最后展开索引列顺序;SEQ_IN_INDEX 用来识别联合索引的组成
SELECT INDEX_NAME, SEQ_IN_INDEX, COLUMN_NAME,
       SUB_PART, NON_UNIQUE
FROM INFORMATION_SCHEMA.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
  AND TABLE_NAME = 'customer'
ORDER BY INDEX_NAME, SEQ_IN_INDEX;

盘点结果里有三个信号值得优先处理:第一,索引列是多个可变长度字符串;第二,表仍使用 COMPACT 或 REDUNDANT;第三,实例页大小不是默认的 16KB。对这三类表,不要把“字段总长度小于 767 个字符”当成安全结论,应该按字符集最大字节数重新估算。

MySQL字符集迁移前的字符列、索引定义、行格式页大小、隔离副本和复查清单结构图
图2:迁移边界结构图,帮助读者把列定义、索引定义和 InnoDB 存储约束放在同一张图上核对;这是结构说明图,不是运行截图。

选择方案时优先保住字符集语义

最稳妥的顺序是先决定业务需要的字符集和排序规则,再处理索引容量。MySQL 官方建议在可能的情况下使用 utf8mb4;为了绕过索引报错而把整张表退回旧字符集,会留下无法保存补充平面字符的隐患。

如果索引估算在当前限制内,可以直接做表级转换;如果超限,再按下面的优先级处理:

  1. 确认表是否可以使用 ROW_FORMAT=DYNAMIC 或 COMPRESSED,并结合实例页大小重新核对。
  2. 对只需要前缀检索的长文本列缩短索引前缀,例如把 VARCHAR(255) 的索引前缀控制在业务可接受的长度。
  3. 重新设计联合索引,让真正用于等值过滤和排序的列保留在前面,避免为了凑一个宽索引把多个长字符串全部塞进去。
  4. 不要只改 VARCHAR(255) 为更小的声明长度;先确认业务是否允许截断,以及排序、唯一性和查询选择性是否会改变。
-- 先在迁移副本中统一字符集和排序规则
ALTER TABLE customer
  CONVERT TO CHARACTER SET utf8mb4
  COLLATE utf8mb4_0900_ai_ci;

-- 如果旧行格式是 COMPACT,先单独评估改为 DYNAMIC 的影响
ALTER TABLE customer ROW_FORMAT = DYNAMIC;

-- 只在业务允许按前缀识别时缩短索引;前缀长度不是万能默认值
ALTER TABLE customer
  DROP INDEX idx_customer_lookup,
  ADD INDEX idx_customer_lookup (nickname(191), tenant_code, region_code);

上面几个变更不应机械地连续执行。更安全的做法是先在隔离副本中分别记录原始 DDL、数据量、索引定义和代表性查询,再选择一种方案做完整演练。特别是唯一索引,缩短前缀可能让原本不同的值在索引前缀层面发生碰撞,不能只看 DDL 是否成功。

执行后怎样确认迁移没有改变索引意图

迁移完成后至少复查四件事:所有目标字符列已经使用预期字符集;排序规则符合比较语义;索引名称、列顺序和唯一性没有意外变化;代表性查询仍然能使用预期索引。这里的“确认”指迁移流程中的结构复查,不是把一次 DDL 成功当成完整验证。

-- 复查表级定义和索引列顺序
SHOW CREATE TABLE customerG
SHOW INDEX FROM customer;

-- 复查字符集是否已经落到列级定义
SELECT COLUMN_NAME, CHARACTER_SET_NAME, COLLATION_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
  AND TABLE_NAME = 'customer'
  AND CHARACTER_SET_NAME IS NOT NULL;

-- 用执行计划观察代表性查询是否仍走目标索引
EXPLAIN SELECT customer_id
FROM customer
WHERE nickname = 'alice'
  AND tenant_code = 'tenant-a';

如果生产变更必须拆分,先处理低风险表或影子表,保留原表与回滚方案,再安排依赖它的外键、唯一约束和应用连接字符集。连接端也要统一,否则表已经是 utf8mb4,应用仍以旧字符集通信时,问题会从索引长度变成数据读写或比较结果异常。

常见疑问

utf8mb4 一定会让所有索引翻成 4 倍吗?

不会。4 字节是单个多字节字符的最大值,实际数据可能主要由占用更少字节的字符组成;但索引容量评估应使用最坏情况,不能只拿当前样本数据判断。

把索引前缀统一改成 191 就一定安全吗?

也不一定。191 只是按 4 字节字符与 767 字节边界得到的常见经验值,联合索引、页大小、排序规则、唯一性要求和查询选择性都可能改变结论。先算完整索引,再决定是否使用前缀。

最简单的迁移写法是什么?

对结构简单且索引容量明确的表,可以在副本中执行 ALTER TABLE ... CONVERT TO CHARACTER SET utf8mb4,然后核对表定义和索引。不要跳过副本演练,也不要把命令返回成功等同于业务查询已保持不变。

小结

避免 MySQL 字符集迁移引发索引长度问题,核心是把“字符数”换算成“最坏字节数”,同时读取表的行格式和实例页大小。先用 SHOW CREATE TABLE、INFORMATION_SCHEMA.COLUMNS 和 INFORMATION_SCHEMA.STATISTICS 找出高风险索引,再优先保留 utf8mb4,必要时调整行格式、前缀或联合索引设计,最后在隔离副本中完成数据与查询复查。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>