我们重点分析实现同样功能的不同SQL 语
句在性能方面会产生较大的差异的根本原因,并通过一个较为典型的示例来对我们的分析做出相应的验
证。

  为什么返回完全相同结果集的不同SQL 语句,在执行性能方面存在差异呢?这里我们先从SQL 语句在
数据库中执行并获取所需数据这个过程来做一个大概的分析了。

  当MySQL Server 的连接线程接收到Client 端发送过来的SQL 请求之后,会经过一系列的分解
Parse,进行相应的分析。然后,MySQL 会通过查询优化器模块(Optimizer)根据该SQL 所设涉及到的数
据表的相关统计信息进行计算分析,然后再得出一个MySQL 认为最合理最优化的数据访问方式,也就是
我们常说的“执行计划”,然后再根据所得到的执行计划通过调用存储引擎借口来获取相应数据。然后
再将存储引擎返回的数据进行相关处理,并以Client 端所要求的格式作为结果集返回给Client 端的应
用程序。
  注:这里所说的统计数据,是我们通过ANALYZE TABLE 命令通知MySQL 对表的相关数据做分析之后所
获得到的一些数据统计量。这些统计数据对MySQL 优化器而言是非常重要的,优化器所生成的执行计划
的好坏,主要就是由这些统计数据所决定的。实际上,在其他一些数据库管理软件中也有类似相应的统
计数据。

  我们都知道,在数据库管理软件中,最大的性能瓶颈就是在于磁盘IO,也就是数据的存取操作上
面。而对于同一份数据,当我们以不同方式去寻找其中的某一点内容的时候,所需要读取的数据量可能
会有天壤之别,所消耗的资源也自然是区别甚大。所以,当我们需要从数据库中查询某个数据的时候,
所消耗资源的多少主要就取决于数据库以一个什么样的数据读取方式来完成我们的查询请求,也就是取
决于SQL 语句的执行计划。

  对于唯一一个SQL 语句来说,经过MySQL Parse 之后分解的结构都是固定的,只要统计信息稳定,其
执行计划基本上都是比较固定的。而不同写法的SQL 语句,经过MySQL Parse 之后分解的结构结构就可能
完全不同,即使优化器使用完全一样的统计信息来进行优化,最后所得出的执行计划也可能完全不一
样。而执行计划又是决定一个SQL 语句最终的资源消耗量的主要因素。所以,实现功能完全一样的SQL 语
句,在性能上面可能会有差别巨大的性能消耗。当然,如果功能一样,而且经过MySQL 的优化器优化之
后的执行计划也完全一致的不同SQL 语句在资源消耗方面可能就相差很小了。当然这里所指的消耗主要
是IO 资源的消耗,并不包括CPU 的消耗。

  下面我们将通过一两个具体的示例来分析写法不一样而功能完全相同的两条SQL 的在性能方面的差
异。

示例一
需求:取出某个group(假设id 为100)下的用户编号(id),用户昵称(nick_name)、用户性别
( sexuality ) 、用户签名( sign ) 和用户生日( birthday ) , 并按照加入组的时间
(user_group.gmt_create)来进行倒序排列,取出前20 个。

解决方案一、
SELECT id,nick_name
FROM user,user_group
WHERE user_group.group_id = 1
and user_group.user_id = user.id
limit 100,20;
解决方案二、
SELECT user.id,user.nick_name
FROM (
SELECT user_id
FROM user_group
WHERE user_group.group_id = 1
ORDER BY gmt_create desc
limit 100,20) t,user
WHERE t.user_id = user.id;

