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

MySQL utf8mb4 索引为什么超长:前缀索引、排序规则与唯一性取舍

来源:17golang原创

时间:2026-07-26 12:32:26 499浏览 收藏

老订单表把 email 定义成 VARCHAR(255),原来用 utf8 时唯一索引一直正常;切换到 utf8mb4 后,执行 ALTER TABLE 却收到“Specified key was too long”的错误。问题不在于邮箱突然变长,而在于索引长度按字符集的最大字节数计算。处理这类迁移时,最先要保住的是业务上的唯一性语义,而不是简单把索引前缀砍短。

索引超长报错不是字符数超了,是utf8mb4按单字符最大4字节折算后的总字节数触到引擎上限,优先保障业务唯一性,不要为了绕过报错直接削索引长度。

要点速览

  • utf8mb4 一个字符最多按 4 个字节计算,VARCHAR(255) 的索引预算可能达到 1020 字节。
  • InnoDB 常见 3072 字节上限不是“字符数上限”,联合索引还要把各列和长度前缀一起算进去。
  • 前缀索引适合缩小扫描范围,但不能单独保证整列字符串唯一。
  • 需要严格唯一时,优先缩短业务字段或增加确定性哈希辅助列,并保留碰撞后的完整值复核。

一次 utf8mb4 迁移,为什么会卡在索引创建

用用户邮箱做登录标识是常见场景:

CREATE TABLE account (
  id BIGINT PRIMARY KEY,
  email VARCHAR(255) NOT NULL,
  display_name VARCHAR(120) NOT NULL,
  UNIQUE KEY uk_account_email (email)
) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;

计算时要看索引涉及的字节数,不要只看 255 这个字符数。单列邮箱通常还没触到 InnoDB 上限,但历史表常见的是多列联合唯一键,例如租户、邮箱、来源一起参与约束;再叠加较长的 VARCHAR,总预算很快就会超过限制。

SHOW FULL COLUMNS FROM account;
SHOW CREATE TABLE account;

先确认真实字符集、排序规则、存储引擎和索引列顺序。某些环境的默认排序规则不同,比较规则和索引大小也可能与开发库不一致,不能只凭迁移脚本里的旧定义下结论。

MySQL utf8mb4 迁移中联合唯一索引字节预算超限的表结构证据与错误提示插画

先算清预算,再决定缩哪一部分

估算一条索引的粗略方法是把每个字符串列的最大字符数乘以字符集最大字节数,再加上其他列的固定字节与索引开销。它不是替代数据库验证的精确公式,却足以在设计阶段发现明显超预算的定义。

-- 例如租户编号 + 邮箱的联合唯一键
tenant_id BIGINT       -- 8 字节
email    VARCHAR(255)  -- utf8mb4 按最大 4 字节估算:1020 字节

如果只是查询邮箱前缀,可以使用前缀索引:

ALTER TABLE account
  ADD KEY idx_email_prefix (email(191));

但这里要特别留意:email(191) 只比较前 191 个字符。它能帮助 LIKE 'abc%' 或相似条件缩小范围,却不能保证两个前缀相同、后半段不同的邮箱不会同时写入。把它直接改成唯一前缀索引,是一个很容易把数据约束悄悄改弱的决定。

三种方案的取舍:查询快、约束强,不能混为一谈

方案一:缩短业务字段

如果业务能明确邮箱、外部账号或订单号的最大长度,直接把列收紧到合理值最简单。迁移前先统计现有最大长度和超长样本,确认应用校验、接口文档、导入脚本都接受新边界。这种方案的约束最直观,索引也最容易理解。

方案二:普通前缀索引

它适合搜索和排序,不适合作为完整唯一性证明。前缀长度要根据数据分布测量,不是固定照搬 191;可以对不同长度做基数统计,观察前缀区分度是否足够。

SELECT
  COUNT(*) AS total_rows,
  COUNT(DISTINCT LEFT(email, 120)) AS distinct_prefix
FROM account;

方案三:哈希辅助列加完整值复核

需要严格唯一、原字符串又不方便缩短时,可以增加固定长度的确定性摘要列并建立联合唯一索引:

ALTER TABLE account
  ADD COLUMN email_sha BINARY(32)
    GENERATED ALWAYS AS (UNHEX(SHA2(LOWER(email), 256))) STORED,
  ADD UNIQUE KEY uk_email_sha (email_sha);

生产实现不要把哈希碰撞当成数学上绝对不可能。写入流程仍应在摘要命中后用完整 email 做二次判断;若结果不同,记录冲突并停止写入。若排序规则要求大小写或重音不敏感,摘要输入也必须与数据库比较语义一致。

MySQL 索引超长时从缩短字段、前缀索引到哈希辅助列的决策路径与唯一性边界插画

迁移前后要验证四个结果

  • 结构验证:SHOW CREATE TABLE 中字符集、排序规则、列长度和索引顺序符合设计。
  • 数据验证:现有值没有被截断,大小写、重音和尾部空格的比较结果符合业务预期。
  • 约束验证:故意写入相同完整值应失败;只共享前缀但完整值不同的样本应按设计处理。
  • 性能验证:对真实分布运行 EXPLAIN,确认搜索条件使用了目标索引,而不是只看索引名称存在。

大表变更还要单独评估执行窗口、空间峰值和回退方式。可以先在副本上重放数据,再用低峰流量做小批量验证;不要为了躲过一次长度报错,直接在线上删掉旧约束。

相关问题:前缀索引和唯一索引怎么选

utf8mb4 一定要把 255 改成 191 吗?

不一定。191 是历史上常见的保守值,不是所有表都必须遵循的规则。应根据 MySQL 版本、索引类型、联合列总长度和业务字段上限计算并验证。

唯一前缀索引能防止邮箱重复吗?

只能防止索引前缀重复,不能证明完整邮箱重复。登录标识这类强约束字段,优先使用完整可比较的值、缩短字段或哈希辅助列方案。

排序规则会影响唯一索引吗?

会。大小写、重音以及某些尾部空格的比较规则会决定哪些值被认为相同。迁移前要用业务样本验证,而不是只看列定义中的字符集名称。

把索引长度问题当成数据模型问题处理

索引报错只是表结构把真实约束暴露出来的时刻。查询型前缀索引、严格唯一约束和字符串搜索是三件事,分别设计、分别验收,后续维护会比“统一改成 191”可靠得多。完成迁移后,把字符集、排序规则、字段上限与唯一性规则写进表结构说明,下一次扩展字段时就不会重新踩同一个坑。

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