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

MySQL 不同字符集列的索引长度参数对比

来源:17golang原创

时间:2026-10-31 23:30:24 118浏览 收藏

这类问题通常出现在把旧表从 utf8mb3 迁移到 utf8mb4 之后:原来能创建的 INDEX(name(255)) 突然报索引过长。根因不是“255 这个参数失效”,而是 MySQL 对非二进制字符串把前缀参数按字符数解释,却按字节数检查索引上限。utf8mb4 最多使用 4 字节表示一个字符,所以同样的 255 个字符,最坏情况会占 1020 字节。

官方文档:https://dev.mysql.com/doc/refman/8.4/en/column-indexes.html

要点速览
  • 非二进制字符串的 INDEX(col(N)) 中,N 按字符数写;索引前缀上限按字节数算。
  • 保守换算公式是:安全字符数 = floor(字节上限 ÷ 字符集最大字节数)。
  • 迁移前要同时看字符集、InnoDB 行格式、联合索引的总和与前缀选择性,不能只把 255 改成 191。

影响面:同一个 255 参数为什么在迁移后失败

我在排查这类迁移错误时,先把问题拆成两个单位:DDL 中的长度是字符,存储引擎限制是字节。MySQL 文档明确说明,CHAR、VARCHAR、TEXT 的索引前缀参数按字符解释;BINARY、VARBINARY、BLOB 则按字节解释。

因此旧表若使用 utf8mb3,255 个字符按最坏情况估算为 255 × 3 = 765 字节,可以贴近 767 字节上限。转换为 utf8mb4 后,最坏情况变为 255 × 4 = 1020 字节;在 COMPACT 或 REDUNDANT 行格式下就可能触发 Specified key was too long。

MySQL 字符集最大字节数、索引前缀字符数和字节上限之间的静态关系说明图
图1:结构说明图,展示字符数参数经过字符集字节宽度换算后落到 InnoDB 索引上限的关系,不是数据库运行截图。

时间线:先按字符集换算,再对照两种常见上限

可以用下面这张保守对照表快速判断。它使用字符集的最大字节数,而不是某一批实际数据的平均字节数;这样迁移时不会因为当前样本 mostly 是 ASCII 而低估风险。

字符集最大字节/字符767 字节上限的保守前缀3072 字节上限的保守前缀
ascii / latin11767 字符3072 字符
utf8mb33255 字符1024 字符
utf8mb44191 字符768 字符
utf16最多 4按 4 字节保守估算 191 字符按 4 字节保守估算 768 字符

767 字节适用于 InnoDB 的 COMPACT 或 REDUNDANT 行格式;DYNAMIC 或 COMPRESSED 可使用到 3072 字节的前缀上限。这里的“安全前缀”是最坏字节数下的整数下取整,不代表前缀越长查询就一定越快。

-- 用表定义确认列字符集和行格式,再决定迁移参数。
SHOW CREATE TABLE customer;

-- 查看索引是否已经是前缀索引,Sub_part 对字符串表示字符数。
SHOW INDEX FROM customer;

-- 用最坏字节数做迁移前的保守估算。
-- utf8mb4 在 767 字节上限下:FLOOR(767 / 4) = 191。

触发条件:单列能算对,联合索引仍可能超限

迁移中第二个容易漏掉的点是联合索引。比如 (title(191), slug(191)) 的两个前缀都分别符合 utf8mb4 的 767 字节单列换算,但它们放在同一个索引键中时,必须按各 key part 的最坏字节数合计,再结合该存储引擎的总键长度限制判断。列字符集不同,也要分别使用各自的最大字节数。

另外,VARCHAR(255) 的列长度和 INDEX(col(191)) 的索引前缀不是同一个参数:前者决定列最多存多少字符,后者只把前 N 个字符放进索引。把列直接改成 VARCHAR(191) 会改变数据模型,只有确认业务不需要更长值时才这样做;仅为了降低索引大小,优先讨论索引前缀。

MySQL 联合索引中不同字符集列分别换算字节预算并汇总的静态关系说明图
图2:边界说明图,展示联合索引各列按自身字符集换算后共享键长度预算,不是执行计划或运行结果截图。

根因修复:缩短前缀、调整行格式,还是重新评估选择性

如果目标表确实是旧行格式,且业务只需要前缀查找,最小改动通常是把 utf8mb4 的前缀从 255 调整为 191,再单独评估唯一索引是否仍能满足唯一性。唯一索引不能因为超限而悄悄缩短,否则可能把原本不同的值压成相同的索引键。

如果表可以采用 DYNAMIC 或 COMPRESSED 行格式,则可以使用更大的 3072 字节预算,但这仍不是“所有索引都写 768”的理由:联合索引、实例页大小和列的实际业务区分度都需要重新核对。前缀太短会降低选择性,让 MySQL 在索引筛选后回表检查更多记录。

-- 仅示意旧表迁移;先在变更窗口确认行格式和索引用途。
ALTER TABLE customer
  MODIFY title VARCHAR(255) CHARACTER SET utf8mb4 NOT NULL,
  DROP INDEX idx_title,
  ADD INDEX idx_title (title(191));

-- 调整后再次查看索引定义,确认前缀参数已经落在目标列上。
SHOW INDEX FROM customer WHERE Key_name = 'idx_title';

防复发:把“字符参数”与“字节预算”写进迁移清单

  • 先读 SHOW CREATE TABLE,记录每个索引列的字符集、排序规则和 InnoDB 行格式。
  • 按最大字节数计算,而不是拿现有数据样本的平均长度计算。
  • 联合索引按所有 key part 的最坏情况汇总;涉及唯一索引时,先确认缩短前缀不会破坏唯一性。
  • 前缀缩短后关注选择性;必要时改为更适合查询的列组合,而不是无条件放大索引。

常见问题

为什么 utf8mb4 常见的前缀长度是 191?

在 767 字节的旧 InnoDB 前缀上限下,按每字符最多 4 字节保守计算,floor(767 / 4) = 191。这不是 utf8mb4 的固定语法限制,而是字节上限换算结果。

实际数据大多是英文,可以按 1 字节计算吗?

不建议用于结构迁移。索引定义要能容纳列允许的最坏字符,实际值偏 ASCII 只能帮助理解平均占用,不能替代上限计算。

把行格式改成 DYNAMIC 后就不用管前缀了吗?

不用。它只把某些 InnoDB 表的前缀预算提高到 3072 字节,联合索引合计、页大小、唯一性和前缀选择性仍然要单独判断。

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