索引优化策略有哪些

一、前言

  本文基于mysql8.0的innodb测试,建表在做对应的优化策略测试时记得加索引,由于文中太多查询例子不一一针对建立索引了,只挑几个建索引举例。

CREATE TABLE `user` (
  `id` int(11) NOT NULL,
  `name` varchar(20) DEFAULT NULL,
  `sex` varchar(5) DEFAULT NULL,
  `address` varchar(255) DEFAULT NULL,
  `birthday` timestamp(6) NULL DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

二、优化策略

  下面演示均对过滤列字段建立单索引不一一建立了,联合索引我会特别建立。

  1.不要在索引列上加函数或运算

-- 全表扫描
explain select * from user where year(birthday) < 2020
-- 走索引
explain select * from user where birthday < '2020-03-17'
-- 全表扫描
explain select * from user where id + 1 = 2
-- 走索引
explain select * from user where name = 'lihua'

  2.隐式类型转换

  以address字段举例,address为varchar类型,我们为其建立索引

-- 全表扫描
explain select * from user where address = 2
-- 走索引
explain select * from user where address = '2'

 

   通过对比可以发现第一次没有使用索引,第二次是使用了索引idx_address的,为什么会这样呢?因为第一种发生了隐式转换,即:

explain select * from user where address = 2
-- 等价于
explain select * from user where CAST(address AS signed int) = 2

  隐式转换在索引字段上做了函数处理,因此会全表扫描。

   3.前导模糊查询不会使用索引

-- 全表扫描
explain select * from user where name like '%li'
-- 使用索引
explain select * from user where name like 'li%'

 

   对比结果可以发现非前导模糊查询可以使用索引(%li%也不能使用索引,不做单独演示了)

  4.联合索引最左匹配原则

  mysql从左到右匹配,知道遇到范围查询(>、<、between、like)就停止匹配(这个停止匹配是指不会在接着向右匹配,当前范围查询还是可以匹配到的),举个例:

select * from user where name = 'lihua' and sex = 'm' and birthday < '2020-03-17' and address = '1'

  建立(`name`, `sex`, `birthday`, `address`)顺序的索引,address使用不到索引的,而建立(`name`, `sex`, `address`, `birthday`)顺序的索引,则都可以使用到索引。

  5.查询时=可以乱序

-- 可以乱序
select * from user where name = 'lihua' and sex = 'm'
select * from user where sex = 'm'and name = 'lihua'

  mysql查询时会将查询顺序优化成和联合索引顺序一致。

  6.避免filesort排序

  mysql中无法利用索引完成的排序成为“文件排序”,执行计划Extra中若出现Using filesort, 说明mysql会对数据使用一个外部的索引排序,而不是按照表内索引顺序进行读取,最左匹配原则也适用于排序,我们建立(`age`, `name`, `sex`)的联合索引,看下面具体5个例子及执行结果:

-- 不带过滤条件
explain select * from user order by age

-- 排序使用索引
explain select * from user where age = 22 order by age

-- 最左匹配原则,排序条件未走索引,文件排序
explain select * from user where age > 22 order by name

-- 当前筛选条件与排序条件使用的字段顺序与索引不一致,文件排序
explain select * from user where age = 22 order by sex,name

-- 排序字段方向一个升序一个降序,文件排序
explain select * from user where age = 22 order by name asc,sex desc

   7.union、in、or均可命中索引

-- 使用索引
explain 
select name from user where name = 'lihua'
union all
select name from user where name = 'limei'
-- 使用索引
explain select name from user where name in ('lihua','limei')
-- 使用索引
explain select name from user where name = 'lihua' or name = 'limei'

  三个关键字使用后的查询效率比较:

  1)对于索引列来说,最好使用union all因为复杂的查询(包含运算等),将使or、in放弃索引而全表扫描,除非你能确定or、in会使用索引;

  2)对于只有非索引字段来说,就老是用or、in,因为非索引字段本来就要全表扫描union all只会成倍数增加表扫描次数;

  3)对于非索引字段及索引字段(索引字段有效)都有的情况来说,union all、or、in都可以理论上;

  8.负向条件不会使用索引(不绝对,有时会走范围索引,取决于范围大小)

  负向条件有!=、<>、not in、not like、not exists等,多数介绍sql优化的文章都会提到避免使用!=,因为不走索引,我们用实例验证一下,只对name建立但索引,库中数据只有三条,此时使用!=,查看执行计划:

-- 使用范围索引
explain select * from user where name != 'lihua'

   观察执行计划,可以发现!=确确实实是走了name的范围索引的,分析原因实际应用中很可能是因为不等于的数据占比很高,走索引不如全表扫描效率高。

  9.分页查询优化

  mysql的分页并不是跳过offset行,而是取offset+n行,然后放弃前offset行取后面n行,当offset很大时效率就很低,利用覆盖索引,避开回表

  解决方案一:书签,记录上次访问位置,下次直接从书签位置开始

select id from table limit 10000 20
-- 改成
select id from table where id>10000 limit 20

  解决方案二:关联(或者join),根据覆盖索引查询需要的主键,再根据主键关联原表获得需要的数据

select id from table,
(select id from table limit 10000,20) tmp where table.id = tmp.id

  查询所有数据

select * from table
where id> =(select id from table limit 10000, 1) limit 20
select *from table,
(select id from table limit 10000,20) tmp where table.id = tmp.id

  一个关于limit的小经验:当使用limit时不使用order by ,查询id走的是索引,按索引存储位置取数据,*是查全表按表记录位置取结果,所以得出结论直接select index from table查询,不管是但索引还是组合索引都会返回索引为止的数据,如果select中包含其他费索引列就会返回顺序记录结果。

三、结语

  1.本文测试均基于mysql8.0innodb,不同版本可能有所不同,实际开发中还是要具体问题具体分析多查看执行计划,一切以提升效率为前提不用过于在意条条框框,毕竟索引优化也是为了效率服务,本文主要记录mysql学习过程,如有错误请指正,一起学习一起进步。

  2.本文中提到的覆盖索引,回表可以查看 https://www.cnblogs.com/ghoster/p/12509611.html

  还有建立单索引还是联合索引问题,推荐看这篇博客 https://blog.csdn.net/Abysscarry/article/details/80792876

猜你喜欢

转载自www.cnblogs.com/ghoster/p/12512110.html