高性能MySQL(4):查询性能优化

一、怎么优化查询?思考题,下文有详细分析答案

并不是所有表都要优化,一般优化都是由架构师带着一两个人指导性地做,每周开个会系统性指导性地进行。

——优化数据访问

  • a、检查是否向数据库请求来了不需要的数据:比如不需要的记录、多表关联返回全部列、总是取出全部列、重复查询相同数据;
  • b、检查是否扫描了额外的记录:比如扫描的行数和返回的行数相差很大,可以优化一下(怎么优化,看下文);
  • c、使用索引

——重构查询方式

  • a、切分查询:大查询可以分割为几个小查询的思想,比如可用于删除大量旧数据;
  • b、分解关联查询:拆分数据库关联查询,对每个表进行一个单表查询,在代码中关联,有很多好处(什么好处,看下文)

——使用一些中间件

  • a、比如MyCat读写分离、分表分库插件(可查看读笔者续 的文章);
  • b、

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

查询性能最基本原因是hi访问数据太多,但对于低效的查询可以通过性下面两个步骤分析:

  • 是否向数据库请求了不需要的数据,意味着访问了太多的行或者太多的列;
  • MySQL是否扫描太多超过需要的行,扫描了额外的记录。

2.1、是否向数据库请求了不需要的数据 

(1)查询不需要的记录

误以为MySQL只会返回需要的数据,实际上MySQL是先返回全部结果集再进行计算。比如使用SELECT语句查询大量结果,然后获取前面N行后关闭结果集(比如查询1000条数据只在页面展示10条),最有效的方法是在查询后面加LIMIT

(2)多表关联时返回全部列

(3)总是取出全部列

SELECT * ,会让优化器无法完成索引覆盖扫描这类优化,还会带来额外性能开销。

(4)重复查询相同的数据

重复执行相同的查询,每次都返回完全相同的数据。例如:在用户评论的地方需要查询用户头像URL,那么用户多次评论时可能会反复查询这个URL数据;解决:初次查询的时候将这个数据缓存起来,需要时再从缓存中取。

2.2、MySQL是否在扫描额外的记录

在查询只返回需要的数据下,接下来考虑查询结果返回是否扫描了过多数据,衡量查询开销的三个指标,这三个指标都会记录到MySQL的慢日志中,所以检查慢日志记录是找出扫描行数过多的查询好办法:

  • 响应时间;
  • 扫描的行数;
  • 返回的行数。

(1)响应时间

响应时间指两部分之和:服务时间和排队时间。服务时间——数据库处理这个查询真正花了多长时间;排队时间——服务器因为等待某些资源而没有真正执行查询的时间(比如等I/O操作完、等行锁...),但是一般而言很难分别判断两者都是合在一起判断响应时间。

(2)扫描的行数和返回的行数

理想状态下扫描的行数和返回的行数应该是相同的,但实际上很难做到,让他们接近最好。

访问类型:

MySQL有好几种方式可以查找并返回一行结果,速度从慢到快:全表扫描、索引扫描、范围扫描、唯一索引查询、常数索引。如果找不到合适访问类型最好的办法就是增加一个合适的索引,索引让MySQL以最高效,扫描行数最少的方式找到需要的记录

如果发现查询需要扫描大量的数据但是只返回少数的行,可以这样优化:

a、使用索引覆盖扫描:把所有需要用到的列都放在索引中,这样存储引擎无需回表获取对应行就可以返回结果了;

b、改变库表结构:例如使用单独的汇总表;

c、重写这个复杂的查询:让MySQL优化器能够以更优化的方式执行这个查询。

三、重构查询方式

3.1、选择一个复杂查询还是多个简单查询

3.2、切分查询(DLETE)

有时候对于一个大查询可以切分为小查询,每个查询功能完全一样,只完成一小部分,每次只返回一小部分查询结果。

例子:删除旧数据、或者flag为0的假删除数据

如果 用一个大语句一次性完成可能需要一次性锁住很多数据,占用整个事物日志、占用系统资源、阻塞一些很小但很重要的查询,将一个大的DELETE切分,例如需要每个月运行一次下面语句:

如果每次删除数据后都歇一会再删除可以把原本一次性删除给服务器的压力分散到一个很长的时间中,可减少删除时锁的时间。

3.3、分解关联查询

就是对每一个表进行一次单表查询,然后将结果在代码应用程序中进行关联。例如:

(前提是这几个表都很大,并且整体很慢才有意义)

分解关联查询的好处:

a、让缓存的效率更高:MySQL的查询缓存中如果关联中的某个表发生变化,就无法使用查询缓存了;拆分后如果某个表很少变化,则基于该表的查询可以重复利用查询缓存结果;(例如上面例子:如果tag已经被缓存应用可以跳过第一个查询)

b、减少锁竞争:查询分解后执行单个查询可减少锁竞争;

c、提高扩展性:在代码层做关联,更容易对数据库拆分;

d、提高查询本身效率:如上例子使用IN()代替关联查询,可以让MySQL按照ID顺序进行查询,比随机关联更高效;

e、可以减少冗余记录的查询:代码层做关联某些记录只需要查询一次,数据库层做关联某些记录可能要查多次;

 

 

四、查询执行基础

MySQL执行一个查询的过程:

(1)协议:客户端发送一条查询给服务器;

(2)查询状态:服务器先检查缓存,如果命中了缓存,则立刻返回存储在缓存中的结果。否则进入下一个阶段;

(3)查询缓存:服务器端进行SQL解析、预处理,再由优化器生成对应的执行计划;

(4)优化器:MySQL根据优化器生成的执行计划,调用存储引擎的API来执行查询;

(5)将结果返回给客户端

