- 重构查询方式
- 有的时候,将一个大查询分解为多个小查询是很有必要的。
3.1 切片查询
- 删除旧的数据就是一个很好的例子。定期地清楚大量数据时,如果用一个大的语句一次性完成的话,则可能需要一次锁住很多数据、占满整个事务日志、耗尽系统资源、阻塞很多小的但很重要的查询。将一个大的delete语句切分成多个较小的查询可以尽可能小地影响MySQL性能。
定时删除1W条数据,然后停留一段时间再次删除1W条
3.2 分解关联查询
-
用分解关联查询的方式重构查询有如下的优势:
- 让缓存的效率更高。
- 将查询分解后,执行单个查询可以减少锁的竞争。
- 在应用层做关联,可以更容易对数据库进行拆分,更容易做到高性能和可扩展。
- 查询本身效率也可能会有所提升。
- 可以减少冗余记录的查询,在应用层做关联查询,意味着对于某条记录应用只需要查询一次,而在数据库中做关联查询,则可能需要重复地访问一部分数据。从这点看,这样的重构还可能会减少网络和内存的消耗。
- 更进一步,这样做相当于在应用中实现了哈希关联,而不是使用MySQL的嵌套循环关联。某些场景哈希关联的效率要高很多。
- 查询执行的基础
-
MySQL执行过程
-
- 客户端发送一条查询给服务器。
- 服务器先检查查询缓存,如果命中了缓存,则立刻返回存储在缓存中的结果。否则进入下一个阶段。
- 服务器端进行SQL解析、预处理,再由优化器生成对应的执行计划。
- MySQL根据优化器生成的执行计划,调用存储引擎的API来执行查询。
- 将结果返回给客户端。
4.1 mysql客户端/服务器通信协议
-
查询状态
show full processlist
- sleep:线程正在等待客户端发送新的请求。
- query:线程正在执行查询或者正在将结果发送给客户端。
- locked:在MySQL服务器层,该线程正在等待表锁。在存储引擎级别实现的锁,例如InnoDB的行锁,并不会体现在线程状态中。对于myisam来说这是一个比较典型的状态,但在其他没有行锁的引擎中也经常会出现。
- analyzing and statistics:线程正在收集存储引擎的统计信息,并生成查询的执行计划。
- coping to tmp table [on disk]:线程正在执行查询,并且将其结果集都复制到一个临时表中,这种状态一般要么是在做group by操作,要么是文件排序操作,或者是union操作。如果这个状态后面还有on disk标记,那表示MySQL正在将一个内存临时表放到磁盘上。
- sorting result:线程正在对结果集进行排序。
- sending data:这表示多种情况,线程可能在多个状态之间传送数据,或者在生成结果集,或者在向客户端返回数据。
-
MySQL客户端和服务器之间的通讯协议是“半双工”的,这意味着,在任何一个时刻,要么是由服务器向客户端发送数据,要么是由客户端向服务器发送数据,这两个动作不能同时发生。
4.2 查询缓存
- 在解析一个查询语句之前,如果查询缓存是打开的,那么mysql会优先检查这个查询是否命中查询缓存中的数据。这个检查是通过一个对大小写敏感的哈希查找实现的。查询和缓存中的查询即使只有一个字节不同,那也不会匹配缓存结果,这种情况下查询就会进入下一阶段的处理。如果当前的查询恰好命中了查询缓存,那么在返回查询结果之前MySQL会检查一次用户权限。这仍然是无须解析查询SQL语句的,因为在查询缓存中已经存放了当前查询需要访问的表信息。如果权限没有问题,mysql会跳过所有其他阶段,直接从缓存中拿到结果并返回给客户端。这种情况下,查询不会被解析,不用生成执行计划,不会被执行。
- mysql通过关键字将SQL进行解析,并生成一棵对应的“解析树”。mysql解析器将使用mysql语法规则验证和解析查询。例如,它将验证是否使用错误的关键字,或者使用关键字的顺序是否正确等,再或者他还会验证引号是否能前后正确匹配。
- 预处理器则根据一些mysql规则进一步检查解析树是否合法,例如,这里将检查数据表和数据列是否存在,还会解析名字和别名,看看它们是否有歧义。
4.3 查询优化处理
-
语法解析器(生成解析树)
- MySQL通过关键字将SQL语句进行解析,生成解析树。
- MySQL解析器使用MySQL语法规则验证和解析查询。
- 验证是否使用错误的关键字,或者使用关键字的顺序是否正确,再或者验证引号是否能前后正确匹配。
-
预处理器(生成优化后的解析树)
- 根据MySQL的规则进一步检查解析树是否合法。
- 检查数据表和数据列是都存在,还会解析名字和别名是否有歧义。
- 验证权限。
-
优化器
-
优化器作用是将合法的语法树(解析树)转换成执行计划。
-
MySQL使用基于成本的优化器,,它将尝试预测一个查询使用某种执行计划时的成本,并选择其中成本最小的一个。
-
导致优化器选择错误的执行计划原因有下:
- 统计信息不准确。MySQL依赖存储引擎提供的统计信息来评估成本,但是有的存储引擎提供的信息是准确的,有的偏差可能非常大。
- 执行计划中的成本估算不等同于实际执行的成本。
-
优化器使用不同的优化策略进行优化,优化策略简单分为:静态优化和动态优化。
-
优化类型:
- 重新定义关联表的顺序。
- 将外连接转换成内连接。
- 使用等价变换规则。
- 优化COUNT()、MIN()、MAX()。
- 预估并转化为常数表达式。
- 覆盖索引扫描。
- 子查询优化。
- 提前终止查询。
- 等值传播。
- 列表IN()的比较:在很多数据库系统中,IN()完全等同于多个OR条件的子句,因为这两者是完全等价的。在MySQL中这点是不成立的,MySQL将IN()列表中的数据先进行排序,然后通过二分法查找的方式确定列表中的值是否满足条件,这是一个O(logn)复杂度的操作,等价地转换成OR查询的复杂度O(n),对于IN()列表中有大量取值的时候,MySQL的处理速度将会更快。
-
-
数据和索引的统计信息
MySQL架构由多个层次组成。在服务器层有查询优化器,却没有保存数据和索引的统计信息。统计信息由存储引擎实现,不同的存储引擎可能会存储不同的统计信息。
因为服务器层没有任何统计信息,所以MySQL查询优化器在生成查询的执行计划时,需要向存储引擎获取相应的统计信息,存储引擎则提供优化器对应的统计信息,包括:每个表或者索引有多少个页面,每个表的每个索引的基数是多少,数据行和索引长度、索引的分布信息等。优化器根据这些信息来选择一个最优的执行计划。
- MySQL如何执行关联查询
MySQL对任何关联都执行嵌套循环关联操作,即MySQL先在一个表中循环取出单条数据,然后再嵌套循环到下一个表中寻找匹配的行,依次下去,直到找到所有表中匹配的行为止。然后根据各个表匹配的行,返回查询中需要的各个列。MySQL会尝试在最后一个关联表中找到所有匹配的行,如果最后一个关联表无法找到更多的行以后,MySQL返回上一层次关联表,看是否能够找到更多的匹配记录。
- 执行计划
MySQL生成查询的一棵指令树,然后通过存储引擎执行完成这课指令树并返回结果。最终的执行计划包含了重构查询的全部信息。
- 排序优化
当不能使用索引生成排序结果的时候,MySQL需要自己进行排序,如果数据量小则在内存中进行,如果数据量大则需要使用磁盘,不过MySQL将这个过程统一称为文件排序,即使完全是内存排序不需要任何磁盘文件时也是如此。
4.4 查询执行引擎
MySQL在优化阶段就为每个表创建了一个handler实例,优化器根据这些实例的接口可以获取表的相关信息,包括表的所有列名、索引统计信息等。
并不是所有的操作都由handler完成,如果是所有存储引擎共有的特性则由服务器层实现,比如时间和日期函数、视图、触发器等。
5 MySQL查询优化器的局限性
5.1 关联子查询
5.2 UNION的限制
5.3 索引合并优化
5.4 等值传递
5.5 并行执行
MySQL无法利用多核特性来并行执行查询。
5.6 松散索引扫描
MySQL并不支持松散索引扫描(5.0之后,部分查询可以,但不够完善;5.6之后的版本,关于松散索引扫描的一些限制将会通过索引条件下推的方式解决),也就无法按照不连续的方式扫描一个索引。通常,MySQL的索引扫描需要先定义一个起点和终点,即使需要的数据只是这段索引中很少数的几个,MySQL仍需要扫描这段索引中每一个条目。
5.9 在同一个表上查询和更新
MySQL不允许对同一张表同时进行查询和更新,可以通过使用生成表的形式来绕过上面的限制。
6 查询优化器的提示
暂时不必看,优先级下移
7 优化特定类型的查询
7.1 优化COUNT
的作用
COUNT()
是一个特殊的函数,有两种非常不同的作用:它可以统计某个列值的数量,也可以统计行数。在统计列值时要求列值是非空的(不统计null)。如果在COUNT()的括号中指定了列或者列的表达式,则统计的就是这个表达式有值的结果数。
myisam的count()函数总是非常快,不过这是有前提条件的,即只有没有任何where条件的count()才非常快,因此此时无须实际地去计算表的行数。myisam可以利用存储引擎的特性直接获取这个值。如果MySQL知道某列col不可能为null值,那么MySQL内部会将count(col)表达式优化为count()。除此之外,myisam的count()和其他存储引擎没有任何不同。
可以利用myisam的count()的速度优化其他查询,比如count()-id<=5的记录数,就可以得到id>5的记录数等操作。
问题:如何在同一个查询中统计同一个列不同值的数量,以减少查询的语句量。例如:假设可能需要通过一个查询返回各种不同颜色的商品数量?
# 方法1:
SELECT SUM(IF(color = 'blue',1,0)) AS blue,SUM(IF(color = 'red',1,0)) AS red FROM items;
# 方法2:
SELECT COUNT(color = 'blue' OR NULL) AS blue,COUNT(color = 'red' OR NULL) AS red FROM items;
7.2 优化关联查询
- 确保ON或者USING子句中的列上有索引。在创建索引的时候就要考虑到关联的顺序。当表A和表B用列c关联的时候,如果优化器的关联顺序是B、A,那么就不需要在B表的对应列上建上索引。没有用到的索引只会带来额外的负担。一般来说,除非有其他理由,否则只需要在关联顺序中的第二个表的相应列上创建索引。
- 确保任何的GROUP BY 和 ORDER BY 中的表达式只涉及到一个表中的列,这样MySQL才有可能使用索引来优化这个过程。
7.3 优化 GROUP BY 和 DISTINCT
- GROUP BY使用两种策略来完成:使用临时表或者文件排序来做分组。
- 如果没有 通过ORDER BY子句显式地指定排序列,当查询使用GROUP BY 子句的时候,结果集会自动按照分组的字段进行排序。如果不关心结果集的顺序,而这种默认排序又导致了需要文件排序,则可以使用ORDER BU NULL,让MySQL不再进行文件排序。也可以在GROUP BY子句中直接使用DESC或者ASC关键字,使分组的结果集按需要的方向排序。
7.5 优化LIMIT
分页
- 要优化limit相关查询,要么是在页面中限制分页数量,要么是优化大偏移量的性能,优化此类分页查询的一个简单的办法就是尽可能地使用索引覆盖扫描。
8 案例学习
8.1 总结
- 优化通常都需要三管齐下:不做、少做、快速地做。