为什么需要前缀索引
问题
我们在对一张表里的某个字段或者多个字段建立索引的时候,是否遇到过这个问题。
Specified key 'uniq_code' was too long; max key length is 767 bytes.
表结构如下:
create table `t_account`(
`id` BIGINT(20) UNSIGNED NOT NULL auto_increment COMMENT '自增ID',
`date` varchar(50) NOT NULL DEFAULT '' COMMENT '日期',
`nick_name` varchar(50) NOT NULL DEFAULT '' COMMENT '昵称',
`account` varchar(50) NOT NULL DEFAULT '' COMMENT '账号',
`city` varchar(100) NOT NULL DEFAULT '' COMMENT '城市',
...
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_code` (`nick_name`,`account`,`city`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Test';
复制代码
原因
在MySQL5.6里默认 innodb_large_prefix=0 限制单列索引长度不能超过767bytes。
在MySQL5.7里默认 innodb_large_prefix=1 解除了767bytes长度限制,但是单列索引长度最大还是不能超过3072bytes。
至于为什么是767字节,是依赖于具体的存储引擎实现的,找了官方文档,也没说为啥。 https://dev.mysql.com/doc/refman/8.0/en/create-index.html
varchar(n)占用几个字节跟字符集有关系:
字符类型若为gbk,每个字符占用2个字节,
字符类型若为utf8,每个字符最多占用3个字节,
字符类型若为utf8mb4,每个字符最多占用4个字节
复制代码
这里我设置的编码为utf8mb4编码,一个字符是占了4个字节,而我创建的索引50+50+100=200字符,总共就是800字节,所以超出了长度。
所以我们经常会见到把字段设置成varchar(255)长度的,在utf8字符集下这个是最大不超过767bytes的长度了,但是并不是一定要设置成varchar(255),还是要根据业务设置每个字段的长度,太长了也不利于我们建立联合索引。
解决办法
-
可以直接去改字段的长度,或者说,把索引的字段取消掉一些,但是这样改对表本身是不友好的。
-
通过限定字段的前n个字符为索引,可以通过衡量实际的业务中数据中的长度来取具体的值。
UNIQUE KEY `uniq_code` (`nick_name`(20),`account`(20),`city`(20)) 复制代码
表示三个字段取前20字符作为唯一索引,这样的话就是长度就不会超出,这个就是我们说的
前缀索引
-
修改单个索引的最大长度
修改索引限制长度需要在my.ini配置文件中添加以下内容,并重启: #修改单列索引字节长度为767的限制,单列索引的长度变为3072 innodb_large_prefix=1 但是开启该参数后还需要开启表的动态存储或压缩: 系统变量innodb_file_format为Barracuda ROW_FORMAT为DYNAMIC或COMPRESSED 复制代码
如何确定前缀索引的长度
上面我们说到可以通过前缀索引
来解决索引长度超出限制的问题,但是我们改如何确定索引字段取多长的前缀才合适呢?
这里我们可以通过计算选择性来确定前缀索引的选择性,计算方法如下
全列选择性:
SELECT COUNT(DISTINCT column_name) / COUNT(*) FROM table_name;
某一长度前缀的选择性:
SELECT COUNT(DISTINCT LEFT(column_name, prefix_length)) / COUNT(*) FROM table_name;
当前缀的选择性越接近全列选择性的时候,索引效果越好。
前缀索引的优缺点
- 占用空间小且快
- 无法使用前缀索引做 ORDER BY 和 GROUP BY
- 无法使用前缀索引做覆盖扫描
- 有可能增加扫描行数
比如身份证加索引,可以加哈希索引或者倒序存储后加前缀索引。
再谈联合索引的创建
当我们不确定在一张表上建立的联合索引应该以哪个字段作为第一列时,上面的创建规则同样适用。
下面这个例子就是在建立customer_id,staff_id的联合索引时进行判断,最终选择(customer_id,staff_id)这样的组合。
# staff_id_selectivity: 0.0001
# customer_id_selectivity: 0.0373
# COUNT(*): 16049
# 通过结果发现,customer_id 的选择性更高,所以应该选择 customer_id 作为联合索引的第一列
SELECT
COUNT(DISTINCT staff_id)/COUNT(*) as staff_id_selectivity,
COUNT(DISTINCT customer_id)/COUNT(*) as customer_id_selectivity,
COUNT(*)
FROM payment
复制代码
所以说
当索引选择性越接近全列选择性的时候,索引效果越好。
也就是用此字段创建索引时,它在这个表的数据里区分度更加明显。
参考
作者:何小H
链接:https://juejin.im/post/6844904200254521351
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。