MySQL的索引优化与查询优化:
目录
索引优化:
总结:
关联查询优化:
join语句的原理:
简单嵌套循环连接:
索引嵌套循环连接:
块嵌套循环链接:
总结:
Hash join:
子查询优化:
分页查询优化:
limit1对优化的影响:
覆盖查询:
索引下推:
select对查询性能的影响:
物理查询优化:通过索引和表连接方式等技术来进行优化。
逻辑查询优化:通过SQL等价变化提升查询效率,也就是换一种查询写法来提升效率。
索引优化:
如果查询语句中where搜索条件涉及的单个字段没有索引或者涉及多个字段,可以创建相对应的索引来进行优化。
联合索引遵循最佳左前缀法则,即最左边的索引优先,检索时从联合索引的最左边开始匹配,查询中要使用联合索引要注意如果一旦跳过某个字段的索引那么该联合索引中该字段的索引后的索引无法使用,要按照联合索引的创建顺序依次满足。
如果插入的主键不是有序的,可能会出现该数据页的数据记录刚好满,但是继续插入数据记录,如果是有序的主键值,那么就插入到下一个数据页,但是如果不是有序的,那么需要把当前页面分裂成两个页面,并且移动数据,会造成性能有损耗。所以可以给主键加上auto_increment保证主键是有序的。
如果查询中where条件中的字段使用了计算、函数或者类型转换会导致该字段的索引失效。索引应该尽量避免对字段使用计算、函数或者类型转换。
对于查询where中的范围查询,在联合索引中,使用了范围查询的索引右边的索引将会失效。
select * from 表名 where 字段1 = 值 and 字段2(范围查询) and 字段3 = 值;
与
sleect * from 表名 where 字段1 = 值 and 字段3 = 值 and 字段2(范围查询);联合索引:(字段1,字段2,字段3);
其中字段2使用了范围查询,而联合索引中的索引顺序是字段1,字段2,字段3。所以会导致字段3的索引失效。需要注意的是在查询语句中像上述一样改变查询语句中的顺序不会影响索引使用的个数。
为了避免上述的情况,创建联合索引时可以先创建等值索引再创建范围查询的索引。
使用不等于比较运算符、is not null或者not like可能会导致索引失效,因为如果数据量太大,优化器会强制全表扫描。
like开头通配符可能会导致多因失效,因为前面的内容无法确定,需要进行全表扫描之后进行比对。
在where子句中,如果or前后的列有一个字段没有索引,那么就会导致索引失效,只有or前后的列都有索引才会使用索引。因为or的含义是满足一个即可,是对查询结果取并集,所以没有索引的字段依然要进行查询,就会进行全表扫描。
不同的字符集比较之前可能会导致索引失效,因为在比较之前会进行转换。索引数据库和表的字符集可以统一使用,避免由于字符集转换产生的乱码。
总结:
对于单列索引,尽量选择针对当前查询过滤性更好的索引。
在选择组合索引时,当前查询中过滤性最好的字段在索引字段顺序中,位置越靠前越好。
在选择组合索引时,尽量选择能够包含当前查询中where子句中更多字段的索引。
在选择组合索引时,如果某个字段可能出现范围查询时,尽量把这个字段放在索引的最后面。
关联查询优化:
驱动表(Driving Table):在多表连接查询中,驱动表是首先被访问的表。数据库会读取驱动表中的所有记录,作为基础数据用于匹配其他表。
被驱动表(Driven Table):基于驱动表的记录进行匹配的表。数据库会根据连接条件(如 ON 子句),用驱动表的每条记录去被驱动表中查找对应数据。
外连接:
如果是左(右)外连接,只能添加一个字段索引的话,给被驱动表连接关系中的字段添加索引。
内连接:
与外连接不同的是因为获取两个表的交集,所以优化器可能会对驱动表和被驱动表进行交换。如果只能添加一个索引的话,有索引的字段会被作为驱动表,都存在索引的话,结果集较小的表作为驱动表。
join语句的原理:
join连接多个表本质上就是各个表之间数据的循环匹配。
简单嵌套循环连接:
从驱动表A取出一条数据,遍历非驱动表B。
每取一条数据,都需要遍历一次被驱动表B。其表中的记录都会被加载到内存中再与驱动表A中取出的数据进行匹配,匹配结束后清除内存,这样会增加IO次数。
索引嵌套循环连接:
主要是减少了内层表数据的匹配次数,所以被驱动表B加上索引,通过外层匹配条件直接与内层表索引进行匹配,避免了内层表进行全表扫描去进行比较,减少了内层表的匹配次数。
如果不是主键索引是需要进行回表操作,如果连接条件中的字段是主键,那么效率会更高。
块嵌套循环链接:
为了减少被驱动表的IO次数,每一次从驱动表B中一块一块获取数据,将其放入join buffer缓冲区中,全表扫描被驱动表,将其与join buffer中的数据进行匹配(内存中操作),将简单嵌套循环中驱动表数据与被驱动表数据比较的次数减少,降低了被驱动表的访问次数。
需要注意的是不只是缓存关联表的列,slelect后面的列也会缓存起来。
在一个n个join关联中,sql会分配n-1个join buffer,所以查询时尽量减少不必要的字段,可以让join buffer中存放更多的列。
总结:
整体效率是索引嵌套循环好于块嵌套循环好于简单嵌套循环。
用小的结果集驱动大的结果集,为了减少外层循环的数据数量。
为被驱动表连接的字段加上索引,减少内层循环的匹配次数。
增大join buffer size的大小,可以一次性缓存更多的数据,减少内层扫描次数。
Hash join:
适用于大数据集,优化器使用两个表中相对较小的表利用join key在内存中建立散列表,然后扫描较大的表并探测散列表,找出与Hash表匹配的行。能够很好地工作于没有索引的大表和并行查询,并且提供最好的性能。只能应用于等值连接。
当较小的表能够完全放入内存中,那么总成本就是访问两个表的成本之和。
如果表不能完全放入内存中,那么优化器会将表分割成若干不同的分区,不能放入内存的部分就把该分区写入磁盘的临时段,此时要求有较大的临时段来尽量提高I/O性能。
子查询优化:
在MySQL中,支持两种排序方式:index排序,索引可以保证数据的有序性,不需要再进行排序。FileSort排序,一般在内存中进行排序,占用的CPU较多,如果待排结果较大,会产生临时文件IO到磁盘进行排序的情况,效率较低。所以在where条件字段上加上索引还要再order by字段上加上索引。
SQL中,可以在where和order by中使用索引,目的是在where中避免全表扫描,在order by中避免Filesort排序。
尽量使用index完成order by排序,如果where和order by后面是相同的字段就是用单索引列,如果不是则使用联合索引。
无法使用index是,需要对Filesort进行调优,单路排序比双路排序效果更好。
当where过滤掉字段之后,剩余的数据量很小时,可能会导致order by中的索引失效,因为数据量小,优化器会走全表扫描。
order by中对多个字段进行排序,如果不是一致的排序方式,索引会失效。
尽量减少使用order by,能不排序尽量不排序。
GROUP BY优化:
group by即使没有过滤条件,也可以直接使用索引。
当无法使用索引时,可以增大max_length_for_sort_data避免双路排序和sort_buffer_size减少IO次数。
where的效率比having更高,能写在where中尽量写在where中。
分页查询优化:
一般分页查询能够通过创建覆盖索引比较好的提高性能。
在索引上完成排序分页,最后根据主键索引关联回表查询所需的数据。
如果主键是自增的话,可以把limit查询转换成某个位置的查询。
limit1对优化的影响:
如果是针对的全表扫描,且可以确定结果集只有一条,那么加上limit1会加快查询速度,因为当找到一条结果时就不会继续再扫描。
如果数据表对字段建立了唯一索引,加上limit1页没什么影响,因为通过索引进行查询,不会进行全表扫描。
覆盖查询:
非聚簇复合索引的一种形式,包括在查询里的select、join和where子句中用到的所有列,即创建索引的字段正好是覆盖查询条件中所涉及的字段,也就是一个索引列包含了满足查询结果的数据,不需要进行回表操作。
简单说就是索引列+主键包含了select到from之间查询的列。
可以避免InnoDB表进行回表,因为二级索引的叶子节点保存了该列的索引和主键,找到相应的主键时,还需要进行回表操作获取数据。在覆盖索引中,二级索引的叶子节点上包含了查询的字段,那么就不需要进行回表操作,减少了IO操作,提升了查询效率。
可以把随机IO变成顺序IO加快查询效率。因为覆盖索引是按键值的顺序存储的,对于IO密集型的范围查询,对比随即从磁盘读取每一行的数据IO要少很多,是因为如果要满足查询的结果通过二级索引找到对应的主键很多,都需要进行回表操作的话,聚簇索引中叶子节点存放了完整的数据,而数据存放在数据页中,可能查找到的数据所在的数据页距离很远,造成随机IO,而随机IO的效率是比顺序IO的效率更低的。而通过覆盖索引,在二级索引的叶子节点中找到的字段包括了查询的字段,既不用回表操作,而且因为是按照键值的顺序存储的,所以是一个顺序IO。
覆盖索引可以减少树的搜索次数,显著提升查询性能,所以覆盖索引是一个常用的性能优化手段。但是需要注意的是索引的维护是需要成本的,需要考虑冗余索引的影响。
前缀索引可能会导致覆盖索引失效,因为前缀索引是包含字符串前一部分作为索引,而不是一个完整的值,如果需要获取完整的值是需要进行回表操作的,所以导致覆盖索引失效。
索引下推:
一种存储引擎使用索引过滤数据的方式。
为了通俗易懂,这里使用一个例子来讲解:
select 字段 from 表名 where 字段1的条件 and 字段2的条件;
联合索引:(字段1,字段2)
如果此时字段1能够使用索引,而字段2无法使用索引。如果没有使用索引下推,那么整体流程应该是通过字段1的索引在二级索引中找到相对应的主键值之后,进行回表操作后取出对应的完整数据之后,进行对字段2进行判断筛选数据,如果字段1筛选出的结果集很大,那么进行回表操作时,所需要的成本就较大。而如果使用索引下推,那么在经过字段1在二级索引筛选出的结果集中,不马上进行回表操作,而是继续判断字段2进行筛选减少结果集,此时再进行回表操作,就能减少回表的成本。
所以索引下推可以减少存储引擎必须访问基表的次数和MySQL服务器必须访问存储引擎的次数,但是加速效果取决于存储引擎内通过索引下筛选掉的数据比例。
索引下推默认是开启的,但是可以通过对系统变量optimizer_switch控制来开启关闭索引下推。
set optimizer_switch = 'index_condition_pushdown=off';//关闭索引下推
set optimizer_switch = 'index_condition_pushdown=on';//打开索引下推
select对查询性能的影响:
在表查询中,尽量明确查询的字段,不适用select * from 表名,这样的语法。因为MySQL解析过程中,会通过查询数据字典将*号转换为所有的列名,会大大耗费资源和时间,而且还会导致覆盖索引失效。