经过explain性能分析之后判断什么情况下索引最优化:
创建合理的索引达到最优效果
- id 号每个号码,表示一趟独立的查询。一个 sql 的查询趟数越少越好。
- select_type 代表查询的类型,主要是用于区别普通查询、联合查询、子查询等的复杂查询。
- table字段表示:这个数据是基于哪张表的
- type 是查询的访问类型。是较为重要的一个指标,结果值从最好到最坏依次是:system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index >ALL ,一般来说,得保证查询至少达到 range 级别,最好能达到 ref
- possible_keys 字段表示: 显示可能应用在这张表中的索引,一个或多个。查询涉及到的字段上若存在索引,则该索引将被列出,但不一 定被查询实际使用。
- key字段表示:实际使用的索引。如果为NULL,则没有使用索引
- key_len 字段表示:表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。 key_len 字段能够帮你检查是否充分的 利用上了索引。ken_len 越长,说明索引使用的越充分。
- ref字段表示:显示索引的哪一列被使用了,如果可能的话,是一个常数。哪些列或常量被用于查找索引列上的值。
- rows字段表示:rows 列显示 MySQL 认为它执行查询时必须检查的行数。越少越好!
索引失效的原理是什么?(有创建索引却没有使用到索引,索引失效会导致性能下降)
单值索引在B+树的结构里,一个节点只存一个键值对
下图,由数据库的a字段和b字段组成一个联合索引。
MySQL技术内幕 InnoDB存储引擎 第2版
从本质上来说,联合索引也是一个B+树,和单值索引不同的是,联合索引的键值对不是1,而是大于1个。
a, b 排序分析
a顺序:1,1,2,2,3,3
b顺序:1,2,1,4,1,2
大家可以发现a字段是有序排列,b字段是无序排列(因为B+树只能选一个字段来构建有序的树)一不小心又会发现,在a相等的情况下,b字段是有序的。
大家想想平时编程中我们要对两个字段排序,是不是先按照第一个字段排序,如果第一个字段出现相等的情况,就用第二个字段排序。这个排序方式同样被用到了B+树里。
————————————————
遵循最佳左前缀法则情况下:
首先a字段在B+树上是有序的,所以我们可以通过二分查找法来定位到a=1的位置
其次在a确定的情况下,b是相对有序的,因为有序,所以同样可以通过二分查找法找到b=2的 位置。
不遵循最佳左前缀法则下:
我们来回想一下b有顺序的前提:在a确定的情况下。
现在你的a都飞了,那b肯定是不能确定顺序的,在一个无序的B+树上是无法用二分查找来定位到b字段的。
避免索引失效:
1.最佳左前缀法则:
查询字段与索引字段顺序的不同会导致,索引无法充分使用,甚至索引失效!
原因:使用复合索引,需要遵循最佳左前缀法则,即如果索引了多列,要遵守最左前缀法则。指的是查询从索 引的最左前列开始并且不跳过索引中的列。
结论:过滤条件要使用索引必须按照索引建立时的顺序,依次满足,一旦跳过某个字段,索引后面的字段都无 法被使用。
2. 不要在索引列上做任何计算
不在索引列上做任何操作(计算、函数、(自动 or 手动)类型转换),会导致索引失效而转向全表扫描。
3.在查询列上做了转换
字符串不加单引号,则会在 name 列上做一次转换!
4.索引列上不能有范围查询
建议:将可能做范围查询的字段的索引顺序放在最后,因为放在前面会导致后面的索引失效。
5.尽量使用覆盖索引
即查询列和索引列一致,不要写 select *!(查询列不写星号)
6.使用不等于(!= 或者<>)的时候
mysql 在使用不等于(!= 或者<>)时,有时会无法使用索引会导致全表扫描。
7.字段的 is not null 和 is null
当字段允许为 Null 的条件下:
is not null 用不到索引,is null 可以用到索引。
8.like 的前后模糊匹配
前缀不能出现模糊匹配!
9.减少使用 or
使用 union all 或者 union 来替代:
假设 索引 index(a,b,c);即有三索引 a , b , c
Where 语句 | 索引是否被使用 |
where a = 3 | Y,使用到 a |
where a = 3 and b = 5 | Y,使用到 a,b |
where a = 3 and b = 5 and c = 4 | Y,使用到 a,b,c |
where b = 3 或者 where b = 3 and c = 4 或者 where c = 4 | N |
where a = 3 and c = 5 | 使用到 a, 但是 c 不可以,b 中间断了 |
where a = 3 and b > 4 and c = 5 | 使用到 a 和 b, c 不能用在范围之后,b 断了 |
where a is null and b is not null | is null 支持索引 但是 is not null 不支持,所 以 a 可以使用索引,但是 b 不可以使用 |
where a <> 3 | 不能使用索引 |
where abs(a) =3 | 不能使用 索引 |
where a = 3 and b like 'kk%' and c = 4 | Y,使用到 a,b,c |
where a = 3 and b like '%kk' and c = 4 | Y,只用到 a |
where a = 3 and b like '%kk%' and c = 4 | Y,只用到 a |
where a = 3 and b like 'k%kk%' and c = 4 | Y,使用到 a,b,c |
避免索引失效(联合查询情况下优化):
1.左连接left join
原因:左连接时以左表为基准,所以左表所做的是all全表扫描,而右表是以左表为基准所做的查询就不需要全表扫描只需做连接匹配扫描。即要在右表做索引。
如何优化?
- 在优化关联查询时,只有在被驱动表(右表)上建立索引才有效!
- left join 时,左侧的为驱动表,右侧为被驱动表!
2.内连接inner join
- inner join 时,mysql 会自己帮你把小结果集的表选为驱动表。
- straight_join: 效果和 inner join 一样,但是会强制将左侧作为驱动表!
优化方法:小表连接大表查询,所以要以小的表作为左侧表而大表做为右侧表。
explain性能测试在rows上就能得到一定的优化,rows字段优化策略是越少越好。
3.四个关联查询案例分析
上述两个案例,第一个查询效率较高,且有优化的余地。
第二个案例中,子查询作为被驱动表,由于子查询是虚表, 无法建立索引,因此不能优化。
结论:
- 子查询尽量不要放在被驱动表,有可能使用不到索引;
- left join时,尽量让实体表作为被驱动表。
- 能够直接多表关联的尽量直接关联,不用子查询!
避免索引失效(子查询情况下优化):
如何优化?
- 解决 dept 表的全表扫描,建立 ceo 字段的
- 进一步优化,替换 not in。在范围判断时,尽量不要使用 not in 和 not exists,使用 left join on xxx is null 代替。on与is null精确度更高。
排序分组优化:
1.无过滤不索引
创建索引:create index idx_age_deptid_name on emp (age,deptid,name);
using filesort 说明进行了手工排序!原因在于没有 where 作为过滤条件!
无过滤,不索引。where,limt 都相当于一种过滤条件,所以才能使用上索引!
2.顺序错,必排序
empno 字段并没有建立索引,因此也无法用到索引,此字段需要排序!
where 两侧列的顺序可以变换,效果相同,但是 order by 列的顺序不能随便变换!order by未能遵从最佳左前叠法则
deptid 作为过滤条件的字段,无法使用索引,因此排序没法用上索引。未能遵从最佳左前叠法则导致后面索引失效。
3.方向反,必排序
如果可以用上索引的字段都使用正序或者逆序,实际上是没有任何影响的,无非将结果集调换顺序。
如果排序的字段,顺序有差异,就需要将差异的部分,进行一次倒置顺序,因此还是需要手动排序的!
4.索引的选择
①首先,清除 emp 上面的所有索引,只保留主键索引! drop index idx_age_deptid_name on emp;
②查询:年龄为 30 岁的,且员工编号小于 101000 的用户,按用户名称排序
③全表扫描肯定是不被允许的,因此我们要考虑优化。
思路:首先需要让 where 的过滤条件,用上索引; 查询中,age.empno 是查询的过滤条件,而 name 则是排序的字段,因此我们来创建一个此三个字段的复合索引:
再次查询,发现 using filesort 依然存在。
原因: empno 是范围查询,因此导致了索引失效,所以 name 字段无法使用索引排序。 所以,三个字段的符合索引,没有意义,因为 empno 和 name 字段只能选择其一!
④解决: 鱼与熊掌不可兼得,
因此,要么选择 empno,要么选择 name
drop index idx_age_empno_name on emp;
create index idx_age_name on emp(age,name);
create index idx_age_empno on emp(age,empno);
原因:所有的排序都是在条件过滤之后才执行的,
所以如果条件过滤了大部分数据的话,几百几千条数据进行排序 其实并不是很消耗性能,即使索引优化了排序但实际提升性能很有限。
相对的 empno<100000 这个条件如果没 有用到索引的话,要对几万条的数据进行扫描,这是非常消耗性能的,使用 empno 字段的范围查询,过滤性更好 (empno 从 100000 开始)!
结论: 当范围条件和 group by 或者 order by 的字段出现二选一时 ,优先观察条件字段的过滤数量,如果过滤的 数据足够多,而需要排序的数据并不多时,优先把索引放在范围字段上。反之,亦然。
5.using filesort(使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。)
mysql 的排序算法
①双路排序
MySQL 4.1 之前是使用双路排序,字面意思就是两次扫描磁盘,最终得到数据,读取行指针和 orderby 列,对他 们进行排序,然后扫描已经排序好的列表,按照列表中的值重新从列表中读取对应的数据输出。
从磁盘取排序字段,在 buffer 进行排序,再从磁盘取其他字段。
简单来说,取一批数据,要对磁盘进行了两次扫描,众所周知,I\O 是很耗时的,所以在 mysql4.1 之后,出现了第二种改进算法,就是单路排序。
②单路排序
从磁盘读取查询需要的所有列,按照 order by 列在 buffer 对它们进行排序,然后扫描排序后的列表进行输出, 它的效率更快一些,避免了第二次读取数据。
并且把随机 IO 变成了顺序 IO,但是它会使用更多的空间, 因为它把每一行都保存在内存中了。
③单路排序的问题
由于单路是后出的,总体而言好过双路。
但是存在以下问题:
在 sort_buffer 中,方法 B 比方法 A 要多占用很多空间,因为方法 B 是把所有字段都取出, 所以有可能取出的数 据的总大小超出了 sort_buffer 的容量,导致每次只能取 sort_buffer 容量大小的数据,进行排序(创建 tmp 文件,多路合并),排完再取取 sort_buffer 容量大小,再排……从而多次 I/O。
结论:
本来想省一次 I/O 操作,反而导致了大量的 I/O 操作,反而得不偿失。
单路排序如何优化?
①增大 sort_butter_size 参数的设置
不管用哪种算法,提高这个参数都会提高效率,当然,要根据系统的能力去提高,因为这个参数是针对每个进 程的 1M-8M 之间调整
②增大 max_length_for_sort_data 参数的设置
mysql 使用单路排序的前提是排序的字段大小要小于 max_length_for_sort_data。
提高这个参数,会增加用改进算法的概率。
但是如果设的太高,数据总容量超出 sort_buffer_size 的概率就增大, 明显症状是高的磁盘 I/O 活动和低的处理器使用率。(1024-8192 之间调整)。
③减少 select 后面的查询的字段。
当 Query 的字段大小总和小于 max_length_for_sort_data 而且排序字段不是 TEXT|BLOB 类型时,会用改进后的 算法——单路排序, 否则用老算法——多路排序。
两种算法的数据都有可能超出 sort_buffer 的容量,超出之后,会创建 tmp 文件进行合并排序,导致多次 I/O, 但是用单路排序算法的风险会更大一些,所以要提高 sort_buffer_size。
6.使用覆盖索引
覆盖索引:SQL 只需要通过索引就可以返回查询所需要的数据,而不必通过二级索引查到主键之后再去查询数据。
7. group by
group by 使用索引的原则几乎跟 order by 一致 ,唯一区别是 group by 即使没有过滤条件用到索引,也可以直 接使用索引。