工作中的点点滴滴-MySql的索引失效问题

  最近的工作内容比较枯燥,就是根据运营小姐姐的需求,给她出一些不同维度的数据报表,那么提到报表,多多少少是离不开数据库写sql的,然后就是各种Left Join 呀,Inner Join 呀,子查询呀。然后在这个过程中,避免不了条件过滤的情况,当数据表的数据量大了起来,那执行一个sql可真的是要了我的老命了。所以这个时候你就要想着怎么去优化这个sql语句了,所以创建添加索引就标的必不可少了。

  首先在创建索引的前提是,你应该在哪些字段上去创建索引呢?那说到这里,肯定是需要针对一些需要条件查询的字段去创建索引呀,其实这句话只说对了一半吧。我们在建立索引的时候,首先要pass掉那种频繁修改的字段,因为你在给字段创建索引的时候,其实本质上是在B+树上创建了一个子叶节点,在你更新索引字段的时候,B+树会重建索引,这个过程是非常慢的,并且会伴随着锁表的情况。其次就是区分度不大的那种字段,比如性别这类,因为这种类型的字段建索引意义不大,性能基本上和全表扫描的性能差不多,另外就是mysql的优化器有个默认配置,就是返回数据的比例在30%以外的情况,是不会选择使用索引的,这个30%是一个大概的范围,并不是固定死的。还有一种比较特数据的,就是会有null的这种字段也不合适做索引,虽然说索引是支持null的,但是从规范上讲,null是一种没有意义的对象,可以设置一个默认值来解决这种问题。索引建立好了,并不是放在where后面跟上查询条件就可以使用了,毕竟自己也踩过坑了,要不然也不会有这个笔记的诞生了。

  业务表结构和索引展示如下:

 

           

  这张数据操作表的原始数据了大概是730w左右,通过数据id来查询的sql的执行计划查看如下:

 

来,我们分析一下这个执行计划先,要看懂执行计划,首先我们要搞懂这每一列的含义。

  select_type:查询类型主要有三种,第一种SIMPLE简单查询,这种是最优的查询语句。第二种是PRIMARY子查询,一般是最外层会被标记为PRIMARY。第三种就是联合查询UNION了,一般像Left Join,Inner Join这类查询的。table:查询涉及到的表或者别名。partitions:分区信息,一般为null。type:这一列是我们需要重点照看的字段了,这列是访问类型,也是优化的重点对象,一般这列的结果值从好到不好依次是:system(系统表,少量数据,往往不需要进行磁盘IO)> const (常量连接)> eq_ref (主键索引(primary key)或者非空唯一索引(unique not null)等值扫描)> ref (非主键非唯一索引等值扫描)> range(范围扫描) > index(索引树扫描) > ALL(全表扫描(full table scan)),所以我们常说的sql优化最先就是按照这个指标来优化的。possible_keys:查询可能会用到的索引,这个字段和后面的key有点儿类似。key:执行计划实际上使用到的索引,没有的话就是null。rows:查询结果大致估算出找到所需的记录所需要读取的行数,这个值是越小越好。filtered:和rows类型,所需要查询到的结果行占用读取行的百分比,这个值越大越好。Extra:这列比较特殊,再用到特殊的查询会体现出来,经常用到的有这些值,可以在优化sql的时候考虑进去,using temporary,使用临时表保存中间结果,比如在orderBy或者groupBy的时候。using index,查询语句中使用了覆盖索引-Covering Index时候,效率会比较好,避免访问了数据行。using where使用了where条件的时候。using index condition:虽然命中了索引,但不是所有列都在索引树上需要访问实际的记录行。using join buffer:采用关联查询或者子查询的时候需要进行嵌套循环计算。

  现在我们明白了执行计划了,那么看索引有没有生效,这样就会方便很多了。一:最典型的也是我们经常使用到的不等于<>,type列是All,等于是走了全表扫描。

 

二:列和列相等去做条件查询,比如operate_date_id = operate_user_id,可以看到这种查询也是全表扫描的。

 

通过where in 条件查询,这种查询条件也比较特殊,比如operate_date_id是int类型,然后operate_date_id in ('111')这种是不会走索引的,但是operate_date_id in (111) 却是会走索引的,另外如果where in()的条件多了,也是不会走索引的,所以在使用where in的时候,一定要注意。并且像 not,not in,not exists也都是属于这类似的情况。

四:like通配符来模糊查询,这种查询也是比较坑,稍不留神就掉坑里了,看下面两个执行计划。当使用模糊搜索时,尽量采用后置的通配符,后匹配可以走INDEX RANGE SCAN。

另外其他的经常我们会在where条件用upper(operate_user_name)='小旭旭宝宝A',这种函数表达式,这样也是会导致索引失效。还有一种比较特殊的 大于小于号和between,通过执行计划你可以发现其实这两种条件的执行计划是一抹一样的。但是呢如果你的mysql版本是>=5.6,在information_schema中的optimizer_trace表,可以跟踪到执行计划的具体步骤,通知cost_for_plan执行计划代价指标来判断,返现大于小于这种情况的代价值往往会比between小(个人感觉不会是绝对的,虽然通过上面的业务表测试了几个类型的字段结果都是一致的,但是官方文档并没有明文支出这两种方法的差异),类似于这种大于小于比较符号,通常优化器,会更具实际查询数据量的比例来判断,如果全表扫描比索引快,则不会走索引。

  那么说了索引失效的情况,那具体说为什么索引会失效呢?首先先确认一点就是mysql的索引是以B+树来存储的,那为什么选择B+树,而不是哈希索引或者B树,二叉树呢?对于哈希来说他是无序的,不能进行范围搜索。B树相对于二叉树,虽然可以解决一部分的查找效率,但是都会有回旋查找的问题,而B+树因为他的非叶子节点可以存储key,叶子节点既能存储key也能存储value,并且叶子节点还是有序的,节点之间用指针连接也避免了回旋问题了。如果理解了索引的存储方式后,其实索引查询就和在B+树查询原理是一样的,如果要命中索引,那么首先要确定查询字段的值,也就是查询字段是在哪些节点上,然后在节点上在通过顺序查询或者二分查询具体的节点就可以命中索引,反之则会无法命中索引导致索引失效。

  

 

 

          

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章