mysql优化三:索引优化实战(上)

文章探讨了MySQL中的索引优化问题,包括联合索引在范围查询中的应用、如何强制使用索引以及索引下推在LIKE查询中的影响。通过示例SQL和存储过程,解释了MySQL在不同情况下选择全表扫描或使用索引的策略,并通过trace工具展示了成本计算过程。
摘要由CSDN通过智能技术生成

索引优化实战(上)

实例sql:

CREATE TABLE `employees` (
	`id` INT ( 11 ) NOT NULL AUTO_INCREMENT,
	`name` VARCHAR ( 24 ) NOT NULL DEFAULT '' COMMENT '姓名',
	`age` INT ( 11 ) NOT NULL DEFAULT '0' COMMENT '年龄',
	`position` VARCHAR ( 20 ) NOT NULL DEFAULT '' COMMENT '职位',
	`hire_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '入职时间',
	PRIMARY KEY ( `id` ),
KEY `idx_name_age_position` ( `name`, `age`, `position` ) USING BTREE 
) ENGINE = INNODB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8 COMMENT = '员工记录表';

# 存储过程
drop procedure if exists insert_emp; 
delimiter ;; 
create procedure insert_emp()
begin 
declare i int; 
set i=1; 
while(i<=100000)do
	insert into employees(name,age,position) values(CONCAT('edg_is_champion_',i),i,'dev'); 
	set i=i+1; 
	end while; 
end;; 
delimiter ;
call insert_emp();

简介:根据上面的sql employees有name, age, position的一个联合索引,并且创建有10万条数据用于测试

一些不容易理解的sql索引例子

1.联合索引第一个字段用范围一般不会走索引
在这里插入图片描述
根据上面的sql,理论可以用到name的索引,因为满足了最左前缀原则,虽然是范围查询,那也是age和position的长度没用到,按理说name的长度应该能用到。但是实际上没有
结论:mysql内部会优化分析,如果分析出的结果集可能很大,那么回表效率不高,还不如就全表扫描。
这时候修改一下范围
在这里插入图片描述
即使10万条的数据,范围缩小到>90000 也没有使用索引。
结论:基本上来说 如果联合索引第一个字段就用范围查找不会走索引,mysql内部可能觉得第一个字段就用范围,结果集应该很大,回表效率不高,还不 如就全表扫描。
为什么说是基本上呢,再看下面例子
在这里插入图片描述
当范围缩小的93000的时候索引生效了,只是用了name的长度(索引长度怎么计算之前有讲过)。
那么如果第一条sql要想使用到索引怎么办,可以使用覆盖索引,稍微修改下sql 改为
在这里插入图片描述
这样就能使用到索引。
2.强制走索引

#‐‐ 关闭查询缓存 这个后面再说
set global query_cache_size=0; 
set global query_cache_type=0;

在这里插入图片描述
没有使用索引。
改成一下sql

EXPLAIN SELECT * FROM employees force index(idx_name_age_position)
WHERE name > ‘edg_is_champion_100’;
在这里插入图片描述

使用了索引。
因为mysql内部会对sql进行优化和成本计算(后面再说),有可能使用索引的时候不一定使用,根据成本值来判断是否使用索引。但是可以通过 force index(索引名)强制使用索引。
那么是强制使用索引快呢还是mysql内部优化后选择不走索引快呢?
mysql内部优化不选择使用索引:
在这里插入图片描述
用时0.164s
强制使用索引:
在这里插入图片描述
用时0.269s
mysql优化后选择不使用索引比使用索引快。
结论:可以通过 force index(索引名) 强制使用索引,效率嘛,不一定比mysql内部优化后选择不使用索引快。

3.in和or在表数据量比较大的情况会走索引,在表记录不多的情况下会选择全表扫描
将employees 表复制一张employees_copy1的表,里面保留五六条记录
在这里插入图片描述
使用到索引。
在这里插入图片描述
没有使用到索引

基本上如果in、or的范围如果小的话,一般都是能使用到索引的。在上面实验中,如果employees_copy1数据量有10也会走索引。

索引下推和like查询

drop procedure if exists insert_emp; 
delimiter ;; 
create procedure insert_emp()
begin 
declare i int; 
set i=1; 
while(i<=1000)do
	insert into employees(name,age,position) values(CONCAT('Labce_',i),i,'manager'); 
	set i=i+1; 
	end while; 
end;; 
delimiter ;
call insert_emp();

通过上面的存储过程插入1000条记录。
在这里插入图片描述不使用索引

在这里插入图片描述使用到了索引。并且长度为140 说明使用到了name、age、position的长度。

看到这里就有两个疑问:

  1. like查询 百分号在后面不是可以用到索引吗,为什么查询edg%不行
  2. 为什么查询L%能用到索引。而且like百分号在后面不是类似于范围查询吗,而且上图也看到了type类型为range,那么范围查询后面的索引不是会失效吗,为什么又可以用到age和position的长度。

第一个疑问: like查询 百分号在后面不是可以用到索引吗,为什么查询edg%不行
上面有提到过mysql内部会有优化和成本计算。再看rows那一列,查询edg%的时候是12万多,查询L%只有1000多,这个是预估的扫描行数,并不是实际扫描行数。也就是说查询edg%时,mysql认为它要扫描12万行,而且由于是二级索引,后续也需要回表,因此还不如全盘扫描更直接。
第二个疑问:为什么L%能用到索引并且是全长度的索引。
mysql内部会有优化和成本计算。因为在查询L%的时候,mysql认为它只要扫描1000行,数据远远少于表的总数。因此成本计算后认为使用索引会更快。至于为什么用到了全长度的联合索引,这里涉及到一个概念叫做索引下推,like就使用到了索引下推。
索引下推:MySQL 5.6引入了索引下推优化,可以在索引遍历过程中,对索引中包含的所有字段先做判断,过滤掉不符合条件的记录之后再回表,可以有效的减少回表次数。

再回到上面的例子,对于辅助的联合索引(name,age,position),正常情况按照最左前缀原则,SELECT * FROM employees WHERE name like ‘L%’ AND age = 22 AND position =‘manager’ 这种情况只会走name字段索引,因为根据name字段过滤完,得到的索引行里的age和 position是无序的,无法很好的利用索引。
在MySQL5.6之前的版本,这个查询只能在联合索引里匹配到名字是 ‘L’ 开头的索引,然后拿这些索引对应的主键逐个回表,到主键索引上找出相应的记录,再比对age和position这两个字段的值是否符合。
使用了索引下推优化后,上面那个查询在联合索引里匹配到名字是 ‘L’ 开头的索引之后,同时还会在索引里过 滤age和position这两个字段,拿着过滤完剩下的索引对应的主键id再回表查整行数据。这样就减少了回表的数据。

看到这估计又会有第三个疑问:为什么范围查找Mysql没有用索引下推优化?
估计应该是Mysql认为范围查找过滤的结果集过大,like KK% 在绝大多数情况来看,过滤后的结果集比较小,所以这里Mysql选择给 like KK% 用了索引下推优化,当然这也不是绝对的,有时like KK% 也不一定就会走索引下推。

mysql成本计算和trace工具

上面查询like edg%的例子中,看到没有使用到索引。但是查询liek L%的时候有使用到了索引。这个例子说过mysql内部会进行成本计算。那么mysql是如何进行成本计算,并且最终选择合适的索引呢,可以通过trace工具使用进行分析。开启trace工具会影响mysql性能,所以只能临时分析sql使用,用完之后立即关闭
trace工具使用
开启trace工具:set session optimizer_trace="enabled=on",end_markers_in_json=on;
关闭trace工具:set session optimizer_trace="enabled=off";
开启后,查询时如下图

SELECT * FROM employees WHERE name LIKE ‘edg%’ and age = 20 and
position = ‘dev’; SELECT * FROM information_schema.OPTIMIZER_TRACE;
在这里插入图片描述
两条sql要同时查询。

将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` like 'edg%') and (`employees`.`age` = 20) and (`employees`.`position` = 'dev'))"
          }
        ] /* steps */
      } /* join_preparation */
    },
    {
      "join_optimization": {  ‐‐第二阶段:SQL优化阶段
        "select#": 1,
        "steps": [
          {
            "condition_processing": {  ‐‐条件处理
              "condition": "WHERE",
              "original_condition": "((`employees`.`name` like 'edg%') and (`employees`.`age` = 20) and (`employees`.`position` = 'dev'))",
              "steps": [
                {
                  "transformation": "equality_propagation",
                  "resulting_condition": "((`employees`.`name` like 'edg%') and (`employees`.`position` = 'dev') and multiple equal(20, `employees`.`age`))"
                },
                {
                  "transformation": "constant_propagation",
                  "resulting_condition": "((`employees`.`name` like 'edg%') and (`employees`.`position` = 'dev') and multiple equal(20, `employees`.`age`))"
                },
                {
                  "transformation": "trivial_condition_removal",
                  "resulting_condition": "((`employees`.`name` like 'edg%') and (`employees`.`position` = 'dev') and multiple equal(20, `employees`.`age`))"
                }
              ] /* 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": 120915, ‐‐扫描行数
                    "cost": 24602  ‐‐查询成本
                  } /* 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": [  ‐‐索引使用范围
                          "edg\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000 <= name <= edg"
                        ] /* ranges */,
                        "index_dives_for_eq_ranges": true,
                        "rowid_ordered": false,  ‐‐使用该索引获取的记录是否按照主键排序
                        "using_mrr": false,
                        "index_only": false,  ‐‐是否使用覆盖索引
                        "rows": 60457,   ‐‐索引扫描行数
                        "cost": 72549,  ‐‐索引使用成本
                        "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": 120915,
                      "access_type": "scan",  ‐‐访问类型:为scan,全表扫描
                      "resulting_rows": 120915,
                      "cost": 24600,
                      "chosen": true  ‐‐确定选择
                    }
                  ] /* considered_access_paths */
                } /* best_access_path */,
                "condition_filtering_pct": 100,
                "rows_for_plan": 120915,
                "cost_for_plan": 24600,
                "chosen": true
              }
            ] /* considered_execution_plans */
          },
          {
            "attaching_conditions_to_tables": {
              "original_condition": "((`employees`.`age` = 20) and (`employees`.`name` like 'edg%') and (`employees`.`position` = 'dev'))",
              "attached_conditions_computation": [
              ] /* attached_conditions_computation */,
              "attached_conditions_summary": [
                {
                  "table": "`employees`",
                  "attached": "((`employees`.`age` = 20) and (`employees`.`name` like 'edg%') and (`employees`.`position` = 'dev'))"
                }
              ] /* attached_conditions_summary */
            } /* attaching_conditions_to_tables */
          },
          {
            "refine_plan": [
              {
                "table": "`employees`"
              }
            ] /* refine_plan */
          }
        ] /* steps */
      } /* join_optimization */
    },
    {
      "join_execution": { ‐‐第三阶段:SQL执行阶段
        "select#": 1,
        "steps": [
        ] /* steps */
      } /* join_execution */
    }
  ] /* steps */
}

