zoukankan      html  css  js  c++  java
  • 查询在一张表不在另外一张表的记录及效率探究

    在我做项目的时候遇到一个需求,要将存在于表ta而不存在于表tb中的数据查询出来。

    记录使用的方法和探讨效率。

    数据准备

    创建表ta,并且使用存储过程插入13000条数据,在我的机器上运行时间: 346.719s。如果觉得插入的速度比较慢,可以直接导入我建好的表,百度云地址 http://pan.baidu.com/s/1dFtovg1 ,里面已经有数据了,直接导入sql执行即可,这样比用存储过程要快很多。

    DROP TABLE IF EXISTS ta;
    
    CREATE TABLE `ta` (
      `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=13000 DEFAULT CHARSET=utf8;
    
    DROP PROCEDURE IF EXISTS ta_insert;
    
    DELIMITER $$
    CREATE PROCEDURE ta_insert()
     MODIFIES SQL DATA
     BEGIN
     SET @i=1;
     SET @max=13000;
     WHILE @i<@max DO
     INSERT INTO `ta` VALUES ();
     SET @i = @i + 1;
    END WHILE;
    end $$
    
    CALL ta_insert();
    

    创建表tb,并且使用存储过程插入10000条数据,在我的机器上运行时间: 224.102s。

    DROP TABLE IF EXISTS tb;
    
    CREATE TABLE `tb` (
      `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=8000 DEFAULT CHARSET=utf8;
    
    DROP PROCEDURE IF EXISTS tb_insert;
    
    DELIMITER $$
    CREATE PROCEDURE tb_insert()
     MODIFIES SQL DATA
     BEGIN
     SET @i=1;
     SET @max=8000;
     WHILE @i<@max DO
     INSERT INTO `tb` VALUES ();
     SET @i = @i + 1;
    END WHILE;
    end $$
    
    CALL tb_insert();
    

    子查询

    使用NOT IN,ta表中的每一个id值都要去与tb表中的id匹配,匹配到就停止,也就是说,存在于ta表而不存在于tb表的id值需要与所有tb表中的id值进行匹配。

    执行子查询时,MYSQL需要创建临时表,查询完毕后再删除这些临时表,所以,子查询的速度会受到一定的影响,这里多了一个创建和销毁临时表的过程。

    平均时间为 0.04s。

    SELECT ta.id FROM ta WHERE ta.id IN (SELECT id FROM tb)
    

    左连接

    使用 LEFT JOIN,ta表左连接tb表,而存在于ta表不存在于tb表中的字段为NULL,于是我们可以通过判断WHERE tb.id IS NULL来找到存在于ta表而不存在于tb表的id值。

    平均时间是 0.06s。

    SELECT ta.id FROM ta LEFT JOIN tb ON ta.id = tb.id WHERE tb.id IS NULL
    

    效率之谜

    版本问题?

    按理来说,连接应该比子查询要快,但是在我进行试验的时候发现却不是这样的,子查询居然还比连接要快。

    搜索了解到

    对于类似NOT IN这样的子查询,也能受益于subquery materialize,将子查询的结果集cache到临时表里,使用hashindex来进行检索;物化的子查询可以看到select_type字段为SUBQUERY,而在MySQL5.5里为DEPENDENT SUBQUERY

    可能是版本原因,我用的是mysql5.7,可能做了优化。

    于是使用mysql5.5再次测试。

    发现子查询和左连接的查询时间都在0.12s附近,还是不能说明连接比子查询高效。进一步猜测,我的数据组织格式是否出现了问题,于是使用上面百度云盘连接中的mm_member表和mm_log表(这是 mysql(4)—— 表连接查询与where后使用子查询的性能分析。 提供的数据) 。

    猜测验证

    子查询

    SELECT mm_member.id FROM mm_member WHERE mm_member.id NOT IN (SELECT DISTINCT mm_log.member_id FROM mm_log)
    

    左连接

    SELECT mm_member.id FROM mm_member LEFT JOIN (SELECT DISTINCT mm_log.member_id FROM mm_log ) AS mm
    ON mm.member_id = mm_member.id WHERE mm.member_id IS NULL
    

    mysql5.7

    子查询为1.1s左右,左连接为1.55s,子查询依然速度较快。

    mysql5.5

    子查询为48s左右,左连接为1.4s,将近34倍的差距,由此印证上面引用的那部分,mysql5.7确实已经多子查询做了优化,使其达到了逼近左连接的效率

    那为什么我自己所建立的表无法体现版本的这种性能差别?

    猜测应该是数据类型的原因,可能int类型的查询效率已经都优化好了。

    网传最高效

    不太清楚其中的原理,并且在我的测试中性能跟连接差不多。

    SELECT id FROM ta WHERE (SELECT COUNT(1) AS num FROM tb WHERE ta.id = tb.id) = 0
    

    而且网上流传的版本((数据库篇) SQL查询~ 存在一个表而不在另一个表中的数据)为

    select * from B where (select count(1) as num from A where A.ID = B.ID) = 0
    应该是
    select * from A where (select count(1) as num from B where A.ID = B.ID) = 0
    大表在前,小表在后。

    注意

    1. 存储过程循环插入比普通方式插入数据慢很多倍。
    2. 索引可以有效提高搜索效率。
    3. 不是所有的子查询都比连接慢的。

    参考文档

    1. MySQL 5.6的优化器改进
    2. mysql(4)—— 表连接查询与where后使用子查询的性能分析。
  • 相关阅读:
    TSQL循环打印一年中所有的日期(WHILE循环)
    给Table加字段的SQL
    [正则表达式]前台JS得到控件ID (该控件被其它控件包住了)
    1.SQL Server中批量更新Object的Owner 2.附加数据库
    转:动态LINQ的几种方法
    转:查看LINQ生成SQL语句的几种方法
    TrimZero方法
    Oracle关联更新语法(TSQL中的update...from)
    Table之间的空隙或Table与父控件之间的空隙怎么去掉?
    自动完成带来的麻烦
  • 原文地址:https://www.cnblogs.com/morethink/p/7858129.html
Copyright © 2011-2022 走看看