mysql检索所有数据舒_MySQL 数据库 Query 的优化详解

理解MySQL的Query Optimizer

MySQL Optimizer是一个专门负责优化SELECT 语句的优化器模块,它主要的功能就是通过计算分析系统中收集的各种统计信息,为客户端请求的Query 给出他认为最优的执行计划,也就是他认为最优的数据检索方式。

Query 语句优化基本思路和原则

在分析如何优化MySQL Query 之前,我们需要先了解一下Query 语句优化的基本思路和原则。一般来说,Query 语句的优化思路和原则主要体现在以下几个方面:

1. 优化更需要优化的Query;

2. 定位优化对象的性能瓶颈;

3. 明确的优化目标;

4. 从Explain 入手;

5. 多使用profile

6. 永远用小结果集驱动大的结果集;

7. 尽可能在索引中完成排序;

8. 只取出自己需要的Columns;

9. 仅仅使用最有效的过滤条件;

10. 尽可能避免复杂的Join 和子查询;

上面所列的几点信息,前面4 点可以理解为Query优化的一个基本思路,后面部分则是我们优化中的基本原则。

下面我们先针对Query 优化的基本思路做一些简单的分析,理解为什么我们的Query 优化到底该如何进行。

优化更需要优化的Query

为什么我们需要优化更需要优化的Query?这个地球人都知道的“并不能成为问题的问题”我想就并不需要我过多解释吧,哈哈。

那什么样的Query 是更需要优化呢?对于这个问题我们需要从对整个系统的影响来考虑。什么Query 的优化能给系统整体带来更大的收益,就更需要优化。一般来说,高并发低消耗(相对)的Query 对整个系统的影响远比低并发高消耗的Query 大。我们可以通过以下一个非常简单的案例分析来充分说明问题。

假设有一个Query 每小时执行10000 次,每次需要20 个IO。另外一个Query 每小时执行10 次,每次需要20000 个IO。我们先通过IO 消耗方面来分析。可以看出,两个Query 每小时所消耗的IO 总数目是一样的,都是200000 IO/小时。假设我们优化第一个Query,从20 个IO 降低到18 个IO,也就是仅仅降低了2 个IO,则我们节省了2 * 10000 = 20000 (IO/小时)。而如果希望通过优化第二个Query 达到相同的效果,我们必须要让每个Query 减少20000 / 10 = 2000 IO。我想大家都会相信让第一个Query 节省2 个IO远比第二个Query 节省2000 个IO 来的容易。

其次,如果通过CPU 方面消耗的比较,原理和上面的完全一样。只要让第一个Query 稍微节省一小块资源,就可以让整个系统节省出一大块资源,尤其是在排序,分组这些对CPU 消耗比较多的操作中尤其突出。

最后,我们从对整个系统的影响来分析。一个频繁执行的高并发Query 的危险性比一个低并发的Query 要大很多。当一个低并发的Query 走错执行计划,所带来的影响主要只是该Query 的请求者的体验会变差,对整体系统的影响并不会特别的突出,之少还属于可控范围。但是,如果我们一个高并发的Query 走错了执行计划,那所带来的后果很可能就是灾难性的,很多时候可能连自救的机会都不给你就会让整个系统Crash 掉。曾经我就遇到这样一个案例,系统中一个并发度较高的Query 语句走错执行计划,系统顷刻间Crash,甚至我都还没有反应过来是怎么回事。当重新启动数据库提供服务后,系统负载立刻直线飙升,甚至都来不及登录数据库查看当时有哪些Active 的线程在执行哪些Query。如果是遇到一个并发并不太高的Query 走错执行计划,至少我们还可以控制整个系统不至于系统被直接压跨,甚至连问题根源都难以抓到。

总体来说就是优先优化并发高的query,高并发的query一定要想办法优化到最优。

定位优化对象的性能瓶颈

当我们拿到一条需要优化的Query 之后,第一件事情是什么?是反问自己,这条Query 有什么问题?我为什么要优化他?只有明白了这些问题,我们才知道我们需要做什么,才能够找到问题的关键。而不能就只是觉得某个Query 好像有点慢,需要优化一下,然后就开始一个一个优化方法去轮番尝试。这样很可能整个优化过程会消耗大量的人力和时间成本,甚至可能到最后还是得不到一个好的优化结果。这就像看病一样,医生必须要清楚的知道我们病的根源才能对症下药。如果只是知道我们什么地方不舒服,然后就开始通过各种药物尝试治疗,那这样所带来的后果可能就非常严重了。

所以,在拿到一条需要优化的Query 之后,我们首先要判断出这个Query 的瓶颈到底是IO 还是CPU。到底是因为在数据访问消耗了太多的时间,还是在数据的运算(如分组排序等)方面花费了太多资源?

明确的优化目标

当我们定为到了一条Query 的性能瓶颈之后,就需要通过分析该Query 所完成的功能和Query 对系统的整体影响制订出一个明确的优化目标。

