这个报错和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即可
alter table t1 row_format=dynamic;

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