MySQL查询性能优化

1、MySQL查询执行基础mysql

1. MySQL查询执行流程原理算法

 

<1> 客户端发送一条查询给服务器。sql

<2> 服务器先检查查询缓存,若是命中了缓存,则马上返回存储在缓存中的结果。不然进入下一阶段。缓存

<3> 服务器进行SQL解析、预处理,再由优化器生成对应的执行计划。性能优化

<4> MySQL根据优化器生成的执行计划,调用存储引擎的API来执行查询。服务器

<5> MySQL将结果返回给客户端,同时保存一份到查询缓存中。函数

2. MySQL客户端/服务器通讯协议性能

<1> 协议类型:半双工。优化

<2> Mysql一般须要等全部的数据都已经发送给客户端才能释放这条查询所占用的资源。spa

<3> PHP函数中,mysql_query()会将整个查询的结果集缓存到内存中,而mysql_unbuffered_query()则不会缓存结果,直接从mysql服务器获取结果。当结果集很大时,使用后者能减小内存的消耗,但服务器的资源会被这个查询占用比较长的时间。

3. 查询状态

  可使用命令来查询mysql当前查询的状态:show full processlist。返回结果中的“State”键对应的值就表示查询的状态,主要有如下几种:

<1> Sleep:线程正在等待客户端发送新的请求。

<2> Query:线程正在执行查询或正在将结果发送给客户端。

<3> Locked:在MySQL服务器层,该线程正在等待表锁。(在没行锁的引擎出现)

<4> Analyzing and statistics:线程正在收集存储引擎的统计信息,并生成查询的执行计划。

<5> Copying to tmp [on disk]:线程正在执行查询,而且将其结果集都复制到一个临时表中,这种状态要么是在group by操做,要么是文件排序操做,或者是union操做。

<6> Sorting result:线程正在对结果集进行排序。

<7> Sending data:表示多种请况,线程可能在多个状态之间传送数据,或者在生成结果集,或者在向客户端返回数据。

4. 查询缓存

<1> 这个检查是经过一个对大小写敏感的哈希查找实现的。

<2> 命中查询缓存以后,检查用户权限,直接从缓存中返回数据给客户端,不须要解析查询。

5. 查询优化处理

语法解析器和预处理:

<1> 经过关键字对SQL语句进行解析,生成一棵“解析树”。

<2> 解析器使用MySQL语法规则验证和解析查询(关键字是否正确...)。

<3> 预处理器根据一些MySQL规则进一步检查解析树是否合法(表、列是否存在...)。

<4> 预处理器验证权限。

查询优化器:

  MySQL使用基于成本的优化器。它将尝试预测一个查询使用各类执行计划时的成本,并选择其中成本最小的一个。其中,成本是根据存储引擎提供的数据和引擎的统计信息计算得来的,能够经过查询当前会话的Last_query_cost值来得知MySQL计算的当前查询的成本。优化器在评估成本的时候并不考虑任何层面的缓存,它假设读取任何数据都需要一次磁盘I/O。有不少缘由会致使MySQL优化器选择并非最优的执行计划。MySQL能处理的优化类型:从新定义关联表的顺序、将外链接转化成内链接、使用等价变换规则、优化count()min()max()、预估并转化为常数表达式、覆盖索引扫描、子查询优化、提早终止查询、等值传播、列表in()的比较等等。

数据和索引的统计信息:

  统计信息由存储引擎实现。Mysql查询优化器在生成查询的执行计划时须要向存储引擎获取相应的统计信息。

MySQL如何执行关联查询:
<1> MySQL认为任何一个查询都是一次关联,对任何关联都执行嵌套循环关联操做,从一个表开始一直嵌套循环、回溯完成全部表关联。

<2> Union查询:先将一系列的单个查询结果放到一个临时表中,而后再从新读出临时表数据来完成union查询。(临时表没有任何索引)

<3> MySQL在执行子查询的时候也是先将子查询的结果放到一个临时表中。

<4> 遇到右链接的时候mysql会将其改写成等价的左链接。

<5> 将全部的查询类型都转换成相似的执行计划。。

<6> 并非全部的查询均可以经过嵌套循环和回溯的方式完成,例如全外链接,因此Mysql并不支持全外链接。

执行计划:

  对某个查询执行explain extended后再执行show warnings就能够看到重构出的查询。由于mysql执行查询采用的老是嵌套循环关联操做,因此mysql的执行计划老是一棵左侧深度优先的树。

