表连接查询与where后使用子查询的性能分析。

子查询就是在一条查询语句中还有其它的查询语句,主查询得到的结果依赖于子查询的结果。

子查询的子语句可以在一条sql语句的FROM,JOIN,和WHERE后面,本文主要针对在WHERE后面使用子查询与表连接查询的性能做出一点分析。

对于表连接查询和子查询性能的讨论众说纷纭,普遍认为的是表连接查询的性能要高于子查询。本文将从实验的角度,对这两种查询的性能做出验证,并就实验结果分析两种查询手段的执行流程对性能的影响。

首先准备两张表

1,访问日志表mm_log有150829条记录(相关sql文件已放在文章结尾的链接中)。

2,用户表mm_member有373条记录(相关sql文件已放在文章结尾的链接中)。

现在要求我们根据这两张表查出2017-02-06那天有那些用户登录过系统。

我们先来看一下使用表连接查询

SELECT
    SQL_NO_CACHE mm.*
FROM
    mm_member mm
JOIN
    mm_log ml
ON
    mm.id = ml.member_id
WHERE
    ml.access_time LIKE ‘%2017-02-06%‘
GROUP BY
    ml.member_id;

这里使用了 SQL_NO_CACHE 是因为要多次执行这条sql语句,并计算出这条sql查询所耗费的平均时间,所以要关掉mysql的查询缓存,防止多次执行从缓存中读取数据。

