跳至主要內容

索引优化实战

Yaien Blog原创大约 11 分钟DBMySQL索引

索引优化实战

MYSQL 索引优化实战笔记

优化实战

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

mysql内部优化规则可能会觉得第一个字段就用范围,结果集应该很大,回表效率不高,还不知直接全表扫

强制走索引

-- 语句
EXPLAIN SELECT * FROM user force index(idx)

添加强制索引可以让语句走索引,但是最总查找的效率不一定会比扫全表高,因为回表效率不高

覆盖索引优化

对于不走索引的语句,可以尝试使用覆盖索引来进行优化

in和or在表数据大会走索引,反之不会

like语句一般情况下都会走索引

like语句看着其实跟大于小于号差不多,之所以会走索引是因为用到了索引下推

索引下推

联合索引是按照最左前缀原则来进行匹配,正常情况下联合索引首个字段就使用like,那只会匹配到这个字段, 后面的因为不能确保是有序的就无法再利用索引。

5.6版本以前,首个字段就停止索引匹配,那就会拿这些对应索引的主键逐个回表找数据,再比对后序字段是否符合。5.6对此进行优化,在索引便利过程中,对索引中包含的字段先做判断,过滤掉不符合条件的记录之后再回表,可以有效的减少回表次数。这就是索引下推。

索引下推回减少回表次数,对于innodb引擎只能适用于二级索引,对主键索引并不适用

trace工具

trace

-- 开启trace
set session optimizer_trace = "enabled=on",end_markers_in_json=on;

-- 一起执行查询
select * from employees where name > 'a' order by position;
SELECT * FROM information_schema.OPTIMIZER_TRACE;

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

结果集