由上面trace分析出全盘扫描的cost值为24600,使用联合索引cost为72549,所以mysql选择全盘扫描而不是走索引。

继续使用trace来分析

{
  "steps": [
    {
      "join_preparation": {-- 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` like 'L%') and (`employees`.`age` = 20) and (`employees`.`position` = 'manager'))"
          }
        ] /* steps */
      } /* join_preparation */
    },
    {
      "join_optimization": { -- sql优化
        "select#": 1,
        "steps": [
          {
            "condition_processing": { --条件处理
              "condition": "WHERE",
              "original_condition": "((`employees`.`name` like 'L%') and (`employees`.`age` = 20) and (`employees`.`position` = 'manager'))",
              "steps": [
                {
                  "transformation": "equality_propagation",
                  "resulting_condition": "((`employees`.`name` like 'L%') and (`employees`.`position` = 'manager') and multiple equal(20, `employees`.`age`))"
                },
                {
                  "transformation": "constant_propagation",
                  "resulting_condition": "((`employees`.`name` like 'L%') and (`employees`.`position` = 'manager') and multiple equal(20, `employees`.`age`))"
                },
                {
                  "transformation": "trivial_condition_removal",
                  "resulting_condition": "((`employees`.`name` like 'L%') and (`employees`.`position` = 'manager') and multiple equal(20, `employees`.`age`))"
                }
              ] /* 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": 120915,
                    "cost": 24602
                  } /* 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": [
                          "L\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000 <= name <= L"
                        ] /* ranges */,
                        "index_dives_for_eq_ranges": true,
                        "rowid_ordered": false,
                        "using_mrr": false,
                        "index_only": false,
                        "rows": 1000,
                        "cost": 1201,
                        "chosen": true --确定选择
                      }
                    ] /* range_scan_alternatives */,
                    "analyzing_roworder_intersect": {
                      "usable": false,
                      "cause": "too_few_roworder_scans"
                    } /* analyzing_roworder_intersect */
                  } /* analyzing_range_alternatives */,
                  "chosen_range_access_summary": {--选择索引总结
                    "range_access_plan": {
                      "type": "range_scan",
                      "index": "idx_name_age_position",
                      "rows": 1000,
                      "ranges": [
                        "L\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000 <= name <= L"
                      ] /* ranges */
                    } /* range_access_plan */,
                    "rows_for_plan": 1000,
                    "cost_for_plan": 1201,
                    "chosen": true
                  } /* chosen_range_access_summary */
                } /* range_analysis */
              }
            ] /* rows_estimation */
          },
          {
            "considered_execution_plans": [
              {
                "plan_prefix": [
                ] /* plan_prefix */,
                "table": "`employees`",
                "best_access_path": {
                  "considered_access_paths": [ --最终访问路径
                    {
                      "rows_to_scan": 1000,
                      "access_type": "range", --查询类型是范围查询
                      "range_details": {
                        "used_index": "idx_name_age_position"
                      } /* range_details */,
                      "resulting_rows": 1000,
                      "cost": 1401,
                      "chosen": true
                    }
                  ] /* considered_access_paths */
                } /* best_access_path */,
                "condition_filtering_pct": 100,
                "rows_for_plan": 1000,
                "cost_for_plan": 1401,
                "chosen": true
              }
            ] /* considered_execution_plans */
          },
          {
            "attaching_conditions_to_tables": {
              "original_condition": "((`employees`.`age` = 20) and (`employees`.`name` like 'L%') and (`employees`.`position` = 'manager'))",
              "attached_conditions_computation": [
              ] /* attached_conditions_computation */,
              "attached_conditions_summary": [
                {
                  "table": "`employees`",
                  "attached": "((`employees`.`age` = 20) and (`employees`.`name` like 'L%') and (`employees`.`position` = 'manager'))"
                }
              ] /* attached_conditions_summary */
            } /* attaching_conditions_to_tables */
          },
          {
            "refine_plan": [
              {
                "table": "`employees`",
                "pushed_index_condition": "((`employees`.`age` = 20) and (`employees`.`name` like 'L%') and (`employees`.`position` = 'manager'))",
                "table_condition_attached": null
              }
            ] /* refine_plan */
          }
        ] /* steps */
      } /* join_optimization */
    },
    {
      "join_execution": {
        "select#": 1,
        "steps": [
        ] /* steps */
      } /* join_execution */
    }
  ] /* steps */
}

根据上面trace分析 全盘扫描cost:24602,使用索引cost:1401 因此选择了使用索引。

由于篇幅原因,后续实战会对group by 、order by、where、分页等等常见sql优化

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值