mm.*是取GROUP BY ml.member_id分组后的诸多临时表的第一行数据,相关用法及原理请参见我的另一篇博客(http://www.cnblogs.com/cdf-opensource-007/p/6502556.html)

对这条sql语句执行了10次,查询所耗费的平均时间在0.120s左右。

查询结果:(一共有5个用户访问过系统)

至于以上这条sql的执行流程已经在前几篇博客中描述的很详细了,这里就不再做叙述了。

下面使用WHERE后使用子查询的方式实现

SELECT
    SQL_NO_CACHE mm.username
FROM
    mm_member mm
WHERE
    mm.id IN(SELECT ml.member_id FROM mm_log ml WHERE ml.access_time LIKE ‘%2017-02-06%‘ GROUP BY ml.member_id);

当我第一次运行这条sql语句的时候,等了十几秒一直没有结果,我以为我的电脑死机,可Navicat显示处理中,最后40多秒的时候才运行出结果,接连运行了好多次在都是41秒左右出结果。

我们看到执行结果同上。那么使用子查询的性能到底低在哪里呢?

我的第一种推测是子语句:SELECT ml.member_id FROM mm_log ml WHERE ml.access_time LIKE ‘%2017-02-06%‘ GROUP BY ml.member_id耗费了大量的查询时间,因为mm_log这张表中有150829条记录。

把子语句单拿出来运行一下,发现子语句的运行时间也就在0.111s左右。

SELECT SQL_NO_CACHE member_id FROM mm_log ml WHERE ml.access_time LIKE ‘%2017-02-06%‘ GROUP BY ml.member_id;

这就说明我的第一种推测是不合理的。

那就分析下在WHERE后使用子查询IN的执行原理:

1,IN后面跟的子查询语句的执行结果只能有一列是用来和IN前面的主表的字段匹配的,在这里指的是mm.id。

2,一条带有子查询的sql语句首先执行的是子语句,主表的数据行按照IN前面主表的字段依次跟子查询的结果进行匹配,子查询中结果中有该数据行对应字段的值,则返回true,该行被WHERE筛选出来。没有则返回false,该行不被筛选。

3,那么按照2的说法,子查询的效率应该也不低啊,子语句的耗时在0.111s左右,而且主表mm_member和子语句的查询结果相匹配的次数,肯定是要少于表连接查询时数据行间匹配的次数的,但实验结果显示使用子查询的性能确实很低。

所以我有了第二种推测,主表mm_member数据行的每一行在与IN后面子语句的结果相匹配时,子语句都会重新执行一次,也就是说子语句第一次执行时,不会在内存中有缓存。这类似与使用了两个FOR循环嵌套,外层的FOR循环每拿出一个值,内层的FOR循环都要遍历一次。

那么根据以上的推测,拿主表mm_member的数据行数乘以子语句的执行时间就应该是整个查询的时间。

mm_member的数据行数:373

多次执行子语句算出平均时间在:0.111s

整个查询耗时的理论时间:41.403s

多次执行整个查询得出实际查询时间的平均值:40.834s

计算误差:(理论值-实际值)÷理论值 = 1.37%

误差还是在可以接受的范围内的,可以证明以上的推测。

根据以上的实验,我们可以得出的结论是,表连接查询的性能是要高于子查询的。

另外,对于在子查询中使用IN的性能高还是是用EXITS的性能高,有一种普遍的说法是:

1,在外表大,内表小,外表中有索引的情况下,使用IN。

2,在外表小,内表大,内表中有索引的情况下,使用EXITS。

先介绍一下EXITS的用法,刚好本例符合外表小内表大的情况,就以本例介绍一下。看下SQL:

SELECT
    SQL_NO_CACHE mm.*
FROM
    mm_member mm
WHERE
    EXISTS(SELECT * FROM mm_log ml WHERE mm.id = ml.member_id AND ml.access_time LIKE ‘%2017-02-06%‘);

EXITS又简称代入查询,就是把主表的每一行代入子表的每一行进行检验,一旦子表中有符合的数据行就返回true,就可以取得主表中代入检验的那一行数据,否则返回false,不可以取得主表中代入检验的那一行数据。同IN不同的是,EXITS后的子语句不查询出结果,所以说SELECT后面的字段没有意义,一般使用*代替,由于EXITS的这种机制,当子表数据量比较大且有冗余字段的时候就很有可能避免了对子表的全表扫描,不像IN那样每次主表数据行来匹配都要进行全表扫描,并返回结果。所以说EXITS类似于两个FOR循环嵌套时,内层的FOR循环里面有 if(xxx){ break; }这种语法。

以上sql执行时间的平均在34秒左右,比使用IN要快上一些,但是跟表连接查询还不能比。

但是,在表与表之间没有关联关系时,就只能使用IN了。

sql 文件位置:http://pan.baidu.com/s/1gfLwIwr

  最后说一点,我们作为程序员,研究问题还是要仔细深入一点的。当你对原理了解的有够透彻,开发起来也就得心应手了,很多开发中的问题和疑惑也就迎刃而解了,而且在面对其他问题的时候也可做到触类旁通。当然在开发中没有太多的时间让你去研究原理,开发中要以实现功能为前提,可等项目上线的后,你有大把的时间或者空余的时间,你大可去刨根问底,深入的去研究一项技术,为觉得这对一名程序员的成长是很重要的事情。

时间: 2024-10-16 15:41:53

表连接查询与where后使用子查询的性能分析。的相关文章

使用连接(JOIN)来代替子查询(Sub-Queries) mysql优化系列记录

使用连接(JOIN)来代替子查询(Sub-Queries) MySQL从4.1开始支持SQL的子查询.这个技术可以使用SELECT语句来创建一个单列的查询结果,然后把这个结果作为过滤条件用在另一个查询中.例如,我们要将客户基本信息表中没有任何订单的客户删除掉,就可以利用子查询先从销售信息表中将所有发出订单的客户ID取出来,然后将结果传递给主查询,如下所示: DELETE FROM customerinfo WHERE CustomerID NOT in (SELECT CustomerID FR

1.子查询知识体系,单行子查询,多行子查询

 1查询工资比scott高的员工信息 A 第一步:查询出scott这个员工的工资 select sal from emp where ename = 'SCOTT'; B 第二步:查询出工资比scott高的员工信息 select * fromemp where sal >3000; 总结: 子查询的本质:多个select语句的嵌套 2:子查询的知识体系搭建 A 合理的书写风格 B 子查询外面()不要忘记 C 子查询和主查询可以查询的是同一张表,也可以不是同一张表 只要子查询返回的结果,主查询

Hibernate 笔记 HQL查询 条件查询,聚集函数,子查询,导航查询

本笔记继续使用dept部门表,emp员工表,一对多多对一双向映射. 1 条件查询 1.1    查询 员工表emp中 年龄eage小于30,月薪esal大于20000的员工姓名ename sql:select ename from emp where eage<? and esal >?; hql: select ename from Emp where eage<? and esal >? 1.2 问号的设置与别名 问号(?)的设置使用.setParameter(位置, 属性值)

子查询一(WHERE中的子查询)

子查询 子查询就是指的在一个完整的查询语句之中,嵌套若干个不同功能的小查询,从而一起完成复杂查询的一种编写形式,为了让读者更加清楚子查询的概念. 子查询返回结果子查询可以返回的数据类型一共分为四种: 单行单列:返回的是一个具体列的内容,可以理解为一个单值数据: 单行多列:返回一行数据中多个列的内容: 多行单列:返回多行记录之中同一列的内容,相当于给出了一个操作范围: 多行多列:查询返回的结果是一张临时表: 在WHERE子句中使用子查询 在WHERE子句之中处理单行单列子查询.多行单列子查询.单行

mysql 数据操作 子查询 带比较运算符的子查询

带比较运算符的子查询 #比较运算符:=.!=.>.>=.<.<=.<> #查询大于所有人平均年龄的员工名与年龄 思路 先拿到所有人的平均年龄然后 再用另外一条sql语句 进行比较 拿所有员工的年龄 > 所有人的平均年龄 做比较 mysql> select name,age from employee where age >(select avg(age) from employee) ; +------+------+ | name | age | +

[Z]T-SQL查询进阶--深入理解子查询

原文链接: http://www.cnblogs.com/CareySon/archive/2011/07/18/2109406.html 引言 SQL有着非常强大且灵活的查询方式,而多表连接操作往往也可以用子查询进行替代,本篇文章将会讲述子查询的方方面面. 简介 子查询本质上是嵌套进其他SELECT,UPDATE,INSERT,DELETE语句的一个被限制的SELECT语句,在子查询中,只有下面几个子句可以使用 SELECT子句(必须) FROM子句(必选) WHERE子句(可选) GROUP

mysql in子查询的优化

项目遇到一个MySQL查询特别慢的语句: SELECT * FROM ( SELECT DISTINCT t.vc_date, t.c_bankno, t.vc_bankacco, t.vc_moneytype, t.en_totalbala FROM tbankaccobala t WHERE 1 = 1 AND t.id IN ( -- 这个查询需要3s: SELECT SUBSTRING_INDEX(GROUP_CONCAT(id ORDER BY d_importtime DESC),

sql的基础语句-单行函数,dual,数字函数,日期函数,表连接,集合运算,分组报表,单行子查询,多行子查询

3. 单行函数 3.1 转换函数 select ascii('A'),chr(65) from dual; select to_char(1243123),1231451 from dual;靠左边的就是字符串,靠右边的就是数字 select to_char(123512a121) from dual;   --错误的写法,没有引号表示数字,但是数字里面包含了字母,不合法的输入值 select to_number('123141211') from dual; select to_number(

MySQL多表查询之外键、表连接、子查询、索引

一.外键: 1.什么是外键 2.外键语法 3.外键的条件 4.添加外键 5.删除外键 1.什么是外键: 主键:是唯一标识一条记录,不能有重复的,不允许为空,用来保证数据完整性 外键:是另一表的主键, 外键可以有重复的, 可以是空值,用来和其他表建立联系用的.所以说,如果谈到了外键,一定是至少涉及到两张表.例如下面这两张表: 上面有两张表:部门表(dept).员工表(emp).Id=Dept_id,而Dept_id就是员工表中的外键:因为员工表中的员工需要知道自己属于哪个部门,就可以通过外键Dep