Mysql索引优化

1 例子所用表

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('zhuge',i),i,'dev');
    set i=i+1;
   end while;
  end;;
 delimiter ;
 call insert_emp();

2 索引优化

1、联合索引第一个字段用范围不会使用索引。

EXPLAIN SELECT * FROM employees WHERE name > 'LiLei' AND age = 22 AND position ='manager';

        需要关注一下的是possible_keys中,mysql认为可以走索引,却没有走索引,这是因为mysql底层会计算cost,根据cost来选择如何执行。(认为回表的效率还不如全表扫描高)

2、如果你觉得走索引效率必然会高,那么可以让mysql强制走索引,使用force index

EXPLAIN SELECT * FROM employees force index(idx_name_age_position) WHERE name > 'LiLei' AND age = 22 AND position ='manager';

 3、覆盖索引优化。

4、in和or在表数据量比较大的时候会走索引,在表记录不多的情况下会走全表扫描的。

5、like KK% 一般情况都会走索引。(like可能会关系到索引下推 ICP)。

3 mysql是如何选择合适的索引

        前面有写例子说到,执行计划中的possible_key有值,但是最后却没有走索引。这是因为mysql底层会计算一个cost的成本值,根据cost成本值来选择是否走索引,走哪个索引。我们可以通过trace工具来查看,开启trace工具会影响mysql的性能,所以只能临时分析sql使用。用完之后关闭。

