InnoDB索引列超长(Index column size too large)

该问题出现于MySQL5.6(InnoDB),错误信息:Index column size too large. The maximum column size is 767 bytes 

分析如下:
一.为什么会出现这个问题?

因为5.6版本(InnoDB)的相关默认配置如下:

Variable_name: innodb_large_prefix       Value:OFF
Variable_name: innodb_file_format        Value: Antelope

 

Antelope是老的文件格式,其包含2种行格式:Compact和Redudant。5.6之后版本的默认文件格式是Barracuda:引入2种新的行格式:Compressed和 Dynamic。
两种文件格式的整体关系如下图:
InnoDB索引列超长(Index column size too large)

 

InnoDB对于string类型(varchar等)支持的行长理论上是65535字节,实际值略小于这个数。而16k的页,16384字节,是存不下整行数据的。所以在Antelope文件格式下, Compact和Redudant两种行格式,采用的是行溢出的方式存放:

InnoDB索引列超长(Index column size too large)

 

Compact与Redudant在行格式的处理细节上是不同的,但这种行溢出的基本格式是类似的。
而同样16k的索引页也采用了这种prefix的方式。或者换一种说法,索引只是把行存储的prefix 767bytes数据提取为index key,而不再去进行额外的I/O读取溢出页。

 

在之后的Barracuda文件格式下, Compressed和 Dynamic两种行格式,不再存储767的prefix,而只存了20字节的指针:

InnoDB索引列超长(Index column size too large)

 

同样,Compressed与Dynamic在控制信息上是不同的,并且Compressed会使用zlib算法进行数据压缩,但溢出控制结构是类似的。使用这两种格式需要file-per-table的支持,
这格式能突破767bytes的限制,支持更大的large prefix。


具体配置项是:

innodb_file_format= Barracuda;
innodb_file_per_table=NO;
row_format=dynamic; (或Compressed)
innodb_large_prefix = ON;

设置large prefix后索引长度最大能支持3072bytes。

 

二. Why 255,3072bytes ?
有文章说“如果需要在长度大于255字符的字段上创建索引,需要修改…” 这句话是不严谨的,其实真正的限制是767bytes
而如varchar(N),这个N的大小是受字符编码集影响的,如latin1与utf8时N是不一样的,在utf8mb4时 767/4=191,
只支持191个字符的长度。

 

InnoDB的索引是B+树索引,而每页中至少要有2条数据,否则就破坏树结构,变成单链结构了。所以16K页大小时,每条记录分到的最大空间是8K,满载的B+树会有结点分裂问题,甚至糟糕的级联分裂,所以实际中的页空间都是控制在n/2,因此索引记录要控制在4k,即4000bytes以下,扣除掉必要的控制信息位后,这个值是3072bytes。

 

三.这样设置后有什么影响?
1.首先,空间效率变低下,原先只存767,现在最大3072全塞到index key,这点是显而易见的。
2.其次,索引页中能存放的记录数变少,如原先每页可能存n条keys,现在可能只放2条keys。
这说明索引树的节点有更大几率需要拆分,如果表上的操作是修改频繁的,那索引结构会更频繁的变动。


最后,如果要更精细的优化,应该控制prefix的长度,如某字段的前100个字节就有足够的区分度话,那应该是prefix(100)就可以,但这往往与具体业务相关。如果仅就DBA层面来说完成设置large prefix已经完全可以,但如果你的角色是需要考虑全链路的优化,可能可以更进一步。

InnoDB索引列超长(Index column size too large)

上一篇:关于java连接MySQL配置文件中出现的巨坑


下一篇:mysql基础知识----安装