4.MySQL索引优化实战

4.MySQL索引优化实战

1、索引下推优化详解

​ 前提:联合索引为index(index_name_age_position)

索引下推(Index Condition Pushdown,ICP)

​ 对于辅助的联合索引,正常情况是最左前缀原则,select * from test where name like ‘ab%’ and age=30 and positon=‘manager’ 这种情况会走name字段索引,因为根据name字段过滤完,等到的索引行里的age和position是无序的,无法很好的利用索引。

​ 在MySQL5.6之前的版本,这个查询只能在联合索引里匹配到名字是‘ab’开头的索引,然后拿这些索引对应的主键逐个回表,到主键索引上找出相应的记录,再比对age和position这两个字段的值是否符合。

​ 在MySQL5.6引入索引下推优化,可以在索引遍历过程中,对索引中包含的所有字段先做判断,过滤掉不合符条件的记录之后再回表,可以有效减少回表次数。使用索引下推优化后,上面那个查询在联合索引里匹配到的名字是‘ab’开头的索引之后,在同时在索引列中过滤掉age和position这两个字段,拿着过滤剩下的索引对应的主键id再回表查整行数据。

注意:索引下推会减少回表次数,对于InnoDB引擎的表索引下推只用于二级索引,InnoDB的主键索引(聚簇索引)树叶子节点保存这全行数据,所以这时候索引下推并不会起到减少查询的效果。

为什么范围查找MySQL没有用索引下推优化?

​ 应该是Mysql认为范围查找过滤的结果集过大,like kk%在绝大多数情况下,过滤后的结果集比较小,所以MySQL选择给like KK% 用索引下推。

​ 1)联合索引第一个字段用范围不会走索引

​ 原因:MySQL内部可能觉得第一个字段就用范围,结果集应该很大,回表效率不高,还不如全表扫描。

​ 2)强制走索引

​ 在表后面添加 force index(索引名字) 查询条件where之前

explain select * from test force index(index_name_age_position) where name>'a' and age=30 and positon='manager'

​ 结论:虽然使用了强制走索引让联合索引第一个字段范围查找也有索引,执行计划结果rows看上去也少点,但是最用查找效率不一定比全表扫描高,因为回表效率不高。

​ 建议:不建议使用强制索引,建议使用覆盖索引(只查询联合索引列)。

​ 3)in和or在数据量比较大的情况下会走索引,在表记录不多的情况下会选择全表扫描

​ 解释:当数据量比较小时,回表会比较耗时间,所以会选择全表扫描,数据量大的时候会走索引,因为查询的过程是比较耗时的。

2、MySQL优化器索引选择探究

​ 同样表 name>‘a’ 和name>‘zzz’的执行计划的结果可能不一样,MySQL最终如何选择索引,可以用trace工具来一查究竟,开启trace工具会影响mysql性能,所以只能临时分析SQL使用,用完之后立即关闭。

trace工具用法:MySQL提供的一个工具可以看到选择索引计算的过程

set session optimizer_trace='enable=on',end_markers_in_json=on; -- 开启trace

-- 下面的两个sql要一起执行
select * from test where name > 'a' order by position;
select * from information_schema.OPTIMIZER_TRACE;

set session optimizer_trace="enabled=off"; -- 关闭trace

trace 字段解析:

