问题时这样的,我在建表时加了联合索引结果报key长度超过3072个字节了,如下图。
[42000][1071] Specified key was too long; max key length is 3072 bytes
先说解决方案:1.调整索引字段,包括修改字段长度、更换字段;2.使用前缀索引
在MySQL 5.6以及以前的版本,InnoDB引擎默认索引长度不能超过767 bytes,在MySQL 5.5以后支持4个字节的utf8mb4(mb4全称maximun of 4 bytes per multibyte character),也就是说当CHARSET=utf8mb4时varchar最大只能是varchar(191),因为192x4=768大于767了。
可以通过开启innodb_large_prefix让InnoDB引擎默认索引长度达到3072 bytes,在MySQL 5.6默认innodb_large_prefix=OFF,可以通过设置innodb_large_prefix=ON、innodb_file_format=barracuda、innodb_file_per_table=true 且Innodb表的存储格式(row format)为 DYNAMIC 或 COMPRESSED让索引长度达到3072 bytes。
- #MySQL 5.6以及以前的版本可执行
- set global innodb_large_prefix=1;
- set global innodb_file_format=BARRACUDA;
到MySQL 5.7默认innodb_large_prefix=ON,到MySQL 8.0以后直接remove了innodb_large_prefix、innodb_file_format等变量,row format为DYNAMIC或COMPRESSED让索引长度达到3072 bytes,REDUNDANT和COMPACT只有767 bytes,MySQL 8.0默认innodb_default_row_format=DYNAMIC,所以高版本不用动,前提一定要是InnoDB引擎,MyISAM引擎默认是1000 bytes,NDB则不支持。
为什么是767和3072?网上查资料说,767是历史问题,char最大是255,以前设计者以为一个长度255的索引就够用了,以前UTF8最大支持3个字节,256x3-1=767;InnoDB一个page的默认大小是16k,由于是Btree组织,要求叶子节点上一个page至少要包含两条记录,所以一个记录最多不能超过8k,又由于InnoDB的聚簇索引结构,一个二级索引要包含主键索引,因此每个单个索引不能超过4k (极端情况,primay-key和某个二级索引都达到这个限制),由于需要预留和辅助空间,减掉后不能超过3500,取个“整数”即1024bytes*3=3072bytes。
接着说下解决方案,InnoDB引擎默认索引长度最大只能是3072 bytes,对此我想到这两个方案:1.调整索引字段,包括修改字段长度、更换字段;2.使用前缀索引。其中修改字段长度可能会报“[22001][1406] Data truncation: Data too long for column 'xxx' at row N”错。
前缀索引就是用该列的前面部分字符来创建索引,官方给的例子如下:
CREATE INDEX part_of_name ON customer (name(10));
创建前缀索引前我们可以先比较重复率
- select 1.0*count(distinct 字段名)/count(*) from 表名;
- select 1.0*count(distinct left(字段名, 1))/count(*) from 表名;
- select 1.0*count(distinct left(字段名, 2))/count(*) from 表名;
- select 1.0*count(distinct left(字段名, 3))/count(*) from 表名;
- select 1.0*count(distinct left(字段名, 4))/count(*) from 表名;
- ...
最后取结果最接近或等于1的那个,然后添加前缀索引“alter table 表名 add key(字段名(数字));”,其中left函数会返回具有指定长度的字符串的左边部分(LEFT(str, length))。