4.1、客户端/服务端通信协议

4.2、查询状态

等待、查询、锁定、排序

4.3、查询缓存

解析一个查询语句之前,如果查询缓存打开则优先检查是否命中缓存,这个检查是通过一个对大小写敏感的哈希查找实现的。

4.4、优化查询处理

(1)语法解析器和预处理

MySQL通过关键字将SQL语句进行解析,并生成一颗对应的“解析树”。

(2)查询优化器

下面是MySQL能够处理的优化类型:

  • 重新定义关联表的顺序;
  • 将外连接转化为内连接;
  • 使用等价变换规则;
  • 优化COUNT()、MIN()、MAX();
  • 预估转化为常数表达式;
  • 覆盖索引扫描;
  • 子查询优化;
  • 提前终止查询;
  • 等值转播;
  • 列表IN()的比较。

4.5、查询执行引擎

4.6、将 结果返回给客户端

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

5.1、关联子查询

虽然IN()通常能提高效率,但是最糟糕的一类子查询却是WHERE条件中包含IN()的子查询。

子查询可能会提高性能也可能影响性能。

5.2、UNION的局限性

使用UNION无法将限制条件从外层“下推“”到内层,这使得原本能够限制部分返回结果的条件无法应用到内层查询优化上。

比如希望UNION的各个字句能够根据LIMIT只取部分结果集,或者先排序好再合并结果集,则每个UNION的表数据都放在临时表空间然后再取出条件的数据,但是从临时表空间取出来的数据是无序的,所以在外层还需要加一个ORDER BY和LIMIT操作

5.3、索引合并优化

WHERE字句包含多个复杂条件的时候,MySQL能够访问单个表的多个索引以合并和交叉过滤的方式来定位需要查找的行。

5.4、等值传递

5.5、并行执行

MySQL没有并行执行查询的功能

5.6、哈希关联

5.7、松散索引扫描

5.8、最大值和最小值优化

5.9、在同一个表上查询和更新

MySQL不允许同时在一张报表进行更新和查询

六、优化特定类型的查询

6.1、优化COUNT()查询

(1)COUNT ()聚合函数的作用

作用1:可以统计某个列值的函数

作用2:也可以统计行数

(2)简单的优化:

假设上面那个查询会扫描5000行数据,由于在MyISAM中COUNT()函数非常快,前提是没有任何WHERE条件的COUNT(*)才非常快,需要优化成只需要扫描5行数据:

(3)复杂的优化:

通常使用COUNT需要扫描大量的行,除了上面的简单优化,还可以使用索引覆盖扫描。

6.2、优化关联查询

(1)确保ON子句的列上有索引

创建索引时就要考虑关联的顺序,当表A和表B用c列关联的时候,如果优化器的关联顺序是B、A,那么就不需要在B表的c列上创建索引了,没有用到的索引会带来额外的负担。总的来说只需要在关联顺序中的第二个表的相应列创建索引。

(2)确保任何的GROUP BY 和ORDER BY中的表达式只涉及到一个表中的列,这样MySQL才能使用索引来优化这个过程;

6.3、优化GROUP BY

当无法使用索引时,GROUP BY使用两种策略来完成:使用临时表或者文件排序来做分组

优化GROUP BY WITH ROLLUP:

这是分组的一种变种——对返回的分组结果再做一次超级聚合最好不要使用它,可以把它实现的功能放在代码中实现

6.4、优化LIMIT分页

分页可以使用LIMIT+偏移量实现(LIMIT 偏移量,返回页数),同时加上合适的ORDER BY,如果有索引效率还可以没有则需要做大量文件排序操作。

但是当偏移量非常大时要优化:

(比如LIMIT 1000,20,则需要查询10020条记录但是只返回最后20条,前面10000条记录都被抛弃掉)

(1)使用索引覆盖扫描,而不是查询所有的列,然后根据需要做一次关联操作再返回所需的列

可改成:

6.5、优化UNION查询

MySQL总是通过创建并填充临时表的方式来执行UNION,上面也有提到过,所以很多优化策略在UNION都没得使用,经常需要手工地将WHERE、LIMIT、ORDER BY等子句“下推”到UNION的各个子查询中,以便里边优化器能更充分利用这些条件进行优化。

如何优化:

除非明确需要消除重复的行,否则一定要使用UNION ALL,如果没有ALL关键字,MySQL会给临时表加上DISTINCT选项,这会导致对整个临时表的数据做唯一性检查,很费时

七、查询优化案例学习

7.1、使用MySQL构建一个队列表

(1)背景:一个表包含多种类型的记录,高流量高并发情况下的,比如未处理、已处理、正在处理等,当多线程在表中查找未处理记录时,然后生成正在处理,在处理完后再将记录更新程已处理状态。比如邮件发送、多命令处理、评论修改等功能

(2)这种表设计不合理的两个原因

a、随着队列越来越大、索引深度正价,找到未处理记录的速度会随之变慢。(可以通过将队列表分为两部分解决,将已处理记录归档或者存放到历史表,这样可以保证队列很小);

b、一般的处理分为两步,是先找到未处理的记录、然后加锁,找到服务会增加服务器压力,而加锁会让各个消费者进程增加竞争;

(3)要解决的问题

如何让消费者标记正在处理的记录,而不至于让多个消费者重复处理一个记录

上一篇:https://blog.csdn.net/RuiKe1400360107/article/details/103783635

下一篇:https://blog.csdn.net/RuiKe1400360107/article/details/103963493

  参考资料:《高性能MySQL 第三版》

### 若对你有帮助的话,欢迎点赞!评论!+关注!

发布了52 篇原创文章 · 获赞 116 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/RuiKe1400360107/article/details/103963462