我们先来看看执行计划:
sky@localhost : example 10:32:13> explain
-> SELECT id,nick_name
-> FROM user,user_group
-> WHERE user_group.group_id = 1
-> and user_group.user_id = user.id
-> ORDER BY user_group.gmt_create desc
-> limit 100,20\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: user_group
type: ref
possible_keys: user_group_uid_gid_ind,user_group_gid_ind
key: user_group_gid_ind
key_len: 4
ref: const
rows: 31156
Extra: Using where; Using filesort
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: user
type: eq_ref
possible_keys: PRIMARY
key: PRIMARY
key_len: 4
ref: example.user_group.user_id
rows: 1
Extra:
sky@localhost : example 10:32:20> explain
-> SELECT user.id,user.nick_name
-> FROM (
-> SELECT user_id
-> FROM user_group
-> WHERE user_group.group_id = 1
-> ORDER BY gmt_create desc
-> limit 100,20) t,user
-> WHERE t.user_id = user.id\G
*************************** 1. row ***************************
id: 1
select_type: PRIMARY
table: <derived2>
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 20
Extra:
*************************** 2. row ***************************
id: 1
select_type: PRIMARY
table: user
type: eq_ref
possible_keys: PRIMARY
key: PRIMARY
key_len: 4
ref: t.user_id
rows: 1
Extra:
*************************** 3. row ***************************
id: 2
select_type: DERIVED
table: user_group
type: ref
possible_keys: user_group_gid_ind
key: user_group_gid_ind
key_len: 4
ref: const
rows: 31156
Extra: Using filesort

执行计划对比分析:
  解决方案一中的执行计划显示MySQL 在对两个参与Join 的表都利用到了索引,user_group 表利用了
user_group_gid_ind 索引( key: user_group_gid_ind ) , user 表利用到了主键索引( key:
PRIMARY),在参与Join 前MySQL 通过Where 过滤后的结果集与user 表进行Join,最后通过排序取出
Join 后结果的“limit 100,20”条结果返回。
解决方案二的SQL 语句利用到了子查询,所以执行计划会稍微复杂一些,首先可以看到两个表都和
解决方案1 一样都利用到了索引(所使用的索引也完全一样),执行计划显示该子查询以user_group 为
驱动,也就是先通过user_group 进行过滤并马上进行这一论的结果集排序,也就取得了SQL 中的
“limit 100,20”条结果,然后与user 表进行Join,得到相应的数据。这里可能有人会怀疑在自查询中
从user_group表所取得与user 表参与Join的记录条数并不是20 条,而是整个group_id=1 的所有结果。
那么清大家看看该执行计划中的第一行,该行内容就充分说明了在外层查询中的所有的20 条记录全部被
返回。
  通过比较两个解决方案的执行计划,我们可以看到第一中解决方案中需要和user 表参与Join 的记录
数MySQL 通过统计数据估算出来是31156,也就是通过user_group 表返回的所有满足group_id=1 的记录
数(系统中的实际数据是20000)。而第二种解决方案的执行计划中,user 表参与Join 的数据就只有20
条,两者相差很大,通过本节最初的分析,我们认为第二中解决方案应该明显优于第一种解决方案

  下面我们通过对比两个解决觉方案的SQL 实际执行的profile 详细信息,来验证我们上面的判断。由
于SQL 语句执行所消耗的最大两部分资源就是IO和CPU,所以这里为了节约篇幅,仅列出BLOCK IO 和CPU
两项profile 信息(Query Profiler 的详细介绍将在后面章节中独立介绍):
  先打开profiling 功能,然后分别执行两个解决方案的SQL 语句:

sky@localhost : example 10:46:43> set profiling = 1;
Query OK, 0 rows affected (0.00 sec)
sky@localhost : example 10:46:50> SELECT id,nick_name
-> FROM user,user_group
-> WHERE user_group.group_id = 1
-> and user_group.user_id = user.id
-> ORDER BY user_group.gmt_create desc
-> limit 100,20;
+--------+-----------+
| id | nick_name |
+--------+-----------+
| 990101 | 990101 |
| 990102 | 990102 |
| 990103 | 990103 |
| 990104 | 990104 |
| 990105 | 990105 |
| 990106 | 990106 |
| 990107 | 990107 |
| 990108 | 990108 |
| 990109 | 990109 |
| 990110 | 990110 |
| 990111 | 990111 |
| 990112 | 990112 |
| 990113 | 990113 |
| 990114 | 990114 |
| 990115 | 990115 |
| 990116 | 990116 |
| 990117 | 990117 |
| 990118 | 990118 |
| 990119 | 990119 |
| 990120 | 990120 |
+--------+-----------+
20 rows in set (1.02 sec)
sky@localhost : example 10:46:58> SELECT user.id,user.nick_name
-> FROM (
-> SELECT user_id
-> FROM user_group
-> WHERE user_group.group_id = 1
-> ORDER BY gmt_create desc
-> limit 100,20) t,user
-> WHERE t.user_id = user.id;
+--------+-----------+
| id | nick_name |
+--------+-----------+
| 990101 | 990101 |
| 990102 | 990102 |
| 990103 | 990103 |
| 990104 | 990104 |
| 990105 | 990105 |
| 990106 | 990106 |
| 990107 | 990107 |
| 990108 | 990108 |
| 990109 | 990109 |
| 990110 | 990110 |
| 990111 | 990111 |
| 990112 | 990112 |
| 990113 | 990113 |
| 990114 | 990114 |
| 990115 | 990115 |
| 990116 | 990116 |
| 990117 | 990117 |
| 990118 | 990118 |
| 990119 | 990119 |
| 990120 | 990120 |
+--------+-----------+
20 rows in set (0.96 sec)
查看系统中的profile 信息,刚刚执行的两个SQL 语句的执行profile 信息已经记录下来了:

sky@localhost : example 10:47:07> show profiles\G
*************************** 1. row ***************************
Query_ID: 1
Duration: 1.02367600
Query: SELECT id,nick_name
FROM user,user_group
WHERE user_group.group_id = 1
and user_group.user_id = user.id
ORDER BY user_group.gmt_create desc
limit 100,20
*************************** 2. row ***************************
Query_ID: 2
Duration: 0.96327800
Query: SELECT user.id,user.nick_name
FROM (
SELECT user_id
FROM user_group
WHERE user_group.group_id = 1
ORDER BY gmt_create desc
limit 100,20) t,user
WHERE t.user_id = user.id
2 rows in set (0.00 sec)
sky@localhost : example 10:47:34> SHOW profile CPU,BLOCK IO io FOR query 1;

+--------------------+----------+-----------+------------+--------------+---------------+
| Status | Duration | CPU_user | CPU_system | Block_ops_in | Block_ops_out |
+--------------------+----------+-----------+------------+--------------+---------------+
| (initialization) | 0.000068 | 0 | 0 | 0 | 0 |
| Opening tables | 0.000015 | 0 | 0 | 0 | 0 |
| System lock | 0.000006 | 0 | 0 | 0 | 0 |
| Table lock | 0.000009 | 0 | 0 | 0 | 0 |
| init | 0.000026 | 0 | 0 | 0 | 0 |
| optimizing | 0.000014 | 0 | 0 | 0 | 0 |
| statistics | 0.000068 | 0 | 0 | 0 | 0 |
| preparing | 0.000019 | 0 | 0 | 0 | 0 |
| executing | 0.000004 | 0 | 0 | 0 | 0 |
| Sorting result | 1.03614 | 0.5600349 | 0.428027 | 0 | 15632 |
| Sending data | 0.071047 | 0 | 0.004 | 88 | 0 |
| end | 0.000012 | 0 | 0 | 0 | 0 |
| query end | 0.000006 | 0 | 0 | 0 | 0 |
| freeing items | 0.000012 | 0 | 0 | 0 | 0 |
| closing tables | 0.000007 | 0 | 0 | 0 | 0 |
| logging slow query | 0.000003 | 0 | 0 | 0 | 0 |
+--------------------+----------+-----------+------------+--------------+---------------+
16 rows in set (0.00 sec)