{
  "steps": [
    {
      "join_preparation": {    //第一阶段:SQL准备阶段,格式化sql
        "select#": 1,
        "steps": [
          {
            "expanded_query": "/* select#1 */ select `employees`.`id` AS `id`,`employees`.`name` AS `name`,`employees`.`age` AS `age`,`employees`.`position` AS `position`,`employees`.`hire_time` AS `hire_time` from `employees` where (`employees`.`name` > 'a') order by `employees`.`position`"
          }
        ] /* steps */
      } /* join_preparation */
    },
    {
      "join_optimization": {    //第二阶段:SQL优化阶段
        "select#": 1,
        "steps": [
          {
            "condition_processing": {    //条件处理
              "condition": "WHERE",
              "original_condition": "(`employees`.`name` > 'a')",
              "steps": [
                {
                  "transformation": "equality_propagation",
                  "resulting_condition": "(`employees`.`name` > 'a')"
                },
                {
                  "transformation": "constant_propagation",
                  "resulting_condition": "(`employees`.`name` > 'a')"
                },
                {
                  "transformation": "trivial_condition_removal",
                  "resulting_condition": "(`employees`.`name` > 'a')"
                }
              ] /* steps */
            } /* condition_processing */
          },
          {
            "substitute_generated_columns": {
            } /* substitute_generated_columns */
          },
          {
            "table_dependencies": [    //表依赖详情
              {
                "table": "`employees`",
                "row_may_be_null": false,
                "map_bit": 0,
                "depends_on_map_bits": [
                ] /* depends_on_map_bits */
              }
            ] /* table_dependencies */
          },
          {
            "ref_optimizer_key_uses": [
            ] /* ref_optimizer_key_uses */
          },
          {
            "rows_estimation": [    //预估表的访问成本
              {
                "table": "`employees`",
                "range_analysis": {
                  "table_scan": {     //全表扫描情况
                    "rows": 10123,    //扫描行数
                    "cost": 2054.7    //查询成本
                  } /* table_scan */,
                  "potential_range_indexes": [    //查询可能使用的索引
                    {
                      "index": "PRIMARY",    //主键索引
                      "usable": false,
                      "cause": "not_applicable"
                    },
                    {
                      "index": "idx_name_age_position",    //辅助索引
                      "usable": true,
                      "key_parts": [
                        "name",
                        "age",
                        "position",
                        "id"
                      ] /* key_parts */
                    }
                  ] /* potential_range_indexes */,
                  "setup_range_conditions": [
                  ] /* setup_range_conditions */,
                  "group_index_range": {
                    "chosen": false,
                    "cause": "not_group_by_or_distinct"
                  } /* group_index_range */,
                  "analyzing_range_alternatives": {    //分析各个索引使用成本
                    "range_scan_alternatives": [
                      {
                        "index": "idx_name_age_position",
                        "ranges": [
                          "a < name"      //索引使用范围
                        ] /* ranges */,
                        "index_dives_for_eq_ranges": true,
                        "rowid_ordered": false,    //使用该索引获取的记录是否按照主键排序
                        "using_mrr": false,
                        "index_only": false,       //是否使用覆盖索引
                        "rows": 5061,              //索引扫描行数
                        "cost": 6074.2,            //索引使用成本
                        "chosen": false,           //是否选择该索引
                        "cause": "cost"
                      }
                    ] /* range_scan_alternatives */,
                    "analyzing_roworder_intersect": {
                      "usable": false,
                      "cause": "too_few_roworder_scans"
                    } /* analyzing_roworder_intersect */
                  } /* analyzing_range_alternatives */
                } /* range_analysis */
              }
            ] /* rows_estimation */
          },
          {
            "considered_execution_plans": [
              {
                "plan_prefix": [
                ] /* plan_prefix */,
                "table": "`employees`",
                "best_access_path": {    //最优访问路径
                  "considered_access_paths": [   //最终选择的访问路径
                    {
                      "rows_to_scan": 10123,
                      "access_type": "scan",     //访问类型:为scan,全表扫描
                      "resulting_rows": 10123,
                      "cost": 2052.6,
                      "chosen": true,            //确定选择
                      "use_tmp_table": true
                    }
                  ] /* considered_access_paths */
                } /* best_access_path */,
                "condition_filtering_pct": 100,
                "rows_for_plan": 10123,
                "cost_for_plan": 2052.6,
                "sort_cost": 10123,
                "new_cost_for_plan": 12176,
                "chosen": true
              }
            ] /* considered_execution_plans */
          },
          {
            "attaching_conditions_to_tables": {
              "original_condition": "(`employees`.`name` > 'a')",
              "attached_conditions_computation": [
              ] /* attached_conditions_computation */,
              "attached_conditions_summary": [
                {
                  "table": "`employees`",
                  "attached": "(`employees`.`name` > 'a')"
                }
              ] /* attached_conditions_summary */
            } /* attaching_conditions_to_tables */
          },
          {
            "clause_processing": {
              "clause": "ORDER BY",
              "original_clause": "`employees`.`position`",
              "items": [
                {
                  "item": "`employees`.`position`"
                }
              ] /* items */,
              "resulting_clause_is_simple": true,
              "resulting_clause": "`employees`.`position`"
            } /* clause_processing */
          },
          {
            "reconsidering_access_paths_for_index_ordering": {
              "clause": "ORDER BY",
              "steps": [
              ] /* steps */,
              "index_order_summary": {
                "table": "`employees`",
                "index_provides_order": false,
                "order_direction": "undefined",
                "index": "unknown",
                "plan_changed": false
              } /* index_order_summary */
            } /* reconsidering_access_paths_for_index_ordering */
          },
          {
            "refine_plan": [
              {
                "table": "`employees`"
              }
            ] /* refine_plan */
          }
        ] /* steps */
      } /* join_optimization */
    },
    {
      "join_execution": {    //第三阶段:SQL执行阶段
        "select#": 1,
        "steps": [
        ] /* steps */
      } /* join_execution */
    }
  ] /* steps */
}