{
  "steps": [
    {
      "join_preparation": {
        /* 第一阶段:准备阶段,格式化SQL */
        "select#": 1,
        "steps": [
          {
            "expanded_query": "/* select#1 */ select `t_menu`.`menu_id` AS `menu_id`,`t_menu`.`menu_name` AS `menu_name`,`t_menu`.`menu_url` AS `menu_url`,`t_menu`.`parent_id` AS `parent_id`,`t_menu`.`level` AS `level`,`t_menu`.`icon` AS `icon`,`t_menu`.`order_by` AS `order_by`,`t_menu`.`hidden` AS `hidden`,`t_menu`.`remark` AS `remark` from `t_menu` where (`t_menu`.`menu_name` > 'a') order by `t_menu`.`parent_id`"
          }
        ]
        /* steps */
      }
      /* join_preparation */
    },
    {
      "join_optimization": {
        /* 第二阶段:SQL优化阶段 */
        "select#": 1,
        "steps": [
          {
            "condition_processing": {
              /* 条件处理 */
              "condition": "WHERE",
              "original_condition": "(`t_menu`.`menu_name` > 'a')",
              "steps": [
                {
                  "transformation": "equality_propagation",
                  "resulting_condition": "(`t_menu`.`menu_name` > 'a')"
                },
                {
                  "transformation": "constant_propagation",
                  "resulting_condition": "(`t_menu`.`menu_name` > 'a')"
                },
                {
                  "transformation": "trivial_condition_removal",
                  "resulting_condition": "(`t_menu`.`menu_name` > 'a')"
                }
              ]
              /* steps */
            }
            /* condition_processing */
          },
          {
            "substitute_generated_columns": {
            }
            /* substitute_generated_columns */
          },
          {
            "table_dependencies": [
              /* 表依赖详情 */
              {
                "table": "`t_menu`",
                "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": "`t_menu`",
                "range_analysis": {
                  "table_scan": {
                    /* 全表扫描情况 */
                    "rows": 15,
                    /* 扫描行数 */
                    "cost": 6.1
                    /* 扫描成本 */
                  }
                  /* table_scan */,
                  "potential_range_indexes": [
                    /* 查询可能使用的索引 */
                    {
                      "index": "PRIMARY",
                      /* 主键索引 */
                      "usable": false,
                      "cause": "not_applicable"
                    },
                    {
                      "index": "idx_test",
                      /* 二级索引 */
                      "usable": true,
                      "key_parts": [
                        "menu_name",
                        "menu_url",
                        "parent_id",
                        "menu_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_test",
                        "ranges": [
                          "a < menu_name"
                          /* 索引使用范围 */
                        ]
                        /* ranges */,
                        "index_dives_for_eq_ranges": true,
                        "rowid_ordered": false,
                        /* 使用该索引获取的记录是否按照主键排序 */
                        "using_mrr": false,
                        "index_only": false,
                        /* 是否使用覆盖索引 */
                        "rows": 15,
                        /* 扫描行数 */
                        "cost": 19.01,
                        /* 使用成本 */
                        "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": "`t_menu`",
                "best_access_path": {
                  /* 最有访问路径 */
                  "considered_access_paths": [
                    /* 最终选择的访问路径 */
                    {
                      "rows_to_scan": 15,
                      "access_type": "scan",
                      /* 访问类型:scan为全表扫描 */
                      "resulting_rows": 15,
                      "cost": 4,
                      "chosen": true,
                      "use_tmp_table": true
                    }
                  ]
                  /* considered_access_paths */
                }
                /* best_access_path */,
                "condition_filtering_pct": 100,
                "rows_for_plan": 15,
                "cost_for_plan": 4,
                "sort_cost": 15,
                "new_cost_for_plan": 19,
                "chosen": true
              }
            ]
            /* considered_execution_plans */
          },
          {
            "attaching_conditions_to_tables": {
              "original_condition": "(`t_menu`.`menu_name` > 'a')",
              "attached_conditions_computation": [
              ]
              /* attached_conditions_computation */,
              "attached_conditions_summary": [
                {
                  "table": "`t_menu`",
                  "attached": "(`t_menu`.`menu_name` > 'a')"
                }
              ]
              /* attached_conditions_summary */
            }
            /* attaching_conditions_to_tables */
          },
          {
            "clause_processing": {
              "clause": "ORDER BY",
              "original_clause": "`t_menu`.`parent_id`",
              "items": [
                {
                  "item": "`t_menu`.`parent_id`"
                }
              ]
              /* items */,
              "resulting_clause_is_simple": true,
              "resulting_clause": "`t_menu`.`parent_id`"
            }
            /* clause_processing */
          },
          {
            "reconsidering_access_paths_for_index_ordering": {
              "clause": "ORDER BY",
              "steps": [
              ]
              /* steps */,
              "index_order_summary": {
                "table": "`t_menu`",
                "index_provides_order": false,
                "order_direction": "undefined",
                "index": "unknown",
                "plan_changed": false
              }
              /* index_order_summary */
            }
            /* reconsidering_access_paths_for_index_ordering */
          },
          {
            "refine_plan": [
              {
                "table": "`t_menu`"
              }
            ]
            /* refine_plan */
          }
        ]
        /* steps */
      }
      /* join_optimization */
    },
    {
      "join_execution": {
        /* 第三阶段:执行阶段 */
        "select#": 1,
        "steps": [
          {
            "filesort_information": [
              {
                "direction": "asc",
                "table": "`t_menu`",
                "field": "parent_id"
              }
            ]
            /* filesort_information */,
            "filesort_priority_queue_optimization": {
              "usable": false,
              "cause": "not applicable (no LIMIT)"
            }
            /* filesort_priority_queue_optimization */,
            "filesort_execution": [
            ]
            /* filesort_execution */,
            "filesort_summary": {
              /* 文件排序信息 */
              "rows": 15,
              /* 预计扫描行数 */
              "examined_rows": 15,
              /* 参与排序行 */
              "number_of_tmp_files": 0,
              /* 使用临时文件个数,如果为0表示全部用sort_puffer内存排序 */
              "sort_buffer_size": 14656,
              /* 排序缓存大小 */
              "sort_mode": "<sort_key, rowid>"
              /* 排序方式,双路 <sort_key, packed_additional_fields>:单路排序*/
            }
            /* filesort_summary */
          }
        ]
        /* steps */
      }
      /* join_execution */
    }
  ]
  /* steps */
}

ORDER BY 与 GROUP BY 优化

order by排序的优化主要是利用索引已经排好序的规律来优化,如果没有使用到会通过文件进行排序消耗性能

  • 使用order by 联合索引,查询条件与排序条件中间字段不能断
  • order by 多个字段时,顺序要与索引字段一致
  • 使用降序排序不会走索引排序
  • in 不会走索引排序,因为结果集不确定是否有序
  • 使用 > 条件的不会走索引排序,可能是数据量太大

总结

  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限定了。

文件排序原理

排序方式

单路排序

一次性取满足条件的数据的所有字段,然后在sort_buffer中进行排序。

trace工具的sort_mode显示<sort_key, additional_fields>或<sort_key, packed_additional_fields>

双路排序

双路排序又称回表排序。首先根据条件查询出相应的字段,然后取排序字段和主键在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,那么使用双路排序模式;

排序过程

假设条件为 where name = 'a'

单路排序

  1. 从索引中找到第一个符合条件的主键ID
  2. 根据ID取出所有字段存入sort_buffer中
  3. 查找下一个符合条件的ID
  4. 一直重复2,3直到没有符合的记录
  5. 排序
  6. 返回结果

双路排序

  1. 从索引中找到第一个符合条件的主键ID
  2. 根据ID取出ID和排序字段存入sort_buffer中
  3. 查找下一个符合条件的ID
  4. 一直重复2,3直到没有符合的记录
  5. 排序
  6. 按照排序好的ID值回到原表取出所欲字段值然后返回

其实对比两个排序模式,单路排序会把所有需要查询的字段都放到 sort buffer 中,而双路排序只会把主键和需要排序的字段放到 sort buffer 中进行排序,然后再通过主键回到原表查询需要的字段。

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

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

所以,MySQL通过 max_length_for_sort_data 这个参数来控制排序,在不同场景使用不同的排序模式,从而提升排序效率。

注意:如果全部使用sort_buffer内存排序一般情况下效率会高于磁盘文件排序,但不能因为这个就随便增大sort_buffer(默认1M),mysql很多参数设置都是做过优化的,不要轻易调整。

设计原则

1. 代码先行,索引后上

不知大家一般是怎么给数据表建立索引的,是建完表马上就建立索引吗? 这其实是不对的,一般应该等到主体业务功能开发完毕,把涉及到该表相关sql都要拿出来分析之后再建立索引。

2. 联合索引尽量覆盖条件

比如可以设计一个或者两三个联合索引(尽量少建单值索引),让每一个联合索引都尽量去包含sql语句里的where、order by、group by的字段,还要确保这些联合索引的字段顺序尽量满足sql查询的最左前缀原则。

3. 不要在小基数字段上建立索引

索引基数是指这个字段在表里总共有多少个不同的值,比如一张表总共100万行记录,其中有个性别字段,其值不是男就是女,那么该字段的基数就是2。
如果对这种小基数字段建立索引的话,还不如全表扫描了,因为你的索引树里就包含男和女两种值,根本没法进行快速的二分查找,那用索引就没有太大的意义了。
一般建立索引,尽量使用那些基数比较大的字段,就是值比较多的字段,那么才能发挥出B+树快速二分查找的优势来。

4. 长字符串我们可以采用前缀索引

尽量对字段类型较小的列设计索引,比如说什么tinyint之类的,因为字段类型较小的话,占用磁盘空间也会比较小,此时你在搜索的时候性能也会比较好一点。
当然,这个所谓的字段类型小一点的列,也不是绝对的,很多时候你就是要针对varchar(255)这种字段建立索引,哪怕多占用一些磁盘空间也是有必要的。

对于这种varchar(255)的大字段可能会比较占用磁盘空间,可以稍微优化下,比如针对这个字段的前20个字符建立索引,就是说,对这个字段里的每个值的前20个字符放在索引树里,类似于
KEY index(name(20),age,position)。

此时你在where条件里搜索的时候,如果是根据name字段来搜索,那么此时就会先到索引树里根据name字段的前20个字符去搜索,定位到之后前20个字符的前缀匹配的部分数据之后,再回到聚簇索引提取出来完整的name字段值进行比对。
但是假如你要是order by name,那么此时你的name因为在索引树里仅仅包含了前20个字符,所以这个排序是没法用上索引的, group by也是同理。所以这里要对前缀索引有一个了解。

5. where与order by冲突时优先where

在where和order by出现索引设计冲突时,到底是针对where去设计索引,还是针对order by设计索引?到底是让where去用上索引,还是让order by用上索引?

一般这种时候往往都是让where条件去使用索引来快速筛选出来一部分指定的数据,接着再进行排序。 因为大多数情况基于索引进行where筛选往往可以最快速度筛选出你要的少部分数据,然后做排序的成本可能会小很多。

6. 基于慢sql查询做优化

可以根据监控后台的一些慢sql,针对这些慢sql查询做特定的索引优化。

慢sql查询查阅:http://note.youdao.com/noteshare?id=c71f1e66b7f91dab989a9d3a7c8ceb8e&sub=0B91DF863FB846AA9A1CDDF431402C7Bopen in new window

上次编辑于:
贡献者: yanggl