前两天同事提了一个问题,MySQL 5.7中给某张表字段增加一个单键值索引,提示了如下错误,
一些背景信息,
1. 表设置的字符集,utf8mb4。
2. 表的存储引擎,MyISAM。
3. 表的数据量,1个亿。
4. 执行的SQL,select c1, c2 from test where c1 = :1,c1数据类型是varchar(255),区分度较高,需要创建一个单键值索引。
我们知道,MySQL和Oracle在索引上最大的一个区别,就是索引存在长度的限制。如果是超长键值,可以支持创建前缀的索引,顾名思义,取这个字段的前多少个字符/字节作为索引的键值。
P.S. 《小白学习MySQL - 索引键长度限制的问题》
之所以可以定义一个字段前缀作为键值,存储效率是考虑的一个因素,如果列名的前10个字符通常都是不同的,检索这10个字符创建的索引应该会比检索整个列作为索引的效率更高,使用列前缀作为索引会让索引树更小,不仅节省空间,还可能提高插入操作的速度。
对于非二进制的字符串类型(CHAR、VARCHAR、TEXT),前缀会按照字符个数计算,对二进制的字符串类型(BINARY、VARBINARY、BLOB),前缀会按照字节个数计算,因此,当对非二进制的字符串列明确前缀长度的时候,需要考虑多字节字符集的因素。
MySQL官方手册索引的章节提到了,前缀索引长度限制是和引擎相关的,如果用的是InnoDB,前缀上限是767字节,当启用innodb_large_prefix时,上限可以达到3072字节。如果用的是MyISAM,前缀上限是1000字节,这正是上述创建索引错误提示的内容,
https://dev.mysql.com/doc/refman/5.7/en/create-index.html
utf8mb4是MySQL 5.5.3之后增加的编码,mb4就是most bytes 4的意思,专门用来兼容四字节的unicode,utf8mb4是utf8的超集。之所以需要utf8mb4,是因为之前的utf8编码最大字符长度为3字节,如果遇到4字节的宽字符就会插入异常了。三个字节的UTF-8最大能编码的Unicode字符是 0xffff,也就是Unicode中的基本多文种平面(BMP)。因此,任何不在基本多文本平面的Unicode字符,都无法使用MySQL的utf8字符集存储,这就包括Emoji表情(Emoji是一种特殊的Unicode 编码,常见于手机上),和很多不常用的汉字,以及任何新增的Unicode字符等(这些都是utf8的缺点)。
InnoDB,如果需要建索引,就不能超过767bytes,utf8编码,255*3=765 bytes,是能建索引情况下的最大值,utf8mb4编码,默认字符长度则应该是767除以4向下取整,就是191。如果设置了innodb_large_prefix,最大长度是3072字节,utf8编码,1024*3=3072 bytes,utf8mb4编码,768*4=3072。
MyISAM,如果需要建索引,就不能超过1000bytes,utf8编码,333*3=999 bytes,是能建索引情况下的最大值,utf8mb4编码,默认字符长度则应该是1000除以4,就是250。
我们可以测下MyISAM,utf8mb4编码表test1,250长度的字段c1,251长度的字段c2,
代码语言:javascript复制CREATE TABLE test1 (
c1 varchar(250),
c2 varchar(251)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
c1能创建索引,c2创建出错,utf8mb4编码,varchar最大限制250个字符,
代码语言:javascript复制/* 可执行 */
create index idx_test1_01 on test1(c1);
/*SQL 错误 [1071] [42000]: Specified key was too long; max key length is 1000 bytes*/
create index idx_test1_02 on test1(c2);
utf8编码表test2,333长度的字段c1,334长度的字段c2,
代码语言:javascript复制CREATE TABLE test2 (
c1 varchar(333),
c2 varchar(334)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
c1能创建索引,c2创建出错,utf8编码,varchar最大限制333个字符,
代码语言:javascript复制/* 可执行 */
create index idx_test2_01 on test2(c1);
/*SQL 错误 [1071] [42000]: Specified key was too long; max key length is 1000 bytes*/
create index idx_test2_02 on test2(c2);
其他场景的测试相近,不再展示。
因此,针对这个问题,可以为c1字段创建一个前缀索引,
代码语言:javascript复制create index idx_test_01 on test(c1(250(含)以下的值))
但是1亿数据量,创建非常慢,
通过沟通,了解到这个需求是检索今年的数据,表中存在时间戳的字段,且今年的数据,是1000多万,如果能改应用SQL,或者只是通过手工执行SQL的前提下,可以有几种解决方案,
1. 如果从原表检索,可以创建一个c1和时间戳字段的复合索引,利用索引扫描,定位所需数据。
2. 如果不需要从原表检索,可以使用时间戳作为条件,通过CTAS,创建一张今年数据的表,通过c1单键值索引,即可定位数据。
参考链接,
https://www.cnblogs.com/l10n/p/12606582.html
https://www.cnblogs.com/biehongli/p/12389418.html
https://www.cnblogs.com/dataoblogs/p/14121929.html
https://www.cnblogs.com/bcxx/p/13671280.html
https://blog.csdn.net/weixin_39372979/article/details/80825606
https://blog.csdn.net/weixin_39926402/article/details/114806819
小白学习MySQL,
《小白学习MySQL - “投机取巧”统计表的记录数》
《小白学习MySQL - 一次慢SQL的定位》
《小白学习MySQL - TIMESTAMP类型字段非空和默认值属性的影响》
《小白学习MySQL - 聊聊数据备份的重要性》
《小白学习MySQL - InnoDB支持optimize table?》
《小白学习MySQL - table_open_cache的作用》
《小白学习MySQL - 表空间碎片整理方法》
《小白学习MySQL - 大小写敏感问题解惑》
《小白学习MySQL - only_full_group_by的校验规则》
《小白学习MySQL - max_allowed_packet》
《小白学习MySQL - mysqldump保证数据一致性的参数差异》
《小白学习MySQL - 查询会锁表?》
《小白学习MySQL - 索引键长度限制的问题》
《小白学习MySQL - MySQL会不会受到“高水位”的影响?》
《小白学习MySQL - 数据库软件和初始化安装》
《小白学习MySQL - 闲聊聊》