应该使用哪个查询?从MySQL推论得出

时间:2019-06-16 13:48:32

标签: mysql innodb explain

在O'reilly优化SQL陈述书中的“解释MySQL解释”一章的末尾有这个问题。

  

以下是一个业务需求的示例,该业务需要检索父子关系中的孤立父记录。可以用三种不同的方式编写此SQL查询。在输出产生相同结果的同时,QEP显示了三种不同的路径。

mysql> EXPLAIN SELECT p.*
    -> FROM parent p
    -> WHERE p.id NOT IN (SELECT c.parent_id FROM child c)\G
*************************** 1. row ***************************
           id: 1
  select_type: PRIMARY
        table: p
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 160
        Extra: Using where
*************************** 2. row ***************************
           id: 2
  select_type: DEPENDENT SUBQUERY
        table: c
         type: index_subquery
possible_keys: parent_id
          key: parent_id
      key_len: 4
          ref: func
         rows: 1
        Extra: Using index
2 rows in set (0.00 sec)



mysql> EXPLAIN SELECT p.*
    -> FROM parent p
    -> LEFT JOIN child c ON p.id = c.parent_id
    -> WHERE c.child_id IS NULL\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: p
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 160
        Extra:
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: c
         type: ref
possible_keys: parent_id
          key: parent_id
      key_len: 4
          ref: test.p.id
         rows: 1
        Extra: Using where; Using index; Not exists
2 rows in set (0.00 sec)



mysql> EXPLAIN SELECT p.*
    -> FROM parent p
    -> WHERE NOT EXISTS
    -> SELECT parent_id FROM child c WHERE c.parent_id = p.id)\G
*************************** 1. row ***************************
           id: 1
  select_type: PRIMARY
        table: p
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 160
        Extra: Using where
*************************** 2. row ***************************
           id: 2
  select_type: DEPENDENT SUBQUERY
        table: c
         type: ref
possible_keys: parent_id
          key: parent_id
      key_len: 4
          ref: test.p.id
         rows: 1
        Extra: Using index
2 rows in set (0.00 sec)
  

哪个最好?数据随着时间的增长会导致不同的QEP表现更好吗?

据我所能研究的,这本书或互联网都没有答案。

2 个答案:

答案 0 :(得分:2)

我见过一个old article from 2009 stackoverflow 上链接了很多次。那里的测试表明,NOT EXISTS查询比其他两个查询(LEFT JOINNOT IN)慢27%(实际上是26%)。

但是,优化器已在各个版本之间进行了改进。完美的优化器将为所有三个查询创建相同的执行计划。但是,只要优化程序不完美,“哪个查询更快”的答案就可以了。取决于实际设置(包括版本,设置和数据)。

我过去曾经进行过类似的测试,而我所记得的就是LEFT JOIN从未比任何其他方法慢很多。但是出于好奇,我刚刚使用默认设置在 MariaDB 10.3.13 便携式Windows版本上创建了一个新测试。

虚拟数据:

set @parents = 1000;

drop table if exists parent;
create table parent(
    parent_id mediumint unsigned primary key
);
insert into parent(parent_id)
    select seq
    from seq_1_to_1000000
    where seq <= @parents
;

drop table if exists child;
create table child(
    child_id mediumint unsigned primary key,
    parent_id mediumint unsigned not null,
    index (parent_id)
);
insert into child(child_id, parent_id)
    select seq as child_id
    , floor(rand(1)*@parents)+1 as parent_id
    from seq_1_to_1000000
;

禁止进入:

set @start = TIME(SYSDATE(6));

select count(*) into @cnt
from parent p
where p.parent_id not in (select parent_id from child c);

select @cnt, TIMEDIFF(TIME(SYSDATE(6)), @start);

左加入:

set @start = TIME(SYSDATE(6));

select count(*) into @cnt
from parent p
left join child c on c.parent_id = p.parent_id
where c.parent_id is null;

select @cnt, TIMEDIFF(TIME(SYSDATE(6)), @start);

不存在:

set @start = TIME(SYSDATE(6));

select count(*) into @cnt
from parent p
where not exists (
    select *
    from child c
    where c.parent_id = p.parent_id
);

select @cnt, TIMEDIFF(TIME(SYSDATE(6)), @start);

执行时间(以毫秒为单位):

@parents   | 1000 | 10000 | 100000 | 1000000
-----------|------|-------|--------|--------
NOT IN     |   21 |    38 |    175 |    4459
LEFT JOIN  |   24 |    40 |    183 |    1508
NOT EXISTS |   26 |    44 |    180 |    4463

我已经多次执行查询,并且花费的时间最少。 SYSDATE可能不是衡量执行时间的最佳方法-因此,不要以这些数字为准。但是,我们可以看到多达100K的父行,两者之间并没有太大的区别,并且NOT IN方法要快一些。但是对于1M的父行,LEFT JOIN的速度快了三倍。

结论

那答案是什么?我只能说:“ LEFT JOIN”获胜。但事实是-此测试无法证明任何事情。答案是(很多次):“取决于”。当性能至关重要时,最好的办法就是对真实数据运行带有真实查询的测试。如果还没有真实数据,则应创建虚拟数据,其数量和将来的分配期望。

答案 1 :(得分:2)

这取决于您使用的MySQL版本。在较旧的版本中,IN ( SELECT ...)的表现非常糟糕。在最新版本中,它通常与其他变体一样好。而且,MariaDB在此方面可能存在一些优化差异。

在说明意图时,

EXISTS( SELECT 1 ... )可能是最清晰的。而且它可能一直(很快就存在)很快。

NOT INNOT EXISTS是另一只动物。

您的问题中的某些内容可能会产生影响:funcindex_subquery。在类似的查询中,您可能看不到这些,并且这种差异可能会导致性能差异。

或者,重复一遍:

“自2009年以来,Optimizer进行了许多改进。

“致作者(Quassnoi):请重新运行测试,并指定要针对哪个版本运行。还要注意,MySQL和MariaDB可能会产生不同的结果。

“致读者:自己测试变体,不要盲目相信此博客中的结论。”