sky@localhost : example 10:47:40> SHOW profile CPU,BLOCK IO io FOR query 2;
+--------------------+----------+----------+------------+--------------+---------------+
| Status | Duration | CPU_user | CPU_system | Block_ops_in | Block_ops_out |
+--------------------+----------+----------+------------+--------------+---------------+
| (initialization) | 0.000087 | 0 | 0 | 0 | 0 |
| Opening tables | 0.000018 | 0 | 0 | 0 | 0 |
| System lock | 0.000007 | 0 | 0 | 0 | 0 |
| Table lock | 0.000059 | 0 | 0 | 0 | 0 |
| optimizing | 0.00001 | 0 | 0 | 0 | 0 |
| statistics | 0.000068 | 0 | 0 | 0 | 0 |
| preparing | 0.000017 | 0 | 0 | 0 | 0 |
| executing | 0.000004 | 0 | 0 | 0 | 0 |
| Sorting result | 0.928184 | 0.572035 | 0.352022 | 0 | 32 |
| Sending data | 0.000112 | 0 | 0 | 0 | 0 |
| init | 0.000025 | 0 | 0 | 0 | 0 |
| optimizing | 0.000012 | 0 | 0 | 0 | 0 |
| statistics | 0.000025 | 0 | 0 | 0 | 0 |
| preparing | 0.000013 | 0 | 0 | 0 | 0 |
| executing | 0.000004 | 0 | 0 | 0 | 0 |
| Sending data | 0.000241 | 0 | 0 | 0 | 0 |
| end | 0.000005 | 0 | 0 | 0 | 0 |
| query end | 0.000006 | 0 | 0 | 0 | 0 |
| freeing items | 0.000015 | 0 | 0 | 0 | 0 |
| closing tables | 0.000004 | 0 | 0 | 0 | 0 |
| removing tmp table | 0.000019 | 0 | 0 | 0 | 0 |
| closing tables | 0.000005 | 0 | 0 | 0 | 0 |
| logging slow query | 0.000004 | 0 | 0 | 0 | 0 |
+--------------------+----------+----------+------------+--------------+---------------+

  我们先看看两条SQL 执行中的IO 消耗,两者区别就在于“Sorting result”,我们回
顾一下前面执行计划的对比,两个解决方案的排序过滤数据的时机不一样,排序后需要取
得的数据量一个是20000,一个是20,正好和这里的profile 信息吻合,第一种解决方案的
“Sorting result”的IO 值是第二种解决方案的将近500 倍。
然后再来看看CPU 消耗,所有消耗中,消耗最大的也是“Sorting result”这一项,第
一个消耗多出的缘由和上面IO 消耗差异是一样的。
结论:
  通过上面两条功能完全相同的SQL 语句的执行计划分析,以及通过实际执行后的
profile 数据的验证,都证明了第二种解决方案优于第一种解决方案。同时通过后者的实际
验证,也再次证明了我们前面所做的执行计划基本决定了SQL 语句性能。