set session optimizer_trace="enabled=on",end_markers_in_json=on; ‐‐开启trace
SELECT * FROM employees WHERE name > 'LiLei';
SELECT * FROM information_schema.OPTIMIZER_TRACE; --查看
{
  "steps": [
    {
      "join_preparation": {
        "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` > 'LiLei')"
          }
        ] /* steps */
      } /* join_preparation */
    },
    {
      "join_optimization": {
        "select#": 1,
        "steps": [
          {
            "condition_processing": {
              "condition": "WHERE",
              "original_condition": "(`employees`.`name` > 'LiLei')",
              "steps": [
                {
                  "transformation": "equality_propagation",
                  "resulting_condition": "(`employees`.`name` > 'LiLei')"
                },
                {
                  "transformation": "constant_propagation",
                  "resulting_condition": "(`employees`.`name` > 'LiLei')"
                },
                {
                  "transformation": "trivial_condition_removal",
                  "resulting_condition": "(`employees`.`name` > 'LiLei')"
                }
              ] /* 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": 100048, --扫描的行数
                    "cost": 20301   --全表扫描的成本
                  } /* 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": [
                          "LiLei < name"
                        ] /* ranges */,
                        "index_dives_for_eq_ranges": true,
                        "rowid_ordered": false,
                        "using_mrr": false,
                        "index_only": false,
                        "rows": 50024, --使用索引扫描的行数
                        "cost": 60030, --使用索引的成本
                        "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": 100048,
                      "access_type": "scan", --访问类型,scan代表全表扫描
                      "resulting_rows": 100048,
                      "cost": 20299,
                      "chosen": true --确定选择
                    }
                  ] /* considered_access_paths */
                } /* best_access_path */,
                "condition_filtering_pct": 100,
                "rows_for_plan": 100048,
                "cost_for_plan": 20299,
                "chosen": true
              }
            ] /* considered_execution_plans */
          },
          {
            "attaching_conditions_to_tables": {
              "original_condition": "(`employees`.`name` > 'LiLei')",
              "attached_conditions_computation": [
              ] /* attached_conditions_computation */,
              "attached_conditions_summary": [
                {
                  "table": "`employees`",
                  "attached": "(`employees`.`name` > 'LiLei')"
                }
              ] /* 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哦!

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

4 order by和group by索引优化

        

         从explain的执行结果来看:key_len=74,查询使用了name索引,由于用了position进行排序,跳过了age,出现了Using filesort。

        利用最左前缀法则:中间字段不能断,因此查询用到了name索引,从key_len=74也能看出,age索引列用在排序过程中,因为Extra字段里没有using filesort

         当age和postion位置顺序不符合索引的顺序时,使用using filesort。

        age为常量,在排序中被优化,所以索引未颠倒,不会出现Using filesort。

 

        postion使用降序排序,与索引的顺序不一致,所以出现using filesort。Mysql8以上版本有降序索引可以支持该种查询方式。

         对于排序来说多个相等条件查询也是范围查询,我们知道范围查询后面时不走索引。

EXPLAIN SELECT * FROM employees WHERE name>'LiLei' ORDER BY name; --对应结果1
EXPLAIN SELECT id, name,age FROM employees WHERE name>'LiLei' ORDER BY name;--对应结果2

         可以用覆盖索引来优化order by。

总结:

        (1)MySQL支持两种方式的排序:索引排序(index)和文件排序(file sort),Using index是指mysql使用索引来排序,Using filesort表示使用文件排序。index的效率要高于filesort;

        (2)order by满足一下条件会使用index排序。

               1)order by语句使用的列满足最左前列。

                2)where字句和order by子句使用的列组合满足最左前列。

        (3)尽量在索引列上完成排序,遵循索引简历时的最左前缀原则。

        (4)能用覆盖索引尽量用覆盖索引。

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

5 using filesort文件排序的方式

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

(2)双路排序(又叫回表排序模式):根据相应的条件将排序的列和可以直接定位到行的ID取出放在sort buffer中排序,排好序后再根据ID回表取回其他所需的列;用trace工具可以看到sort_mode信息里显示<sort_key,rowid>

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

  • 如果 字段的总长度小于max_length_for_sort_data ,那么使用 单路排序模式;
  • 如果 字段的总长度大于max_length_for_sort_data ,那么使用 双路排序模∙式。

6 Limit优化

        首先讲一下limit是如何执行。

 mysql> select * from employees limit 10000,10;

       上面这条sql表示从10001行开始,取10条数据。 在执行时,mysql会先取出10010条记录,然后抛弃前10000条记录,然后读到后面10条想要的数据。因此要查询一张大表比较靠后的数据执行效率会很低。

6.1根据自增且连续的主键排序的分页查询

        该 SQL 表示查询从第 90001开始的五行数据,没添加单独 order by,表示通过主键排序。我们再看表 employees ,因为主键是自增并且连续的,所以可以改写成按照主键去查询从第 90001开始的五行数据,如下:

      我们可以看到两条sql的查询结果一样的,但是扫描的行数第二条更少,且走了索引。但是,这条改写的SQL 在很多场景并不实用,因为表中可能某些记录被删后,主键空缺(中间删除了行),导致结果不一致。

 6.2 根据非主键字段排序的分页查询

       

 

         通过执行计划可以发现没走索引,按道理应该是可以走索引,这是因为查询数据量大,导致走索引的cost比走全表扫描的cost 还大,所以mysql选择走全表扫描。

        可以让排序时返回的字段尽可能少,所以可以先查出主键和分页操作,然后根据主键去找到相应的记录。

SELECT * FROM (SELECT id FROM employees ORDER BY name LIMIT 90000,5) as t1 INNER JOIN employees as t2 ON t1.id=t2.id; 

          可以看出排序用的是索引排序。

7关联查询

        关于关联查询,阿里巴巴手册中有说超过3个表禁止使用join。

 7.1 例子

CREATE TABLE `t1` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `a` int(11) DEFAULT NULL,
 `b` int(11) DEFAULT NULL,
 PRIMARY KEY (`id`),
 KEY `idx_a` (`a`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

 create table t2 like t1;

 ‐‐ 插入一些示例数据
 ‐‐ 往t1表插入1万行记录
 drop procedure if exists insert_t1;
 delimiter ;;
 create procedure insert_t1()
  begin
   declare i int;
   set i=1;
   while(i<=10000)do
    insert into t1(a,b) values(i,i);
    set i=i+1;
   end while;
  end;;
 delimiter ;
 call insert_t1();
 
 drop procedure if exists insert_t2;
 delimiter ;;
 create procedure insert_t2()
 begin
  declare i int;
  set i=1;
  while(i<=100)do
   insert into t2(a,b) values(i,i);
   set i=i+1;
  end while;
 end;;
 delimiter ;
 call insert_t2();

 7.2 表关联常见的两种算法

(1)嵌套循环连接Nested-Loop Join(NLJ)算法

        一次一行循环地从第一张表(称为驱动表)中读取行,在这行数据中取到关联字段,根据关联字段在另一张表(被驱动表)里取出满足条件的行,然后取出两张表的结果合集。

从执行计划中可以看到这些信息:

  • 驱动表是 t2,被驱动表是 t1。先执行的就是驱动表(执行计划结果的id如果一样则按从上到下顺序执行sql);优化器一般会优先选择小表做驱动表。所以使用 inner join 时,排在前面的表并不一定就是驱动表。当使用left join时,左表是驱动表,右表是被驱动表,
  • 当使用right join时,右表时驱动表,左表是被驱动表,当使用join时,mysql会选择数据量比较小的表作为驱动表,大表作为被驱动表。
  • 使用了 NLJ算法。一般 join 语句中,如果执行计划 Extra 中未出现 Using join buffer 则表示使用的 join 算法是 NLJ。 

上面sql的大致流程如下:
        1. 从表 t2 中读取一行数据(如果t2表有查询过滤条件的,会从过滤结果里取出一行数据);
        2. 从第 1 步的数据中,取出关联字段 a,到表 t1 中查找;
        3. 取出表 t1 中满足条件的行,跟 t2 中获取到的结果合并,作为结果返回给客户端;
        4. 重复上面 3 步。
     整个过程会读取 t2 表的所有数据(扫描100行),然后遍历这每行数据中字段 a 的值,根据 t2 表中 a 的值索引扫描 t1 表中的对应行(扫描100次 t1 表的索引,1次扫描可以认为最终只扫描 t1 表一行完整数据,也就是总共 t1 表也扫描了100行)。因此整个过程扫描了 200 行。
     如果被驱动表的关联字段没索引,使用NLJ算法性能会比较低(下面有详细解释),mysql会选择Block Nested-Loop Join算法。

(2)基于块的嵌套循环连接 Block Nested-Loop Join(BNL)算法

        把驱动表的数据读入到 join_buffer 中,然后扫描被驱动表,把被驱动表每一行取出来跟join_buffer 中的数据做对比。

 上面sql的大致流程如下:
        1. 把 t2 的所有数据放入到 join_buffer 中
        2. 把表 t1 中每一行取出来,跟 join_buffer 中的数据做对比
        3. 返回满足 join 条件的数据
     整个过程对表 t1 和 t2 都做了一次全表扫描,因此扫描的总行数为10000(表 t1 的数据总量) + 100(表 t2 的数据总量) =10100。并且 join_buffer 里的数据是无序的,因此对表 t1 中的每一行,都要做 100 次判断,所以内存中的判断次数是100 * 10000= 100 万次。
     这个例子里表 t2 才 100 行,要是表 t2 是一个大表,join_buffer 放不下怎么办呢?
join_buffer 的大小是由参数 join_buffer_size 设定的,默认值是 256k。如果放不下表 t2 的所有数据话,策略很简单,就是分段放。比如 t2 表有1000行记录, join_buffer 一次只能放800行数据,那么执行过程就是先往 join_buffer 里放800行记录,然后从 t1 表里取数据跟 join_buffer 中数据对比得到部分结果,然后清空 join_buffer ,再放入 t2 表剩余200行记录,再次从 t1 表里取数据跟 join_buffer 中数据对比。所以就多扫了一次 t1 表。

被驱动表的关联字段没索引为什么要选择使用 BNL 算法而不使用 Nested-Loop Join 呢?
        如果上面第二条sql使用 Nested-Loop Join,那么扫描行数为 100 * 10000 = 100万次,这个是磁盘扫描。很显然,用BNL磁盘扫描次数少很多,相比于磁盘扫描,BNL的内存计算会快得多。
因此MySQL对于被驱动表的关联字段没索引的关联查询,一般都会使用 BNL 算法。如果有索引一般选择 NLJ 算法,有索引的情况下 NLJ 算法比 BNL算法性能更高.

对于关联sql的优化
        关联字段加索引,让mysql做join操作时尽量选择NLJ算法
        小表驱动大表,写多表连接sql时如果明确知道哪张表是小表可以用straight_join写法固定连接驱动方式,省去mysql优化器自己判断的时间.。
        straight_join解释:straight_join功能同join类似,但能让左边的表来驱动右边的表,能改表优化器对于联表查询的执行顺序。
比如:select * from t2 straight_join t1 on t2.a = t1.a; 代表指定mysql选着 t2 表作为驱动表。
        straight_join只适用于inner join,并不适用于left join,right join。(因为left join,right join已经代表指定了表的执行顺序)
        尽可能让优化器去判断,因为大部分情况下mysql优化器是比人要聪明的。使用straight_join一定要慎重,因为部分情况下人为指定的执行顺序并不一定会比优化引擎要靠谱。

对于小表定义的明确
        在决定哪个表做驱动表的时候,应该是两个表按照各自的条件过滤,过滤完成之后,计算参与 join 的各个字段的总数据量,数据量小的那个表,就是“小表”,应该作为驱动表。

8 in和exsits优化

 原则:小表驱动大表,即小的数据集驱动大的数据集。

in:

select * from A where id in (select id from B);

#等价于:
for(select id from B){
 select * from A where A.id = B.id
}

        当B表的数据集小于A表的数据集时,in由于exists。

exists:

select * from A where exists (select 1 from B where B.id = A.id)
 #等价于:
for(select * from A){
 select * from B where B.id = A.id
}

#A表与B表的ID字段应建立索引

        当A表的数据集小于B表的数据集时,exists优于in
        将主查询A的数据,放到子查询B中做条件验证,根据验证结果(true或false)来决定主查询的数据是否保留

1、EXISTS (subquery)只返回TRUE或FALSE,因此子查询中的SELECT * 也可以用SELECT 1替换,官方说法是实际执行时会忽略SELECT清单,因此没有区别
2、EXISTS子查询的实际执行过程可能经过了优化而不是我们理解上的逐条对比
3、EXISTS子查询往往也可以用JOIN来代替,何种最优需要具体问题具体分析

count(*)大胆的用

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
MySQL索引优化是提高查询性能的关键。下面是一些优化MySQL索引的方法: 1. 选择合适的索引 索引并非越多越好,需要根据实际情况选择合适的索引。通常来说,可以根据以下几个原则来选择索引: - 对经常查询的列进行索引 - 对频繁用于WHERE、JOIN、ORDER BY和GROUP BY子句的列进行索引 - 对选择性高的列进行索引(即不同值越多的列) 2. 删除不必要的索引 过多的索引会占用过多的磁盘空间并降低写操作的性能,因此需要删除不必要的索引。可以使用如下语句查询所有的索引,并根据实际情况删除不必要的索引: ``` SHOW INDEX FROM table_name; ``` 3. 避免全表扫描 全表扫描是指MySQL查询时没有使用索引,而是扫描整个表的数据。全表扫描会导致查询效率低下,因此需要避免全表扫描。可以通过优化查询语句,例如添加索引、改变表结构等方式来避免全表扫描。 4. 使用覆盖索引 覆盖索引是指查询语句只需要使用索引中的列就可以返回查询结果,而不需要再去查询表中的数据。覆盖索引可以减少查询的IO操作,提高查询效率。 5. 定期维护索引 索引需要定期维护,包括优化查询语句、删除不必要的索引、重新构建索引等。可以使用MySQL自带的OPTIMIZE TABLE命令来优化表的索引和碎片,从而提高查询性能。 以上就是MySQL索引优化的一些方法,需要根据实际情况进行选择和优化。需要注意的是,不同的索引优化策略对于不同的数据库环境和数据结构可能会有所不同,需要根据实际情况进行调整。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值