关联查询优化器

  MySQL优化器最重要的一部分就是关联查询优化。关联优化器经过评估多个表的不一样关联顺序的成原本选择一个代价最小的关联顺序,若是可能,优化器会遍历每个表而后逐个作嵌套循环计算每一棵可能的执行计划树的成本,最后返回最优的一个执行计划。可是当关联表的数量比较多的时候,这样作的成本过高,当须要关联的表数量超过optimizer_search_depth参数值的时候,优化器会选择使用“贪婪”搜索的方式查找“最优”关联顺序。有时候优化器给出的不是最优关联顺序,这时若是不但愿关联优化器改变表的关联顺序的话,可使用straight_join来强制表的链接顺序。

排序优化:

<1> 排序是一个成本很高的操做,应尽量避免排序操做。

<2> Mysql的两种排序算法:

  两次传输排序(旧版本使用):读取行指针和须要排序的字段,对其进行排序,而后再根据排序结果读取所须要的数据行。

  单次传输排序(新版本使用):先读取查询所须要的全部列,而后在根据给定列的值进行排序,最后直接返回结果。

  两个算法各有优缺点,当查询须要全部列的总长度不超过参数max_length_for_sort的值时,mysql使用单次传输排序。

<3> 若是关联查询须要排序,MySQL会分两种状况来处理文件排序:

  若是order by子句中的全部列都来自关联的第一个表,那么MySQL在关联处理第一个表的时候就进行文件排序(Explain结果Extra字段“Using filesort”),不然,MySQL都会先将关联的结果存放到一个临时表中,而后在全部的关联都结束后再进行文件排序(Explain结果Extra字段“Using temporary;Using filesort”)。

 

2、MySQL查询优化器的局限性

1. 关联子查询

  MySQL的子查询实现很是糟糕,最糟糕的一类查询是where条件中包含IN()的子查询语句。MySQL会将相关的外层表压到子查询中进行关联查询。

包含In的子查询优化:

<1> 使用group_concat()in()中构造一个由逗号分隔的列表。

<2> 改写成关联查询或使用exists代替。

2. Union的限制

  有时,MySQL没法将限制条件从外层“下推”到内层,使得本来可以限制部分返回结果的条件没法应用到内层查询的优化上。在union的各个子句中分别使用order bylimit,能够减小临时表中的数据,但想获取正确的顺序还需加上一个全局的order bylimit操做。

3. 索引合并优化

  在5.0和更新的版本中,当where子句中包含多个复杂条件的时候,mysql可以访问单个表的多个索引以合并和交叉过滤的方式来定位须要查找的行。

4. 等值传递

  Mysql优化器会将In()列表复制应用到关联的各个表中。

5. 并行执行

  Mysql没法利用多核特性来并行执行查询。

6. 哈希关联

  能够经过建立一个哈希索引来曲线实现哈希关联。

7. 松散索引扫描

  Mysql并不支持松散索引扫描。

8. 最大值和最小值优化

  在须要取最大/最小值的字段上建立索引,而后在查询语句中加入“use index”语句强制使用索引,当MySQL读到第一条知足条件的记录的时候就是咱们须要找的最大/最小值了。

优化示例:

优化前:

 

优化后:

9. 在同一张表上查询和更新

  MySQL不容许对同一张表同时进行查询和更新。能够经过使用生成表的形式来绕过这个限制,由于MySQL只会把这个表当成临时表来处理。

优化示例:

优化前:

 

优化后:

 

10. 查询优化器的提示

  能够在查询语句中加入一些提示来控制查询的执行计划。

 

3、优化特定类型的查询

1. 优化count()查询

  若是在count()的括号中指定了列或者列的表达式,那么统计的是这个表达式有值的结果数(不包含NULL)。统计行数使用count(*)意义更清晰,性能也会更好。

  MyISAM执行没有任何where条件的count(*)很是快,由于能够利用存储引擎的特性直接得到这个值。若是mysql知道某个col不可能为null值会将count(col)表达式优化为count(*)能够利用MyISAM的这个特性来优化加速一些特定条件的count()查询。优化示例:

优化前(须要扫描不少的数据行):

 

优化后(将须要扫描的数据行减小到5之内):

 

(在查询优化阶段会将其中的子查询直接当作一个常数来处理)

  在对精确值要求不高的状况下,能够经过一些途径取得近似值来达到优化查询的目的:

