MySQL前缀索引

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MySQL前缀索引相关的知识,希望对你有一定的参考价值。

参考技术A

前缀索引顾名思义,定义字符串的一部分当做索引,而不是把整个字符串当做索引。默认地,如果你创建索引的语句不指定前缀长度,那么索引就会包含整个字符串。

假设一张表有 id,name,email 2个字段
1.创建email列的普通索引应该是: alter table T add index idx_email1( email )
2.前缀索引的创建规则为: alter table table T add index idx_email2( email(6) )
当然第一索引包含是的整个字符串,第二个是该字段前6个字节(注意是字节)
对于这2中索引,B+树怎么存储呢?
INSERT INTO T (email) VALUES (\'瞎子\',\'zhangsh1234@163.com\'), (\'剑圣\',\'lisi1998883@163.com\'), (\'露娜\',\'zhangssxyz@163.com\'), (\'李白\',\'zhangsy1998@163.com\'), (\'韩信\',\'zhaq5481993@163.com\'), (\'百里玄策\',\'hhaq5481993@163.com\');
【谁还不是个野王啊】
普通索引存储为:

是的你没看错,前缀索引那颗树上的存储的是email的前6位字节,也就是你创建前缀索引时指定的前缀字节长度。2种树相比,前缀索引存储了更少的数据,那么他所耗费的空间也就相比较少,这正是他的一个优点。同样的也就相对的增加了扫描行数。
什么增加了扫描行数???? 这是为什么呢?

那么小朋友咱们一起来看下吧。
假设SQL如此这般: select id,name,email from T where email = \'zhangsh1234@163.com\'
那么这2个SQL,应该怎么操作呢。
idx_email1:

2.到主键上查到主键为ID1的,判断email值是否正确【为什么判断呢,其实我理解是为了二次判断保证数据一致性吧,比较官方的解释尚未找到】,正确放入结果集

3.取 idx_email1 索引树上刚刚查到的位置的下一条记录,如此往复。
循环过程中,需要回主键取1次数据,所以系统可以认为只扫描了一行【1次是数第一棵树数出来的】

idx_email2:
1.从 索引数上找到满足索引值为 \'zhangs\'的该记录,取得 ID1的值

2.到主键上查到主键值是 ID1 的行,判断出 email 的值是’ zhangsh1234@xxx.com ’,这行记录放入结果集【不是要的值,丢弃,进行下一步】

3.取 idx_email2 上刚刚查到的位置的下一条记录,重复以上步骤

在这个过程中,要回主键索引取 3 次数据,也就是扫描了 3 行。通过这个对比,你很容易就可以发现,使用前缀索引后,可能会导致查询语句读数据的次数变多。

但是,对于这个查询语句来说,如果你定义的 idx_email2 不是 email(6) 而是 email(8),也就是说取 email 字段的前 8 个字节来构建索引的话,即满足前缀’zhangsh’的记录只有一个,也能够直接查到 ID1,只扫描一行就结束了。也就是说使用前缀索引,定义好长度,就可以做到既节省空间,又不用额外增加太多的查询成本。

那么问题来了,到底定义多长才算是合理呢?
一般的定义原则是 count(distinct(columnName))/count(*) ,当前缀索引【count(distinct(columnName(length))),length是你想要创建列的前缀字节长度】越接近此值越好,当有多个前缀字节都一样且都等于这个值时怎么选择呢,当然是 字节越少越好了哈,字节越少越省空间。索引选取的越长,占用的磁盘空间就越大,相同的数据页能放下的索引值就越少,搜索的效率也就会越低。

count(distinct(columnName(length))) 翻译到SQL 为: count(dictinct(left(colunmName, length)))

前面我们说了使用前缀索引可能会增加扫描行数,这会影响到性能。其实,前缀索引的影响不止如此,我们再看一下另外一个场景。
来呀,上SQL: select id,email from T where email=\'zhangsh1234@163.com\'
如果按照email全字段索引,那么此SQL 是不需要回表的【为什么不需要回表?兄嘚,这个相当于覆盖索引了哈】
那么如果按照前缀索引是否需要回表呢?答案是的。
因为当判断前6个字节相等后,需要拿到id 回表拿到email的全部内容进行比较,如果不相同,丢弃这行,否则加入结果集。

那么有人会问了,我把长度放大点,包含所有字节不就好了吗?
那么此时会有如下问题。
1.当你此时的长度是囊括了全字段,但是系统是不知道的,他还是需要回表再次判断的,去确定前缀索引的定义是否截断了完整信息。
2.此时长度是够了,那么能肯定因为业务日后不会增加长度吗?
3.尽可能的加长长度,还不如直接建立全字段索引呢

综上,使用前缀索引就用不上覆盖索引对查询性能的优化了,这也是你在选择是否使用前缀索引时需要考虑的一个因素。

前面说到的是,可以根据字段前面几个字节进行查询的,那么对于身份证这种,一共 18 位,其中前 6 位是地址码,所以同一个县的人的身份证号前 6 位一般会是相同的。
或许你会说,多弄几个字节不就好吗?那么请问下自己为什么使用前缀索引呢,不就是为了节省空间吗?
那么这么做合适吗? 不合适对吗? 乖~,快去反省下吧
那么采用前缀索引显示是不行的,那么如果用前缀索引怎么办呢,聪明的你应该已经猜到了,采用倒叙存储,然后建立前缀索引。
放到SQL 中就应该是这样的: select field_list from t where id_card = reverse(\'id_card_string\');
当然了,这种逻辑建议放到业务逻辑中实现,而不是放到SQL 中。

按照上述第4节的内容,有人或许会有另一个想法,还倒叙建立前缀索引复杂不,hash索引或者hash字段不香吗?

有人会问了,为什么要在创建一个值来存储hash值呢,如果不存储你知道原值是什么吗? 同时hash算法是有一定重复可能的(hash值碰撞)
【可以了解下partition算法哦:[ https://selfboot.cn/2016/09/01/lost_partition 】。如果重复了,不存储原值,你是无法判断出正确数据的。

注:【hash字段不代表hash索引,hash索引原理正在快马加鞭】,简单说下hash索引,hash索引不需要创建一个值来存储hash值,而是有hasn表来存储【hash值碰撞时,由一个链表来搞定了】,存储的内容为 hash值和每行的行指针

说回来啊,跑题了
查询时: select field_list from t where id_card_crc=crc32(\'id_card_string\') and id_card=\'id_card_string\'
不过有个问题相信你也想到了,不管是hash存储值还是hash索引都是不支持范围查询的。

来总结下这2个优缺点吧
1.从占用空间来看呢,倒叙索引不需要额外开辟存储空间,而hash字段需要额外的一个字段,所以从这点上看倒叙索引更胜一筹,NO!并不准确,如果前缀长度过长,那么这2个情况额外的空间也就相差无几了

3.从查询效率上看,使用 hash 字段方式的查询性能相对更稳定一些。因为 crc32 算出来的值虽然有冲突的概率,但是概率非常小,可以认为每次查询的平均扫描行数接近 1。而倒序存储方式毕竟还是用的前缀索引的方式,也就是说还是会增加扫描行数

1.全字段完整索引比较占空间,但是而走覆盖索引

2.前缀索引,节省空间,但会增加扫描 次数 并且不能使用覆盖索引【每次都需回表校验】

3.倒序存储,再创建前缀索引,用于绕过字符串本身前缀的区分度不够的问题。【倒叙方法建立放到业务逻辑中】

4.hash字段索引,相比前缀索引性能较为稳定,但是有额外的存储空间和计算消耗,同时也 支持范围查询

mysql前缀索引的索引选择性

一. 基础概念
在mysql中建立前缀索引的意义在于相对于整列建立索引,前缀索引仅仅是选择该列的部分字符作为索引,减少索引的字符可以节约索引空间,从而提高索引效率,但这样也会降低索引的选择性
关于索引的选择性,它是指不重复的索引值(也称为基数cardinality)和数据表的记录总数的比值,范围从1/(数据表记录总数)到1之间。索引的选择性越高则查询效率越高,因为选择性高的索引可以让MySQL在查找时过滤掉更多的行。选择性为1的索引叫唯一索引,这是最好的索引选择性,性能也是最好的
建立合理前缀索引的诀窍在于要选择足够长的前缀以保证较高的选择性,同时又不能太长(以便节约空间)。前缀应该足够长,以使得前缀索引的选择性接近于索引的整个列。换句话说,前缀的基数应该接近于完整列的基数

二. 实地测试
① 创建测试表music,并插入一些数据

create table music(
name varchar(30)
);

insert into music values(‘BITE‘),(‘There for you‘),(‘Scarborough Fair‘),(‘Shape of You‘),(‘Marvin Gaye‘),(‘Pretty Girl‘),(‘Pretty Boy‘),(‘Walk Away‘),(‘YOUTH‘),(‘Paris‘);

insert into music values (‘Scarborough Fair‘),(‘Shape of You‘),(‘Marvin Gaye‘),(‘Pretty Girl‘),(‘Pretty Boy‘),(‘Walk Away‘),(‘YOUTH‘),(‘Paris‘);
1
2
3
4
5
6
7
8
② 查看刚刚创建的表的完整列索引选择性

select count(distinct name) / count(*) from music;
1


③ 找出合适的前缀长度

select count(distinct left(name,1))/count(*) as sel1, count(distinct left(name,2))/count(*) as sel2, count(distinct left(name,3))/count(*) as sel3, count(distinct left(name,4))/count(*) as sel4 from music;
1


因为该测试表中插入的数据量少,所以可以看到当选择前两个字符作为前缀索引后,索引的选择性达到了0.5,接近完整列的索引选择性0.5556
④ 建立前缀索引并查看

alter table music add index music_index(name(2));
1


⑤ 前缀索引被使用

select * from music where name like ‘s%‘;
1


三. 注意事项
① 前缀索引是一种能使索引更小,更快的有效办法,但另一方面也有其缺点:mysql无法使用其前缀索引做ORDER BY和GROUP BY,也无法使用前缀索引做覆盖扫描
② 要明确使用前缀索引的目的与优势:
.大大节约索引空间,从而提高索引效率
.对于 BOLB 、 TEXT 或者很长的 VARCHAR 类型的列,必须使用前缀索引,因为 MySQL 不允许索引这些列的完整长度
③ 前缀索引会降低索引的选择性
④ 真正的难点在于:要选择足够长的前缀以保证较高的选择性,同时又不能太长, 前缀的长度应该使前缀索引的选择性接近索引整个列,即前缀的基数应该接近于完整列的基数

以上是关于MySQL前缀索引的主要内容,如果未能解决你的问题,请参考以下文章

MySQL 进阶 索引 -- 索引使用原则(验证索引效率最左前缀法则范围查询索引失效情况SQL提示覆盖索引前缀索引单列索引与联合索引)索引设计原则

MySQL 进阶 索引 -- 索引使用原则(验证索引效率最左前缀法则范围查询索引失效情况SQL提示覆盖索引前缀索引单列索引与联合索引)索引设计原则

MySQL索引 索引分类 最左前缀原则 覆盖索引 索引下推 联合索引顺序

mysql索引之前缀索引

MySQL 前缀索引

mysql前缀索引的应用