// 结论:全表扫描的成本低于索引扫描,所以mysql最终选择全表扫描

3、索引优化Order by 与Group by

前提条件 联合索引(index(name,age,position)SQL 前面的都是 select * fromtest

案例1

where  name='lili' and position='dev' order by age

上面SQL的执行计划结果的Extra 字段内容为 Using index condition,说明走索引了

原因:利用做前缀法则:中间字段不能断,因此查询用到name索引,从key_len=74 也能看出,age 索引列在排序过程中(索引存储结构中在name=‘lili’时age是有序的所以上面的那个SQL是走索引的)。

案例2

where  name='lili'  order by position

分析:查询使用name索引,由于用了position进行排序,跳过了age 出现了Using filesort,Extra 字段内容为 Using index condition;Using filesort。也就是一个走索引,order by 没走索引。

案例3

where  name='lili'  order by age,position

分析:查找用到了name 索引,age 和position 用于排序 ;Extra 字段内容为 Using index condition;

案例4

where  name='lili'  order by position,age

分析:和案例3 explain 的执行结果一样,但是出现了Using filesort ,因为索引的创建顺序为name,age,position,但是排序的时候age和position颠倒位置了。

案例5

where  name='lili' and age=12 order by position

分析:与案例4对比,在Extra 中并未出现Using filesort,因为 age 是常量,在排序中被优化,所以索引为颠倒,不会初选Using filesort。

案例6

where  name='lili'  order by age asc,position desc

分析:虽然排序的字段列与索引顺序一样,且order by 默认圣墟,这里的position 变成降序,导致与索引的排序方式不同,从而产生Using filesort。Mysql8以上版本有降序索引可以支持该种查询方式。

案例7

where  name in ('lili','zhang')  order by age,position

分析:对于排序来说,多个相等条件也是范围查询,order by 不能作用在索引的排序上,所以不走索引

案例8

select * fom test where  name >'lili'  order by name

分析:按照索引树的结构,应该走索引,但是没有走索引(MySQL可能觉得数据量太大了 ,查询字段太多,需要回表)。

可以用覆盖索引优化

select name,age,position from test where  name >'lili'  order by name

优化总结

1)MySQL支持两种方式的排序filesort和index,Using index 是指MySQL扫描索引本身完成排序。index效率高,filesort效率低。

2)order by 满足两种情况会使用Using index。

​ 1.order by 语句使用索引最左前列

​ 2.使用where子句与order by 子句 条件列组合满足索引最左前列

3)尽量在索引上完成排序,**遵循索引建立(索引创建的顺序)**时的最左前缀法则。

4)如果order by的条件不在索引列上,就会产生Using filesort

5)能用覆盖索引尽量用覆盖索引

6)Group by 和 order by 类似,其实只是先排序后分组,遵照索引创建顺序的最左前缀法则。对于group by的优化如果不需要排序可以加上order by null 禁止排序。注意 where 高于having ,能写在where 中的限定条件不要写到having中。

4、Using filesort文件排序详解

​ filesort 文件排序方式

​ 1)单路排序模式: 是一次性取出满足条件行的所有字段,然后在sort buffer中进行排序;用trace工具可以看 到sort_mode信息里显示< sort_key, additional_fields >或者< sort_key, packed_additional_fields >

