索引
建立好一个联合索引之后,SQL语句要怎么写,才能让他的查询使用到建立好的索引呢?
索引最基本的规则就是等值匹配了,就是where条件里的查询条件字段和联合索引的字段完全一样,都是基于等于来匹配的。
最左侧列匹配,假如设计的索引是INDEX(class_name, student_name, course_name)
,查询的SQL语句where里不一定要根据三个字段来查询,只要最左侧的部分字段来查,就可以了
比如:select * from student_score where class_name='xx' and student_name='xx'
,只有class_name和student_name可以在索引里搜索,就可以查询某个学生所有科目的成绩。
但如果SQL是select * from where course_name='xx'
,那就不能走索引了,因为B+树里必须先按class_name查找,再按student_name查找,不能跳过前面两个字段。
最左侧匹配规则,假如要用like语法来查,比如select * from student_socre where class_name like '1%'
,查找所有1开头的班级的分数,那么也是可以用索引的。
因为联合索引的B+树里,是按照class_name排序的,所以要是给出class_name的最左前缀就是1,然后后面的给一个模糊匹配符号,那也是可以基于索引来查找的。
但如果where条件是class_name like '%班'
,左侧是模糊查询,就没法用索引了。
范围查找,假如查询的SQL是:select * from student_score where class_name>'1班’ and class_name<'5班'
,查找几个班的分数。也是可以用到索引的。
因为索引的最下层的数据页都是按顺序组成双向链表的,所以完全可以先找到’1班’对应的数据页,再找到’5班’对应的数据页,两个数据页中间的那些数据页,就全都是在范围内的数据了!
ORDER BY如何使用上索引?
假设有一个查询:select * from table where xxx=xxx order by xxx
,似乎应该是基于where语句通过索引快速筛选出来一波数据,接着放到内存里,或者放在一个临时磁盘文件里,然后通过排序算法按照某个字段来一个排序,最后把排序好的数据返回。
如果是这样的话,肯定就会比较慢,所以最好别这么搞。
尤其是类似于select * from table order by xx1,xx2,xx3 limit 10
,这样的SQL语句,查出一批数据后,按照多个字段进行排序,最后返回前10条数据,类似的语句其实常常见于分页SQL语句里。
这种情况,可以建立一个联合索引INDEX(xx1, xx2, xx3)
,这时候数据在索引里默认是按照xx1,xx2,xx3排序的,就不需要查出一批数据再在内存里或磁盘里做复杂的排序工作了。
直接拿到10条数据的主键去聚簇索引里回表查询剩下的字段。
所以,SQL里最好是按照联合索引的字段顺序去进行order by排序,这样就可以直接利用联合索引的数据有序性,到索引树里直接按照字段值的顺序去获取数据了。
GROUP BY如何使用上索引?
那假设有这样一个查询:select count(*) from table group by xx
语句,看起来必须先查出一批数据,然后数据放到一个临时磁盘文件里还有加上部分内存,按照指定字段的值分成一组一组的,接着对每一组都执行一个聚合函数,这个性能也是极差的,因为毕竟涉及大量的磁盘交互。
在索引树里数据都是按照指定的一些字段都排序好的,其实字段值相同的数据都是在一起的,假设要是走索引去执行分组后再聚合,那性能一定比临时磁盘文件去执行好多了。
所以,group by后的字段,最好也是按照联合索引里最左侧的字段开始,按照顺序排列的,这样就可以用上索引来提取一组一组的数据了。
order by和group by用上索引的原理差不多,都是依赖索引数据的顺序性。
索引设计的考虑因素
一般建立索引,尽量使用那些区分度比较大的字段,那么才能发挥出B+树快速二分查找的优势来。
什么是区分度比较大?
就是表里一个列的值不同的越多,区分度越大,值不同的越少,区分度越小。比如订单号,订单表每条数据的订单号都不一样,所以它的区分度大,而性别字段一般就男,女,未知,三个值,区分度就比较小。
要是针对区分度小的字段建立索引,会查出一大批数据,没有太大意义。
尽量对字段类型比较小的列设计索引,比如说tinyint之类的,因为他的字段类型比较小,说明这个字段自己本身的值占用磁盘空间小,此时在搜索的时候性能也会比较好一点。
如果字段是name VARCHAR(255)
,可以针对字段的前20个字符建立索引,就是说,对这个字段的每个值的前20个字符放在索引树里。
但此时,order by name就没法用上索引了,group by name也是一样的道理。因为索引树里仅仅包含了前20个字符。
函数与索引
假设设计好了索引,但SQL里这么写,where sum(xxx)=xx
,给索引里的字段xxx使用了函数,还能用上索引吗?
不能了,所以尽量不要让查询语句里的字段搞什么函数。
现在设计索引的时候需要注意的点都已经讲完了,其实就是好好设计索引,让查询语句都能用上索引,同时注意一下字段基数、前缀索引和索引列套函数的问题,尽量让查询都能用索引,别因为一些原因用不上索引了。
由于索引本身要占用存储空间,增删改的时候也要维护索引本身,索引一般不要设计太多索引,建议两三个联合索引就应该覆盖掉这个表的全部查询了。
覆盖索引
一般二级索引的叶子节点仅仅包含了索引里的几个字段值和主键值,如果要查询表里的很多字段,就需要回表查询,就是先在二级索引里拿到主键id,再回表根据主键id查出所有字段,这个性能其实也不高。
如果能只在二级索引里查到所有字段,那就完美了,这就要引入覆盖索引的概念了。
覆盖索引不是一种索引,是一种基于索引查询的方式。
假设有查询select xx1,xx2,xx3 from table order by xx1,xx2,xx3
这样的语句,这种情况下,仅仅需要联合索引里的几个字段的值,那么其实就只要扫描联合索引的索引树就可以了,不需要回表去聚簇索引里找其他字段了。
要查的xx1,xx2,xx3在二级索引里都能提取出来,不需要到聚簇索引里查,这就是覆盖索引。
所以SQL里最好指定仅仅需要的几个字段,不要动不动搞一个select *
把所有字段都拿出来,甚至最好是直接走覆盖索引的方式,不要去回表到聚簇索引。
即使是必须要回表到聚簇索引,那也尽可能用limit
、where
之类的语句限定一下回表到聚簇索引的次数,从二级索引里筛选最少的数据,然后再回表到聚簇索引里去,这样性能也会好一些。