如果我们使用不恰当的检索方式,会使的MySQL无法使用索引。
如:
select id from student where id+1=5;
前置条件:id为索引列。在这条查询中MySQL没有使用到索引。因为where后面是表达式:id+1=5;这个不是独立的列,MySQL无法直接解析这个表达式,所以无法使用到索引为id的列。
假设的索引是很长的字符列,那么我们的索引就会有很大的存储成本而且很慢。所以我们就需要使用前缀索引。
所谓前缀索引,我们通常可以以索引开始的部分字符作为索引,这样可以大大节约索引的空间。但是也会也会降低索引的选择性。
索引的选择性=(不重复索引的值)/(总记录数的比值)。
我们需要知道的是,索引的选择性越高,那么查询的效率就越高。
诀窍在于要选择足够长的前缀以保证较高的选择性,同时又不能太长(节省空间)。
那么问题来了,如何计算索引的选择性,如果没有例子,那么上面一堆概念将是废话。
方法一:
计算总的数据量:
select count(*) as cnt,city from sakila.city_demo
ground by city cnt desc limit 10;
如果对SQL语句不了解,解释一下:count(*)计算数据表记录的总数,和group by 联合起来使用,就是根据city分组,每个city出现的次数。order by desc 就是排列顺序是降序且限制10行。
cnt | city |
---|---|
65 | London |
49 | Hiroshima |
48 | Teboksary |
48 | Pak Kret |
48 | Yaound |
47 | Tel Aviv-Jaffa |
47 | Shimoga |
45 | Cabuyao |
45 | Callao |
45 | Bislig |
我们注意到:上面的每个值都出现了45~65次。现在查找最频繁出现的城市前缀,先从3个字母前缀开始。
select count(*) as cnt,left(city,3) as pref from sakila.city_demo
group by pref order by cnt desc limit 10;
解释一下:left(被截取的字符串,截取长度),as后为列的别名。
cnt | pref |
---|---|
483 | San |
195 | Cha |
177 | Tan |
167 | Sou |
163 | al- |
163 | Sal |
146 | Shi |
136 | Hal |
130 | Val |
129 | Bat |
可以看到,当截取字符串长度为3时,符合条件的过多,我们需要增大截取的长度。直到这个前缀的选择性接近完整列的选择性。
经过实验我们发现当截取长度为7也就是前缀为7时比较合适
select count(*) as cnt,left(city,7) as pref from sakila.city_demo
group by pref order by cnt desc limit 10;
cnt | pref |
---|---|
70 | Santiag |
68 | San Fel |
65 | London |
61 | Valle d |
49 | Hiroshi |
48 | Teboksa |
48 | Pak Kre |
48 | Yaound |
47 | Tel Avi |
47 | Shimoga |
不过这样方法还是有点麻烦,下面是第二种方法。
我们计算完整列的选择性,并使前缀的选择性接近于完整列的选择性。
select count(distinct city)/count(*) from sakila.city_demo;
count(distinct city)/count(*) |
---|
0.0312 |
如果比值接近0.0312,那么基本上就够了
下面为实例:
select count(distinct left(city,3))/count(*) as sel3
select count(distinct left(city,4))/count(*) as sel4
select count(distinct left(city,5))/count(*) as sel5
select count(distinct left(city,6))/count(*) as sel6
select count(distinct left(city,7))/count(*) as sel7
from sakila.city_demo;
sel3 | sel4 | sel5 | sel6 | sel7 |
---|---|---|---|---|
0.0239 | 0.0293 | 0.0305 | 0.0309 | 0.0310 |
那么当到7的时候,就已经够了,再增加一个,增加的幅度也不大。所以选7.
前缀索引的缺点:MySQL无法使用前缀索引做order by 和group by,也无法使用前缀索引做扫描。
下面展示如何创建前缀索引:
alter table sakila.city_demo add key (city(7));
首先我们创建单列索引来测试:
create table t(t1 int ,t2 int ,t3 int,key(t1),key(t2),key(t3));
insert into t(t1,t2,t3)values(1,2,3),(2,3,4);
那么当我们使用这种条件查询的时候,是否会使用到索引呢?
答案是不。
使用explain关键字的type字段,发现我们使用的是all
这个时候,我们就需要使用联合索引(复合索引、多列索引)。
使用场景:
在MySQL5.0及以后的版本中,查询条件为or、add或者而二者的联合,我们就可以使用复合索引。
查看案例:
create table t2(t1 int ,t2 int ,t3 int,key(t1,t2);
insert into t2(t1,t2,t3)values(1,2,3),(2,3,4);
联合索引 key(姓,名, 生日)
对于联合索引,需要注意的是:
1最左匹配原则:如果不是从索引的的最左侧开始找,那么这个索引将会失效。
在上面这个联合索引中,无法用于查找名字为Bill的人,也无法查找某个特定日期生日的人,因为这两列都不是最左序列(最左序列是姓),同样也无法
2.如果查询中有某个列的范围查询,那么其右边所有的列都无法使用索引的优化查询。
例如:where 姓 =’Smith’ AND 名 LIKE ‘J%’ AND dob=’1976-12-23’.
这个查询只能查索引的前两列,因为这里LIKE是一个范围条件。
3.不能跳过索引的列。比如查找条件是姓和生日,那么MySQL索引只能用索引的第一列。
现在有个问题:
select * from payment where staff_id=2 and customer_id=584
那么我们需要创建一个(staff_id,customer_id)的索引还是应该颠倒一下顺序。
注意这里的索引是指B+树为数据结构的索引,不是哈希索引。
索引的排列顺序对于查询的效率至关重要。
对于经验而言,我们考虑全局的基数和选择性。则有:
select count(distinct staff_id)/count(*) as staff_id_selectivity,
count(distinct customer_id)/count(*) as customer_id_selectivity,
count(*)
from payment;
staff_id_selectivity:0.0001
customer_id_selectivity:0.0373
count(*):16049
customer_id的选择性更高,所以讲它作为第一列。
alter table payment add key(customer_id,staff_id);
参考:《高性能MySQL:高性能索引的策略》