(1071, ‘Specified key was too long; max key length is 767 bytes‘)

这个报错和ROW_FORMAT有关,在 MySQL 的 InnoDB 存储引擎中,ROW_FORMAT主要有4种格式,但在较旧的版本或特定语境下,常讨论的是前三种。以下是这四种格式的详细介绍和区别:

REDUNDANT (冗余格式)

来源:MySQL 5.0 之前的旧格式。

现状:不推荐使用,除非你需要迁移非常古老的数据库。

特点:

  • 为了向后兼容保留。
  • 每个列都存储了长度前缀,即使定长字段也存。
  • 缺点:占用空间大,性能较差。
COMPACT(紧凑格式) MySQL 5.0 - 5.7 默认

来源:MySQL 5.0 引入。

现状:在 MySQL 5.7 及之前是默认格式。现在依然广泛使用,但不如 DYNAMIC 灵活。

特点:

  • 去掉了 REDUNDANT中不必要的长度前缀。
  • NULL 值和长度为 0 的字段不占用存储空间 只占位标记 。
  • 比 REDUNDANT更节省空间,CPU 开销更小。
DYNAMIC(动态格式)MySQL 5.7 / 8.0 默认

来源:MySQL 5.7 引入,8.0 成为默认值。

现状:现代 MySQL 的推荐默认格式。

  • 基于COMPACT格式改进。
  • 关键区别:对于变长字段 如 VARCHAR, TEXT, BLOB ,如果数据太长无法完全存入主页面 Overflow Page ,DYNAMIC格式只会将前 20 字节 或更多,取决于实现 留在主页面,其余部分全部放到溢出页 Overflow Page 。
  • 而 COMPACT格式会尝试将前 768 字节留在主页面。
  • 优势:主页面能容纳更多的行索引记录,提高了缓冲池 Buffer Pool 的命中率,适合大字段较多的场景。
COMPRESSED(压缩格式)

来源:MySQL 5.1 引入。

现状:适用于读多写少、磁盘空间紧张且 CPU 资源充足的场景。注意:在 MySQL 8.0.13 之后,官方已弃用此格式,建议改用表空间压缩或其他手段。

特点:

  • 基于 COMPACT格式。
  • 使用 zlib 算法对数据和索引进行物理压缩。
  • 可以显著减少磁盘占用和 I/O,但会增加 CPU 负担 因为需要解压/压缩 。
  • 通常配合 KEY_BLOCK_SIZE使用。
总结
REDUNDANT COMPACT DYNAMIC COMPRESSED 特性
引入版本 < 5.0 5.0 5.7 5.1
默认状态 过时 5.7及以前默认 8.0 默认 需手动开启
大字段处理 全部存溢出页 前768字节在主页 仅前20字节在主页 压缩后存储
空间效率 最高 (但耗CPU)
性能 高 (缓存命中率高) 低 (CPU密集)
推荐程度 不推荐 可用 推荐 特定场景

在 MySQL 5.6 及更早版本 或者手动设置了innodb_large_prefix OFF 中:

  • COMPACT/ REDUNDANT格式限制:单个索引列的最大长度限制为 767 字节。
  • DYNAMIC /COMPRESSED 格式限制:如果启用了innodb_large_prefix ON MySQL 5.7 默认开启 ,最大长度限制提升至 3072 字节。

为什么会出现这个错误?最常见的情景是你在VARCHAR字段上创建索引,且使用了utf8mb4字符集。

  • 字符集计算:utf8mb4每个字符最多占用 4 个字节。
  • 临界点计算:767 bytes/4 bytes per char 191.75767 bytes/4 bytes per char 191.75。所以,在COMPACT格式下,utf8mb4的 VARCHAR 字段如果长度超过 191,创建索引就会报错。

例如下面的场景:

那有人可能会说这个idx_name不也才8个字符,为什么会超出限制呢?

这是一个非常经典的误解 这里有两个概念混淆了: 索引名的长度 和 索引列内容的长度 。报错 Specified key was too long指的不是索引的名字 idx_name 太长,而是你试图建立索引的那个字段 列 里的数据内容可能太长,超过了限制。

澄清概念

  • idx_name:这是索引对象的名称。MySQL 对索引名的限制通常是64个字符。idx_name只有 8 个字符,完全没问题。

  • key length (报错中的 key):指的是被索引的那一列数据在索引树中占用的字节数。

那有人可能又会说如果varchar(255)中我只存 abc 呢?VARCHAR确实是“弹性”的 变长 ,它只占用实际存储数据所需的字节数加上1-2个字节的长度前缀。如果里面存的是 abc ,它确实只占很少的空间。但是,索引 Index 的工作机制不同。索引 B Tree 是一个预分配结构。当 MySQL InnoDB 引擎创建索引时,它必须为索引中的每一个条目 Entry 预留足够的空间,以容纳该列可能出现的最大值。

在数据库底层:

  • 固定偏移量计算:InnoDB 需要知道索引记录中每一列的最大可能长度,以便快速计算下一条记录的起始位置。
  • 页内布局:在一个 16KB MySQL 或 8KB PG 的数据页中,引擎需要确保即使所有行都填满最大长度,页面也不会溢出到无法管理的状态。

因此,索引长度的限制是基于列定义的MAX LENGTH,而不是当前行的ACTUAL LENGTH 。

解决方案

8.0

将row_format设置为dynamic即可

1
alter table t1 row_format dynamic;

5.6/5.7

将row_format设置为dynamic,并将innodb_large_prefix设置为ON,这2个条件必须都生效