找回密码
 立即注册
首页 业界区 业界 mysql索引优化实战

mysql索引优化实战

俏襟选 前天 19:08
引入索引下推:适用场景

首先我们仍以员工表为例,引入概念:索引下推
  1. CREATE TABLE `employees` (
  2. `id` int(11) NOT NULL AUTO_INCREMENT,
  3. `name` varchar(24) NOT NULL DEFAULT '' COMMENT '姓名',
  4. `age` int(11) NOT NULL DEFAULT '0' COMMENT '年龄',
  5. `position` varchar(20) NOT NULL DEFAULT '' COMMENT '职位',
  6. `hire_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '入职时间',
  7. PRIMARY KEY (`id`),
  8. KEY `idx_name_age_position` (`name`,`age`,`position`) USING BTREE
  9. ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='员工记录表';
  10. INSERT INTO employees(name,age,position,hire_time) VALUES('LiLei',22,'manager',NOW());
  11. INSERT INTO employees(name,age,position,hire_time) VALUES('HanMeimei', 23,'dev',NOW());
  12. INSERT INTO employees(name,age,position,hire_time) VALUES('Lucy',23,'dev',NOW());
  13. ‐‐ 插入一些示例数据
  14. drop procedure if exists insert_emp;
  15. delimiter ;;
  16. create procedure insert_emp()
  17. begin
  18.         declare i int;
  19.         set i=1;
  20.         while(i<=100000)do
  21.                 insert into employees(name,age,position) values(CONCAT('zhuge',i),i,'dev');
  22.                 set i=i+1;
  23.         end while;
  24. end;
  25. delimiter ;
  26. call insert_emp();
复制代码
like KK% 一般情况都会走索引
  1. #都走了全索引
  2. EXPLAIN SELECT * FROM employees WHERE name in ('LiLei','HanMeimei','Lucy') AND age = 22 AND position='manager';
  3. EXPLAIN SELECT * FROM employees WHERE (name = 'LiLei' or name = 'HanMeimei') AND age = 22 AND position='manager';
复制代码
</ol>所以什么是索引下推呢?
对于辅助的联合索引(name,age,position),正常情况按照最左前缀原则,SELECT * FROM employees WHERE name like 'LiLei%' AND age = 22 AND position ='manager' 这种情况只会走name字段索引,因为根据name字段过滤完,得到的索引行里的age和position是无序的,无法很好的利用索引。
在MySQL5.6之前的版本,这个查询只能在联合索引里匹配到名字是 'LiLei' 开头的索引,然后拿这些索引对应的主键逐个回表,到主键索引上找出相应的记录,再比对age和position这两个字段的值是否符合。
MySQL 5.6引入了索引下推优化,可以在索引遍历过程中,对索引中包含的所有字段先做判断,过滤掉不符合条件的记录之后再回表,可以有效的减少回表次数。使用了索引下推优化后,上面那个查询在联合索引里匹配到名字是 'LiLei' 开头的索引之后,同时还会在索引里过滤age和position这两个字段,拿着过滤完剩下的索引对应的主键id再回表查整行数据。
索引下推会减少回表次数,对于innodb引擎的表索引下推只能用于二级索引,innodb的主键索引(聚簇索引)树叶子节点上保存的是全行数据,所以这个时候索引下推并不会起到减少查询全行数据的效果。
为什么范围查找Mysql没有用索引下推优化?
估计应该是Mysql认为范围查找过滤的结果集过大,like KK% 在绝大多数情况来看,过滤后的结果集比较小,所以这里Mysql选择给 like KK% 用了索引下推优化,当然这也不是绝对的,有时like KK% 也不一定就会走索引下推。
Mysql如何选择合适的索引
  1.   EXPLAIN SELECT * FROM employees WHERE name like 'LiLei%' AND age = 22 AND position ='manager';
  2.   EXPLAIN SELECT * FROM employees_copy WHERE name like 'LiLei%' AND age = 22 AND position ='manager';
复制代码
对于上面这两种 name>'a' 和 name>'zzz' 的执行结果,mysql最终是否选择走索引或者一张表涉及多个索引,mysql最终如何选择索引,我们可以用trace工具来一查究竟,开启trace工具会影响mysql性能,所以只能临时分析sql使用,用完之后立即关闭
trace工具用法
  1. #很显然不走索引
  2. EXPLAIN select * from employees where name > 'a';
  3. #通过覆盖索引使其走name部分索引
  4. EXPLAIN select name,age,position from employees where name > 'a' ;
  5. #mysql估算该条件数据较少,也走了索引
  6. EXPLAIN select * from employees where name > 'zzz' ;
复制代码
Order by与Group by优化

Case1:EXPLAIN SELECT * FROM employees  WHERE name = 'LiLei'  AND position ='manager' order by age;
利用最左前缀法则:中间字段不能断,因此查询用到了name索引,从key_len=74也能看出,age索引列用在排序过程中,因为Extra字段里没有using filesort
Case 2:EXPLAIN SELECT * FROM employees  WHERE name = 'LiLei'  order by position;
key_len=74,查询使用了name索引,由于用了position进行排序,跳过了age,出现了Using filesort。
Case 3:EXPLAIN SELECT * FROM employees  WHERE name = 'LiLei'  order by age,position;
查找只用到索引name,age和position用于排序,无Using filesort。
Case 4:EXPLAIN SELECT * FROM employees  WHERE name = 'LiLei'  order by position,age;
和Case 3中explain的执行结果一样,但是出现了Using filesort,因为索引的创建顺序为
name,age,position,但是排序的时候age和position颠倒位置了
Case 5:EXPLAIN SELECT * FROM employees  WHERE name = 'LiLei'  AND age =18 order by position,age;
与Case 4对比,在Extra中并未出现Using filesort,因为age为常量,在排序中被优化,所以索引未颠倒,不会出现Using filesort。
Case 6:EXPLAIN SELECT * FROM employees  WHERE name = 'LiLei'  order by age asc,position desc;
虽然排序的字段列与索引顺序一样,且order by默认升序,这里position desc变成了降序,导致与索引的排序方式不同,从而产生Using filesort。Mysql8以上版本有降序索引可以支持该种查询方式。
Case 7:EXPLAIN SELECT * FROM employees  WHERE name in ('LiLei','Lucy')  order by age,position;
对于排序来说,多个相等条件也是范围查询
Case 8:
  1. mysql> set session optimizer_trace="enabled=on",end_markers_in_json=on; ‐‐开启trace
  2. mysql> select * from employees where name > 'a' order by position;
  3. mysql> SELECT * FROM information_schema.OPTIMIZER_TRACE;
  4. 结论:全表扫描的成本低于索引扫描,所以mysql最终选择全表扫描
  5. mysql> select * from employees where name > 'zzz' order by position;
  6. mysql> SELECT * FROM information_schema.OPTIMIZER_TRACE;
  7. 查看trace字段可知索引扫描的成本低于全表扫描,所以mysql最终选择索引扫描
  8. mysql> set session optimizer_trace="enabled=off"; ‐‐关闭trace
复制代码
总的来说,where条件与order by排序字段的排列组合要遵循最左前缀原则
分页优化查询

很多时候我们业务系统实现分页功能可能会用如下sql实现
select * from employees limit 10000,10;
表示从表 employees 中取出从 10001 行开始的 10 行记录。看似只查询了 10 条记录,实际这条 SQL 是先读取 10010条记录,然后抛弃前 10000 条记录,然后读到后面 10 条想要的数据。因此要查询一张大表比较靠后的数据,执行效率是非常低的。
优化技巧:
连续自增主键
  1. # Using where; Using filesort
  2. EXPLAIN SELECT * FROM employees  WHERE name > 'a'  order by name;
  3. # 用覆盖索引优化:Using where; Using index
  4. EXPLAIN SELECT name,age,position FROM employees  WHERE name > 'a'  order by name;
复制代码
改写后的 SQL 走了索引,扫描行数大大减少,执行效率更高。
但是很显然这样的场景并不实用,因为表中可能某些记录被删后,主键空缺,导致结果不一致
另外如果原 SQL 是 order by 非主键的字段,按照上面说的方法改写会导致两条 SQL 的结果不一致。所以这种改写得满足以下两个条件:
  1. #没添加单独order by,表示通过主键排序
  2. select * from employees limit 90000,5;
  3. #与查询结果一致
  4. select * from employees where id > 90000 limit 5;
复制代码
根据非主键字段排序的分页查询

再看一个根据非主键字段排序的分页查询,SQL 如下:
  1. 1. 主键自增且连续
  2. 2. 结果是按照主键排序的
复制代码
为什么没走索引排序呢?扫描整个索引并查找到没索引的行(可能要遍历多个索引树)的成本比扫描全表的成本更高,所以优化器放弃使用索引。(扫描整个二级索引还要回表)
如何优化:
其实很简单啦。排序时返回尽量少的字段,或者说是索引覆盖的字段,所以可以像这样优化:
  1. select * from employees ORDER BY name limit 90000,5;
  2. #Using filesort  key:null -> 全表扫描
  3. EXPLAIN select * from employees ORDER BY name limit 90000,5;
复制代码
原 SQL 使用的是 filesort 排序,而优化后的 SQL 使用的是索引排序。
Join关联查询优化

首先我们先引入一些测试数据:
[code]‐‐ 示例表: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(icount(字段)>count(主键 id) //字段有索引,count(字段)统计走二级索引,二级索引存储数据比主键索引少,所以count(字段)>count(主键 id)</p>字段无索引:count(*)≈count(1)>count(主键 id)>count(字段) //字段没有索引count(字段)统计走不了索引,count(主键 id)还可以走主键索引,所以count(主键 id)>count(字段)
count(1)跟count(字段)执行过程类似,不过count(1)不需要取出字段统计,就用常量1做统计,count(字段)还需要取出字段,所以理论上count(1)比count(字段)会快一点。
count(*) 是例外,mysql并不会把全部字段取出来,而是专门做了优化,不取值,按行累加,效率很高,所以不需要用count(列名)或count(常量)来替代 count(*)。
为什么对于count(id),mysql最终选择辅助索引而不是主键聚集索引?因为二级索引相对主键索引存储数据更少,检索性能应该更高,mysql内部做了点优化(应该是在5.7版本才优化)。
索引设计原则


  • 代码先行,索引后上
  • 联合索引尽量覆盖条件
  • 不要在小基数字段上建立索引
  • 长字符串我们可以采用前缀索引
  • where与order by冲突时优先where
  • 基于慢sql查询做优化 ,可以根据监控后台的一些慢sql,针对这些慢sql查询做特定的索引优化

来源:豆瓜网用户自行投稿发布,如果侵权,请联系站长删除

相关推荐

您需要登录后才可以回帖 登录 | 立即注册