Mysql查询性能优化

Mysql查询性能优化

慢查询优化基础:优化数据访问

  • 查询须要的记录。查询100条,应用层仅须要10条。
  • 多表关联时返回所有列。*,多表关联,字段查询要加前缀。
  • 老是取出所有列。*
  • 重复查询相同的数据。例如:在用户评论的地方须要查询用户头像URL,那么用户屡次评论的时候,可能就会反复查询这个数据。比较好的方案,当初次查询的时候将这个数据缓存起来,须要的时候从缓存中取出,这样性能显然会更好。

重构查询方式

切分查询

  • 将大查询切分红小查询,每一个查询功能彻底同样,只完成一小部分,每次只返回一小部分查询结果。若是一次性完成的话,则可能须要一次锁住不少数据、占满整个事务日志、耗尽系统资源、阻塞不少小的但重要的查询。

分解关联查询

  • 不少高性能的应用都会对关联查询进行分解。
  • 简单地,能够对每个表进行一次单表查询,而后将结果在应用程序中进行关联。
select * from tag join tag_post on tag_post.id = tag.id join post on post.id = tag_post.id where tag.tag = 'msyql'; 分解为: select * from tag from where tag = 'msyql'; select * from tag_post where id = 1234; select * from post where id in (1,2,3); 
优点
  • 让缓存的效率更高。许多应用程序能够方便地缓存单表查询对应的结果对象。例如:上面查询中的tag已经被缓存了,那么应用就能够跳过第一个查询。再例如,应用中已经缓存了ID为1,2的内容,那么第三个查询的in()中就能够少了几个ID,对MYSQL的查询缓存来讲,若是关联中的某个表发生了变化,那么久没法使用查询缓存了,而拆分后,若是某个表不多改变,那么基于该表的查询就能够重复利用查询缓存结果了。
  • 将查询分解后,执行单个查询就能够减小锁的竞争。
  • 在应用层作关联,能够更容易对数据库进行拆分,更容易作到高性能和高扩展。
  • 查询自己效率也可能会有所提高。使用IN()代替关联查询,可让MYSQL按照ID顺序进行查询,这可能比随机的关联要更搞笑。
  • 能够减小冗余记录的查询。在应用层作关联查询,意味着对于某条记录应用只须要查询一次,而在数据库中作关联查询,则可能须要重复地访问一部分数据。从这点看,这样的重构还可能会减小网络和内存的消耗。
  • 更进一步,这样作至关于在应用中实现了哈希关联,而不是使用MYSQL的潜逃循环关联。某些场景哈希关联的效率要高不少。
  • 在不少场景下,经过重构查询将关联放到应用程序中将会更加高效,这样的场景有不少,好比:当应用可以方便地缓存单个查询的结果的时候,当能够将数据分布到不一样的MYSQL服务器上的时候,当可以使用IN的方式代替关联查询的时候、当查询中使用同一个数据表的时候。

查询执行基础

MYSQL接收到请求都作了什么?

  1. 客户端发送一条查询给服务器。
  2. 服务器先检查查询缓存,若是命中了缓存,则马上返回存储在缓存中的结果。不然进入下一阶段。
  3. 服务器进行SQL解析、预处理,再由优化器生成对应的执行计划。
  4. MYSQL根据优化器生成的执行计划,调用存储引擎的API来执行查询。
  5. 将结果返回给客户端。

MYSQLk客户端/服务端通讯协议mysql