<1> 使用explain出来的优化器估算的行数来替代count(*),不须要真正去执行查询。

<2> 去除一些对总数影响很小的where条件。

<3> 删除distinct约束避免文件排序。

<4> 更复杂的优化:使用汇总表、使用缓存系统等。

2. 优化关联查询
<1> 确保onusing子句中的列上有索引,只须要在关联顺序中的第二个表的相应列上建立索引。

<2> 确保任何的group byorder by中的表达式只涉及到一个表中的列,这样MySQL才有可能使用索引来优化这个过程。

<3> 当升级MySQL的时候须要注意,关联语法、运算符优先级等其余可能会发生变化的地方。

3. 优化子查询

  尽可能使用关联查询替代(并不绝对,若是使用的是MySQL5.6或更新版本或MariaDB的话)。

4. 优化group bydistinct

  优化这两种查询最有效的方法是使用索引来优化。    

  当没法使用索引的时候,group by使用临时表或文件排序来作分组,能够经过提示SQL_BIG_RESULTSQL_SMALL_RESULT来让优化器按照咱们但愿的方式运行。若是须要对关联查询作分组,一般采用查找表的标识列分组的效率会比其余列更高。但若是查询语句没法写成在select中直接使用非分组列的形式或当前sql_mode禁止这样作的话(ONLY_FULL_GROUP_BYsql_mode会返回错误),可使用min()max()函数来绕过这种限制。

  若是没有经过order by子句显式指定排序列,当查询使用group by子句的时候结果集会自动按照分组的字段进行排序,可以在其后面直接使用ascdesc关键字指定排序方向。若不须要进行排序,能够加上order by nullMySQL不在进行文件排序,提升查询性能。

5. 优化group by width rollup

  使用超级聚合的查询不够优化,能够在from子句中嵌套使用子查询来替代超级聚合,或者是经过一个临时表存放中间数据,然后和临时表执行union操做来获得最终结果。但最好的办法仍是尽量将width rollup功能转移到应用程序中进行处理。

6. 优化limit分页

  在使用limit子句的查询中,若是没有对应字段的索引,当偏移量很大的时候,mysql须要查询大量数据行可是只返回一小部分数据,对这种状况进行优化的方法有:

<1> 使用索引覆盖扫描,而后再作一次关联操做返回所需的列。

<2> 想办法将limit查询转化为已知位置的查询。

7. 优化union查询

  Mysql老是经过建立并填充临时表的方式来执行union查询,常常须要手工将wherelimitorder by等子句“下推”到union的各个子查询中进行优化。除非确实须要服务器消除重复的行,不然必定要使用union all,否则mysql会给临时表加distinct选项,这会致使对整个临时表的数据作惟一性检查。

8.优化select *查询

  当使用select * from tbl_name语句进行查询的时候,mysql服务器会先从数据表中解析出所有字段名称,替换掉查询语句中的"*",而后缓存解析替换以后的查询语句,最后再将解析替换以后的查询语句进行执行,而且之后遇到select * from tbl_name语句都会直接使用缓存中的包含数据表所有字段的语句进行查询。因此使用"*"而不指定字段名称有如下弊端:

(1)会增长SQL的解析成本;

(2)若是不是所有字段都有用的话,查询非必需字段还会形成资源浪费甚至影响服务器性能;

(3)没法利用索引覆盖查询,不利于查询的性能优化;

(4)如果数据表结构修改以后还使用缓存中的语句进行查询,还会发生字段映射错误问题。

9. 使用用户自定义变量优化查询

  使用用户自定义变量的查询没法使用查询缓存,生命周期只在一个链接中有效,是一个动态类型,赋值符号:=优先级很低,赋值表达式应该使用括号。

优化排名语句:

 

(演过最多电影的前10名演员,使用子查询生成一个中间的临时表来解决自定义变量赋值时间和咱们预料不一样的问题

避免重复查询刚刚更新的数据:

 

统计更新和插入的数量:

 

编写偷懒的union

  第一个子查询做为分支条件先执行,若是找到了匹配的行,则跳过第二个分支。

 

(将“热”数据和“冷”数据分别放在两个不一样的表,使用union去查询)

  有时优化器会把变量看成一个编译时常量对待而不是对其进行赋值,将函数放在相似least()这样的函数中一般能够避免这样的问题。