​ 2)双路排序模式(回表排序模式): 是首先根据相应的条件取出相应的排序字段和可以直接定位行数据的行 ID,然后在 sort buffer 中进行排序,排序完后需要再次取回其它需要的字段;用trace工具可以看到sort_mode信息里显示< sort_key, rowid >

MySQL 通过比较系统变量 max_length_for_sort_data(默认1024字节) 的大小和需要查询的字段总大小来判断使用哪种排序模式。(也就是查询的所有字段大小之和)

  • 如果 字段的总长度小于max_length_for_sort_data ,那么使用 单路排序模式;
  • 如果 字段的总长度大于max_length_for_sort_data ,那么使用 双路排序模·式。
select * from test where  name ='lili'  order by position

从上面的SQL看看单双录排序的详细过程:

单路排序

  1. 从索引name找到第一满足name ='lili’条件的主键id
  2. 根据主键id获取整行,取出所有字段的值,存入sort_buffer中
  3. 从索引name找到下一个满足name ='lili’条件的id
  4. 重复2、3操作知道不满足条件name =‘lili’
  5. 对sort_buffer中的数据按照字段position 排序
  6. 返回结果给客户端

双路排序

  1. 从索引name找到第一满足name ='lili’条件的主键id
  2. 根据主键id获取整行,把排序字段position 和主键Id这两个字段放到sort_buffer中
  3. 从索引name找到下一个满足name ='lili’条件的id
  4. 重复2、3操作知道不满足条件name =‘lili’
  5. 对sort_buffer中的字段position和主键id 按照字段position 排序
  6. 遍历排序好的id和position ,按照id值回到原表中取出所有的字段返回客户端。

总结:两个排序模式,单路排序会把所有需要查询的字段放到sort_buffer中,而双路排序只会把主键和需要排序的字段放到sort_buffer中进行排序,然后在通过主键回到原表查询需要的字段。

若MySQL排序内存sort_buffer配置的比较小并且没有条件继续增加了,可以适当把max_length_for_sort_data 配置小点,让优化器优先选择双路排序算法,可以在sort_buffer中一次排序更多列,只是需要根据主键回到原表获取数据。

若MySQL排序内存sort_buffer配置的比较大,,可以适当把max_length_for_sort_data 配置大点,让优化器优选选择全字段排序(单路),把所需的字段放到sort_buffer 中,这样排序后就会直接从内存里返回查询结果。

5、索引设计原则与实战

索引设计原则

  1. 代码先行,索引后上:一般应等到主体业务功能开发完毕,把涉及到该表相关的SQL拿出来分析之后再建立索引。
  2. 联合索引尽量覆盖条件:让每一个联合索引尽量去包含SQL语句中的where、order by、group by 的字段,确保联合索引字段顺序尽量满足SQL查询的最左前缀原则。
  3. 不要在小基数字段上建立索引
    索引基础:指这个字段在表中有多少个不同值,例如一张100w行记录,其中性别字段,不是男就是女,该字段的基数为2。小基数建立索引,还不如全表扫描,因为索引树中就两种值,无法快速查找。
    一般建立索引,尽量用那些基数比较大的字段,就是值比较多的字段,那么才能发挥B+树快速二分查找的优势来。
  4. 长字符串可以采用前缀索引:对于varchar(255)的大字段建立索引可能会比较占用磁盘,可以稍微优化下,例如针对这个字段的前20个字符建立索引,把这个字段里的每个值的前20个字符放在索引树里。类似index(name(20),age,position)。
    此时若在where天剑里搜索的时候,如果是根据name字段搜索,那么此时就会先到索引树根据anme的前20字符去搜索,定位到前20字符配置的数据之后,在回到主键索引树中提取完整的name字段进行比对。
    order by和group by无法用到 索引上的排序。
  5. where 与 order 冲突时优先where:一般这种时候往往都是让where条件去使用索引来快速筛选出一部分指定数据,接着在进行排序。
    因为大多数情况基于索引进行where筛选往往可以最快速度筛选出你要的少部分数据,然后做排序的成本可能会小很多。
  6. 基于慢查询做SQL优化:可以根据监控后台一些慢SQL,针对这些慢SQL做特定的索引优化。
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

苹水相峰

你的打赏是对我最大的肯定

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值