MYSQL客户端和服务端之间的通讯协议是“半双工”的,这意味着,在任何一个时刻,要么是由服务器向客户端发送数据,要么是由客户端向服务器发送数据,这两个动做不能同时发生。一旦一端开始发送消息,另外一端要接收完整个消息才能响应它。这就像来回抛球的游戏:在任什么时候刻,只能一我的控制球,并且只能空值求得人才能将球抛回去。
客户端用一个单独的数据包将数据传给服务器,这也是为何当查询的语句很长的时候,参数mac_allow_package就特别重要了。一旦客户端发送了请求,它能作的事情就只能是等待结果了。
相反的,通常服务器响应给用户的数据一般不少,由多个数据包组成。当服务器开始响应客户端请求时,客户端必须完整地接收整个返回结果,而不能简单地只取前面几条结果,而后让服务器中止发送数据。这种状况下,客户端若接收完成的结果,而后取前面几条须要的结果,或者接完几条结果后就“粗暴”地断开链接,都不是好主意。这也是在必要的时候必定要在查询中加上LIMIT限制的缘由。

查询状态

对于一个MYSQL链接,或者说一个线程,任什么时候刻都有一个状态,该状态表示了MYSQL当前正在作什么。有不少方式能查询当前状态,最简单的是使用show full processlist命令。一个查询的生命周期中,状态会变化不少次。
  • Sleep
    • 线程正在等待客户端发送新的请求。
  • Query
    • 线程正在执行查询或者正在将结果发送给客户端。
  • Locked
    • 在MYSQL服务器层,该线程正在等待表锁。在存储引擎级别实现的锁。例如:Innodb的行锁,并不会体如今线程状态中。对于MyISAM来讲这是一个比较典型的状态,但在其余没有行锁的引擎中也常常出现。
  • Analyzing and statistics
    • 线程正在收集存储引擎的统计信息,并生成查询执行计划。
  • Copying to tmp table [on disk]
    • 线程正在执行查询,而且将其结果集都复制到一个临时表中,这种状态通常要么在作Group By操做,要么是文件排序操做,或者是UNION操做。若是这个状态后面还有“on disk”标记,那表示MYSQL正在讲一个内存临时表放到磁盘上。
  • Sorting result
    • 线程正在对结果集进行排序。
  • Sending data
    • 这表示多种状况:线程可能在多个状态之间传送数据,或者在生成结果集,或者在客户端返回数据。

了解这些状态的基本含义很是有用,这可让你很好地了解当前“谁正在持球”。在一个繁忙的服务器上,可能会看到大量的不正常状态,例如statistics正占用大量的时间。这一般表示,某个地方有异常了。sql

查询优化

MYSQL如何执行关联查询

对于UNION查询,MYSQL先将一系列的单个查询结果放到一个临时表中,而后再从新读取临时表数据来完成UNION查询。

在MYSQL的概念中,每一个查询都是一次关联,因此读取结果临时表也是一次关联。数据库

当前MYSQL关联执行的策略很简单:MYSQL对任何关联都执行嵌套循环关联操做,即MYSQL先在一个表中循环取出单条数据,而后再嵌套循环到下一个表中寻找匹配的行,依次下去,知道找到全部表中匹配的行为止。而后根据各个表匹配的行,返回查询中须要的各个列。MYSQL会尝试在最后一个关联表中找到全部匹配的行,若是最后一个关联表没法找到更多的行之后,MYSQL返回到上一层次关联表,看是否可以找到更多匹配记录,一次类推迭代执行。缓存

简单的内链接查询: select tab1.col1, tab2.col2 from tab1 inner join tab2 using(col3) where tab1.col1 in (1,2); 实际执行的伪代码表示: outer_iter = iterator over tabl1 where col1 in (1,2) outer_row = outer_iter.next while outer_row inner_iter = iterator over tab2 where col3 = outer_row.col3 inner_row = inner_iter.next while inner_row output [ outer_row.col1, inner_row.col2] inner_row = inner_iter.next end outer_row = outer_iter.next end 
简单的外链接查询: select tab1.col1, tab2.col2 from tab1 outer join tab2 using(col3) where tab1.col1 in (1,2); 实际执行的伪代码表示: outer_iter = iterator over tabl1 where col1 in (1,2) outer_row = outer_iter.next while outer_row inner_iter = iterator over tab2 where col3 = outer_row.col3 inner_row = inner_iter.next if inner_row while inner_row output [ outer_row.col1, inner_row.col2] inner_row = inner_iter.next end else output [ outer_row.col, null ] end outer_row = outer_iter.next end 

