使用子查询的查询比使用固定数据而不是子查询的相同查询需要更长的时间

Posted

技术标签:

【中文标题】使用子查询的查询比使用固定数据而不是子查询的相同查询需要更长的时间【英文标题】:Query using subquery needs longer than same query with fixed data instead of subquery 【发布时间】:2019-10-30 11:08:41 【问题描述】:

组合查询

select a, b from A where a > 5 and b in (select b from B where c = "some")

大约比固定查询长 30 倍

select a, b from A where a > 5 and b in (1, 2, 3)

虽然

select b from B where c = "some" 产生与固定查询中使用的完全相同的行集,(1, 2, 3) select b from B where c = "some" 单独执行需要 0.01 秒 select a, b from A where a > 5 执行需要 0.3 秒。

A 上的 (a, b) 上有一个索引。

分析组合查询:

analyze select a, b from A where a > 5 and b in (select b from B)
*************************** 1. row ***************************
           id: 1
  select_type: PRIMARY
        table: A
         type: range
possible_keys: idx_a_b
          key: idx_a_b
      key_len: 8
          ref: NULL
         rows: 126459
       r_rows: 66181.00
     filtered: 100.00
   r_filtered: 100.00
        Extra: Using index condition; Using temporary; Using filesort
*************************** 2. row ***************************
           id: 1
  select_type: PRIMARY
        table: B
         type: eq_ref
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 2
          ref: A.b
         rows: 1
       r_rows: 1.00
     filtered: 100.00
   r_filtered: 0.09
        Extra: Using where

请注意,r_rows = 66181 匹配 select a, b from A where a > 5

似乎 MariaDB 仅使用索引的 a 部分而忽略了它应该能够在第一步中从子查询中获取的 b。解释扩展显示 MariaDB 用

替换了我的查询
select b, a from B join A where ((B.b = A.b) and (A.a > 5) and (B.c = "some"))

奇怪的是,如果给定子查询返回的固定集合 (1, 2, 3),而不是子查询本身,MariaDB 确实似乎确实同时使用了索引的 a 和 b,因为可以通过分析固定查询观察到:

analyze select a, b from A where a > 5 and y in (1, 2, 3)
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: A
         type: range
possible_keys: idx_a_b
          key: idx_a_b
      key_len: 10
          ref: NULL
         rows: 126459
       r_rows: 59.00
     filtered: 100.00
   r_filtered: 100.00
        Extra: Using index condition; Using temporary; Using filesort

r_rows = 59 匹配两个查询(组合查询和固定查询)的结果集大小。

如何让 MariaDB 使用与固定查询相同的查询计划,同时使用 A 的索引中的 a 和子查询的 b?

【问题讨论】:

什么版本??这方面已经有所改进。 10.1.40-MariaDB-0ubuntu0.18.04.1 【参考方案1】:

我的大脑变得糊涂,试图处理 B vs b 和 A vs a,所以我更改了表名。

select  Y.b, Y.a
    from  X
    join  Y
    where  ((X.b = Y.b)
       and  (Y.a > 5)
       and  (X.c = "some")
           )

那个查询需要

X:  INDEX(c, b)
Y:  INDEX(a, b), INDEX(b, a)

这样,优化器无论从哪个表开始都可以高效。优化器将根据ac 的统计信息选择更好的表作为开始。但是,除非您打开“直方图”(MariaDB 10.0 - 10.3;10.4 默认启用它们),否则优化器可能没有完整的故事。

【讨论】:

谢谢你,毕竟很令人惊讶。我的测试运行需要 1.18 秒,没有新索引,Y(b, a) 1.08 秒,X(c) 0.81 秒(与 X(c, b) 相同),同时使用 Y(b, a) 和 X( c) 得到即时答复。 @Jojoma - 如果c="some" 是唯一的(或几乎是唯一的),那么(c) 非常快(使用(c,b) 可能更快,然后检查另一个表中很少的行(快因为要检查的行太少)。【参考方案2】:

有时,查询的优化方式不同。当您有一个固定的值列表时,查询规划器会更了解您在做什么。

如果b 中没有重复项,那么join 通常会产生一个好的执行计划:

select a.a, a.b
from a join
     b
     on a.b = b.b
where a.a > 5;

我还建议尝试exists

select a, b
from A
where a > 5 and
      exists (select 1 from B where b.b = a.b) ;

并确保您在b(b) 上有一个索引:

create index idx_b_b on b(b);

【讨论】:

感谢您的建议。调查它我发现了“解释扩展”命令,这表明 MariaDB 实际上确实使用了一个连接:select b, a from B join A where ((B.b = A.b) and (A.a > 5) and (B.c = "some"))(B.c = “some”是最初帖子中 B 上的子查询)。我已经相应地编辑了我的问题。

以上是关于使用子查询的查询比使用固定数据而不是子查询的相同查询需要更长的时间的主要内容,如果未能解决你的问题,请参考以下文章

子查询|视图事务

Mysql 子查询怎么写?

如何使用 Mysql Joins 而不是嵌套子查询来实现相同的结果?

MySQL子查询(六)

如何在查询中使用 $push 将数据插入子文档,而不是检索文档并将其保存回来

CTE 和子查询的区别?