如何设定优化目标?一般来说,我们首先需要清楚的了解数据库目前的整体状态,同时也要清楚的知道数据库中与该Query 相关的数据库对象的各种信息,而且还要了解该Query 在整个应用系统中所实现的功能。了解了数据库整体状态,我们就能知道数据库所能承受的最大压力,也就清楚了我们能够接受的最悲观情况。把握了该Query 相关数据库对象的信息,我们就应该知道实现该Query 的消耗最理想情况下需要消耗多少资源,最糟糕又需要消耗多少资源。最后,通过该Query 所实现的功能点在整个应用系统中的重要地位,我们可以大概的分析出该Query 可以占用的系统资源比例,而且我们也能够知道该Query 的效率给客户带来的体验影响到底有多大。

当我们清楚了这些信息之后,我们基本可以得出该Query 应该满足的一个性能范围是怎样的,这也就是我们的优化目标范围,然后就是通过寻找相应的优化手段来解决问题了。如果该Query 实现的应用系统功能比较重要,我们就必须让目标更偏向于理想值一些,即使在其他某些方面作出一些让步与牺牲,比如调整schema 设计,调整索引组成等,可能都是需要的。而如果该Query 所实现的是一些并不是太关键的功能,那我们可以让目标更偏向悲观值一些,而尽量保证其他更重要的Query 的性能。这种时候,即使需要调整商业需求,减少功能实现,也不得不应该作出让步。

从Explain 入手

为什么从Explain 入手?因为只有Explain 才能告诉你,这个Query 在数据库中是以一个什么样的执行计划来实现的。

永远用小结果集驱动大的结果集

很多人喜欢在优化SQL 的时候说用小表驱动大表,个人认为这样的说法不太严谨。为什么?因为大表经过WHERE 条件过滤之后所返回的结果集并不一定就比小表所返回的结果集大,可能反而更小。

在这种情况下如果仍然采用小表驱动大表,就会得到相反的性能效果。其实这样的结果也非常容易理解,在MySQL 中的Join,只有Nested Loop 一种Join 方式,也就是MySQL 的Join 都是通过嵌套循环来实现的。驱动结果集越大,所需要循环的此时就越多,那么被驱动表的访问次数自然也就越多,而每次访问被驱动表,即使需要的逻辑IO 很少,循环次数多了,总量自然也不可能很小,而且每次循环都不能避免的需要消耗CPU ,所以CPU 运算量也会跟着增加。所以,如果我们仅仅以表的大小来作为驱动表的判断依据,假若小表过滤后所剩下的结果集比大表多很多,结果就是需要的嵌套循环中带来更多的循环次数,反之,所需要的循环次数就会更少,总体IO 量和CPU 运算量也会少。而且,就算是非Nested Loop 的Join 算法,如Oracle 中的Hash Join,同样是小结果集驱动大的结果集是最优的选择。

所以,在优化Join Query 的时候,最基本的原则就是“小结果集驱动大结果集”,通过这个原则来减少嵌套循环中的循环次数,达到减少IO 总量以及CPU 运算的次数。

尽可能在索引中完成排序

只取出自己需要的Columns

仅仅使用最有效的过滤条件

很多人在优化Query 语句的时候很容易进入一个误区,那就是觉得WHERE 子句中的过滤条件越多越好,实际上这并不是一个非常正确的选择。

为什么说过滤条件多不一定是好事呢?请看下面示例:

需求: 查找某个用户在所有group 中所发的讨论message 基本信息。

场景:

1、知道用户ID 和用户nick_name

2、信息所在表为group_message

3、group_message 中存在用户ID(user_id)和nick_name(author)两个索引

方案一:将用户ID 和用户nick_name 两者都作为过滤条件放在WHERE 子句中来查询,Query 的执行计划如下:

sky@localhost : example 11:29:37> EXPLAIN SELECT * FROMgroup_message-> WHERE user_id = 1 AND author='1111111111'\G*************************** 1. row ***************************id:1select_type: SIMPLEtable: group_message

type: ref

possible_keys: group_message_author_ind,group_message_uid_indkey: group_message_author_ind

key_len:98ref: const

rows:1Extra: Usingwhere

1 row in set (0.00 sec)

方案二:仅仅将用户ID 作为过滤条件放在WHERE 子句中来查询,Query 的执行计划如下:

sky@localhost : example 11:30:45> EXPLAIN SELECT * FROMgroup_message-> WHERE user_id = 1\G*************************** 1. row ***************************id:1select_type: SIMPLEtable: group_message

type: ref

possible_keys: group_message_uid_indkey: group_message_uid_ind

key_len:4ref: const

rows:1Extra:1 row in set (0.00 sec)

方案三:仅将用户nick_name 作为过滤条件放在WHERE 子句中来查询,Query 的执行计划如下:

sky@localhost : example 11:38:45> EXPLAIN SELECT * FROMgroup_message-> WHERE author = '1111111111'\G*************************** 1. row ***************************id:1select_type: SIMPLEtable: group_message

type: ref

possible_keys: group_message_author_indkey: group_message_author_ind

key_len:98ref: const

rows:1Extra: Usingwhere

1 row in set (0.00 sec)

初略一看三个执行计划好像都挺好的啊,每一个Query 的执行类型都利用到了索引,而且都是“re

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值