MYSQL的临时表是没有任何索引的,在编写复杂的子查询和关联查询的时候须要注意这一点。这一点对UNION查询也是同样的。性能优化

关联子查询

MYSQL的子查询实现得很是糟糕。最糟糕的一类查询是where条件中包含IN()的子查询语句。
select * from tab1 where col1 in ( select col2 from tab2 where col3 = 1; ) 
MYSQL对IN()列表中的 选项有专门的优化策略,通常会认为MYSQL会先执行子查询返回全部包含col3为1的col2。通常来讲,IN()列查询速度很快,因此咱们会认为上面的查询会这样执行:
- SELECT GROUP_CONCAT(col2) from tab2 where col3 = 1; - Reuslt : 1,2,3,4, select * from tabl1 where col1 in (1,2,3,4); 
很不幸,MYSQL不是这样作的。MYSQL会将相关的外层表压到子查询中,它认为这样能够更高效率地查找到数据行。也就是说,MYSQL会将查询改为下面的这样:
select * from tab1 where exists ( select * from tab2 where col3 = 1 and tab1.col1 = tab2.col1 ); 
这时,子查询须要根据col1来关联外部表的film,由于须要到col1字段,因此MYSQL认为没法先执行这个子查询。
若是tab1表数据量小,性能还不是很糟糕,若是是一个很是大的表,那这个查询性能会很是糟糕。改写这个子查询
select * from tab1 inner join tab2 using(col1) where col3 = 1; && select * from tab1 where exists ( select * from tab2 where col3 = 1 and tab1.col1 = tab2.col1 ); 
一旦使用了DISTINCT和GROUP by,在查询执行的过程当中,一般产生临时中间表。可使用EXISTS子查询优化

UNION的限制

经过将两个表查询结果集合并取前20条
(select *from tab1 order by col1) union all (select * from tab2 order by col2) limit 20; 优化为: (select *from tab1 order by col1 limit 20) union all (select * from tab2 order by limit 20) 

UNION 临时表的数据会大大减小服务器

 

优化COUNT()查询

Count()是一个特殊的函数,有两种很是不一样的做用:它能够统计某个列的数量,也能够统计行数。在统计列值时要求列值是非空的(不统计NULL)。若是在COUNT()的括号中指定了列或者列的表达式,则统计的就是这个表达式有值的结果数。
Count()的另一个做用是统计结果集的行数。当MYSQL肯定括号内的表达式值不可能为空时,实际上就是在统计行数。最简单的就是COUNT(*)。
简单的优化
select count(*) from tab1 where col >5; 优化为: select (select count(*) from tab1 ) - count(*) from tab1 where col <5; 扫描的数量会减小不少 子查询也会当成常数,使用expand可知 
情景:在同一个查询中统计一个列的不一样值的数量,以减小查询的语句量
select sum(if(color = blue), 1, 0) as blue , sum(if(color = red), 1, 0) as red from items ; 一样也可使用Count 

优化关联查询

  • 确保ON或者USING子句中的列有索引。
  • 确保任何group by和order by只涉及到一个表中的列。

优化LIMIT分页

select col1, col2 from tab1 order by col3 limit 50,5; 改写成: select col1, col2 from tab1 inner join ( select col1 from tab1 order by col3 limit 50,5 ) as lim using(col1); 
  • 这里的“延迟关联”将大大提高查询效率,它让MYSQL扫描尽量少的页面,获取须要访问的记录后再根据关联列回原表查询须要的全部列。这个技术能够优化LIMIT查询。网络

  • 有时候也能够将LIMIT查询转换为已知位置的查询,让MYSQL经过范围扫描得到到对应的结果。函数

select col1, col2 from tab1 where col1 between 30 and 50; select col1, col2 from tab1 where col1 < 500 order by col1 limit 20;