6.3 Query 语句对系统性能的影响的更多相关文章

  1. Query语句对系统性能的影响

    需求: 取出某个group(假设id为1)下的用户编号id,用户昵称(nick_name),并按照加入组的时间(user_group.gmt_create)来进行倒序排列,取出前20个 解决方案一: ...

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

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

  3. query语句的拼接.

    在外网服务器上的网页,不能排序了,原因是query语句拼接时,sort没有能拼上,. 小小的思路: 1.是不是网页请求出了问题: 和我本机上的的请求一一对比,并没有发现什么问题. 2.代码除了问题吗: ...

  4. 在Eclipse中用图形界面的方式获取Salesforce中Object的Query语句

    对Salesforce中的Object进行相应的Query是必不可少的操作,大家可以去这个链接去看看官网的解读  http://docs.database.com/dbcom/en-us/db_sos ...

  5. 获取Sqlserver上一句语句执行后受影响的行数@@rowCount

    from:http://blog.163.com/rihui_7/blog/static/212285143201381343240404/ 返回受上一语句影响的行数. ROWCOUNT_BIG.&q ...

  6. 【转】如何应用Query语句进行规则的语法设置?

    在Altium Designer中, 设计规则通常用来定义用户的设计需求. 这些规则涵盖了设计的方方面面, 从布线宽度, 对象的安全间距,内电层的连接风格, 过孔风格等等. 设计规则不仅能在PCB设计 ...

  7. SQLite中7(8)形参的query语句的用法

    SQLite中7(8)形参的query语句的用法 我们先来看看这种7形参的query语句的形参列表: public Cursor query(String table, String[] column ...

  8. Log4J对系统性能的影响

    Log4J对系统性能的影响 主要体现: 1.日志输出的目的地,输出到控制台的速度比输出到文件系统的速度要慢. 2.日志输出格式不一样对性能也会有影响,如简单输出布局(SimpleLayout)比格式化 ...

  9. 吞吐量(Throughput)、QPS、并发数、响应时间(RT)对系统性能的影响

    首先对吞吐量().QPS.并发数.响应时间(RT)几个概念一直比较模糊,也不知道哪些指标可以较好的衡量系统的性能.今天特意查了些资料做一些记录:首先看一些概念(来自百度百科) 1. 响应时间(RT) ...

随机推荐

  1. shell编程——if语句

    if 语句格式 if  条件 then  Command else  Command fi                              别忘了这个结尾 If语句忘了结尾fi test.s ...

  2. Windows远程连接的实现

          实验室有一台电脑,寝室里也有一台电脑,很多时候,事情还没有做完就不得不离开实验室,所以,在寝室里远程控制实验室的电脑是一件很有"意义"的事,其实,Windows系统已经 ...

  3. Docker教程:Docker镜像导出及迁移

    http://blog.csdn.net/pipisorry/article/details/51330126 Docker目录分析 安装docker时,默认的安装位置是/var/lib/docker ...

  4. Google Guava的5个鲜为人知的特性

    译文出处: 花名有孚   原文出处:takipi.com Google Guava有哪些比较冷门但却又实用的特性呢? 它是最流行的开源库之一,你应该听过它的大名,它诞生的地方正是人们举办真正的魁地奇比 ...

  5. HTML5 Web Storage 特性

    原文地址: Using HTML5 Web Storage 原文日期: 2010年06月28日 翻译日期: 2013年08月12日 当下Web开发领域最火爆的词语当属 HTML5.HTML5标准的新特 ...

  6. SpringMVC注解控制器详解

    主要包括四类:@Component.@Repository @Service.@Controller 说明: @Controller 控制层,就是我们的action层 @Service 业务逻辑层,就 ...

  7. aidl使用采坑记

    什么是AIDL? AIDL是 Android Interface definition language的缩写,它是一种Android内部进程通信接口的描述语言,通过它我们可以定义进程间的通信接口 A ...

  8. andrid面试题

    下面的题目都是楼主在android交流群大家面试时遇到的,如果大家有好的题目或者好的见解欢迎分享,楼主将长期维护此帖. 某公司高级面试题(2015-03-14)[感谢helder分享] 1.详述And ...

  9. unity描边效果

    这里总结了几种在unity实现描边效果的方法,首先准备一个模型导入在unity中,使用默认shader,上传一张原始图,以便后面实现功能效果的对比 一.边缘光,这里参照官方的一个SurfaceShad ...

  10. 集群通信组件tribes之使用方法

    上面已经对tribes的内部实现机制及原理进行了深入的剖析,在理解它的设计原理后看看如何使用tribes,整个使用相当简单便捷,只需要四步: ① 定义一个消息对象,由于这个消息对象是要在网络之间传递的 ...