(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)预留足够的空间,以容纳该列可能出现的最大值。

在数据库底层:

  1. 固定偏移量计算:InnoDB 需要知道索引记录中每一列的最大可能长度,以便快速计算下一条记录的起始位置。
  2. 页内布局:在一个 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个条件必须都生效

相关推荐
Leo.yuan1 小时前
数据目录和数据字典有什么区别?一文讲清
大数据·数据库·人工智能
全堆鸿蒙2 小时前
鸿蒙应用开发实战【37】— 数据统计:countByStatus 聚合查询
数据库·华为·harmonyos·鸿蒙系统
惜分飞2 小时前
记录block 0损坏,数据文件大量坏块,使用不当数据库版本恢复等各种操作之后的故障处理---惜分飞
数据库·block0损坏·ora-17503
执笔论英雄2 小时前
【大模型推理】redis kv cache
数据库·redis·缓存
渣渣灰飞2 小时前
MySQL 系统学习 第六阶段:Redis + 缓存 + 高并发设计 第八章:Redis 高级应用
学习·mysql·缓存
tiancaijiben2 小时前
阿里云SAE应用引擎弹性扩缩容完全指南:从手动调控到智能自动化
java·网络·数据库
倔强的石头_2 小时前
8000 条 SQL 里,180 条外连接语义跑偏——Oracle 迁 KES 那些藏得最深的坑
数据库
渣渣灰飞2 小时前
MySQL 系统学习 第六阶段:Redis + 缓存 + 高并发设计 第十章:Redis 综合企业项目实战
学习·mysql·缓存
逆境不可逃2 小时前
RAG 不只是向量数据库:讲透 Chunk、混合检索、重排、引用与权限治理
数据库·oracle