应该使用哪个查询?从MySQL解释推断

应该使用哪个查询?从MySQL解释推断,mysql,innodb,explain,Mysql,Innodb,Explain,在O'reilly Optimization SQL Stations一书中的MySQL解释章节中,最后有一个问题 以下是检索父/子关系中孤立父记录的业务需求示例。这个SQL查询可以用三种不同的方式编写。虽然输出结果相同,但QEP显示了三条不同的路径 哪一个最好?随着时间的推移,数据增长是否会导致不同的QEP表现更好 就我所能研究的而言,书中或互联网上都没有答案。有一个我在stackoverflow上多次看到的链接。那里的测试表明,notexists查询的速度为27%,实际上比其他两个左连接而不

在O'reilly Optimization SQL Stations一书中的MySQL解释章节中,最后有一个问题

以下是检索父/子关系中孤立父记录的业务需求示例。这个SQL查询可以用三种不同的方式编写。虽然输出结果相同,但QEP显示了三条不同的路径

哪一个最好?随着时间的推移,数据增长是否会导致不同的QEP表现更好

就我所能研究的而言,书中或互联网上都没有答案。

有一个我在stackoverflow上多次看到的链接。那里的测试表明,notexists查询的速度为27%,实际上比其他两个左连接而不在的查询慢26%

但是,优化器已从一个版本改进到另一个版本。完美的优化器将为所有三个查询创建相同的执行计划。但只要优化器不是完美的,那么哪个查询的答案会更快?可能取决于实际设置,包括版本、设置和数据

我在过去也运行过类似的测试,我所记得的是左连接从未比任何其他方法慢很多。但出于好奇,我刚刚在MariaDB 10.3.13便携式Windows版本上创建了一个带有默认设置的新测试

虚拟数据: 左连接: 不存在: 执行时间(毫秒): 我已经执行了多次查询,并且占用的时间最少。SYSDATE可能不是度量执行时间的最佳方法,所以不要认为这些数字是准确的。但是,我们可以看到,多达100K的父行之间没有太大的差异,not IN方法的速度要快一点。但对于1M的父行,左连接速度要快三倍

结论 那么答案是什么呢?我只能说:左加入获胜。但事实是——这个测试证明不了什么。答案是:视情况而定。当性能很重要时,您所能做的最好的事情就是对真实数据使用真实查询运行您自己的测试。如果您还没有真实的数据,那么应该创建虚拟数据,其中包含您希望在未来拥有的数量和分布

在stackoverflow上有一个链接,我已经看过很多次了。那里的测试表明,notexists查询的速度为27%,实际上比其他两个左连接而不在的查询慢26%

但是,优化器已从一个版本改进到另一个版本。完美的优化器将为所有三个查询创建相同的执行计划。但只要优化器不是完美的,那么哪个查询的答案会更快?可能取决于实际设置,包括版本、设置和数据

我在过去也运行过类似的测试,我所记得的是左连接从未比任何其他方法慢很多。但出于好奇,我刚刚在MariaDB 10.3.13便携式Windows版本上创建了一个带有默认设置的新测试

虚拟数据: 左连接: 不存在: 执行时间(毫秒): 我已经执行了多次查询,并且占用的时间最少。SYSDATE可能不是度量执行时间的最佳方法,所以不要认为这些数字是准确的。但是,我们可以看到,多达100K的父行之间没有太大的差异,not IN方法的速度要快一点。但对于1M的父行,左连接速度要快三倍

结论
那么答案是什么呢?我只能说:左加入获胜。但事实是——这个测试证明不了什么。答案是:视情况而定。当性能很重要时,您所能做的最好的事情就是对真实数据使用真实查询运行您自己的测试。如果您还没有真实的数据,那么应该创建虚拟数据,其中包含您希望在未来拥有的数量和分布

这取决于您使用的MySQL版本。在旧版本中,在“选择…”。。。表现糟糕。在最新版本中,它通常与其他变体一样好。此外,MariaDB在优化方面也存在一些差异,可能是在这方面

存在选择1。。。这也许是说明意图的最清晰的方式。也许它一出现就一直很快

不存在和不存在是不同的动物

问题中可能会产生影响的一些内容:func和index_子查询。在类似的查询中,您可能看不到这些,这种差异可能导致性能差异

或者,我重复一下:

自2009年以来,优化器有了许多改进

作者:请重新运行您的测试,并指定它们运行的版本。还要注意,MySQL和MariaDB可能会产生不同的结果


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

这取决于您使用的MySQL版本。在旧版本中,在“选择…”。。。表现糟糕。在最新版本中,它通常与其他变量一样好 蚂蚁。此外,MariaDB在优化方面也存在一些差异,可能是在这方面

存在选择1。。。这也许是说明意图的最清晰的方式。也许它一出现就一直很快

不存在和不存在是不同的动物

问题中可能会产生影响的一些内容:func和index_子查询。在类似的查询中,您可能看不到这些,这种差异可能导致性能差异

或者,我重复一下:

自2009年以来,优化器有了许多改进

作者:请重新运行您的测试,并指定它们运行的版本。还要注意,MySQL和MariaDB可能会产生不同的结果


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

请注意,这两个查询并不等同。但是,如果child.parent\u id不能为NULL,它们将返回相同的结果。这是真的,我想这是隐含的,parent\u id是一个fk,因为它也是子表中的一个索引,并且没有子表是孤立的,只有父表是孤立的。ironic.FKs可以是空的可选关系。顺便问一下:哪本书?你引用的是什么?对不起,我刚刚编辑了我的评论。请注意,这两个查询并不等价。但是,如果child.parent\u id不能为NULL,它们将返回相同的结果。这是真的,我想这是隐含的,parent\u id是一个fk,因为它也是子表中的一个索引,并且没有子表是孤立的,只有父表是孤立的。ironic.FKs可以是空的可选关系。顺便问一下:哪本书?你引用的是什么?对不起,我刚刚编辑了我的评论。这是一本书。
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)
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