【问题标题】:SQL query with subquery takes longer than both queries separately带有子查询的 SQL 查询比单独的两个查询花费更长的时间
【发布时间】:2022-01-05 16:19:01
【问题描述】:

问题

我有两个查询,其中一个需要另一个的结果。我的第一个猜测是使用独立的子查询:

SELECT P2.*
FROM ExampleTable P2
WHERE P2.delivery_start >= (
    SELECT MIN(P1.delivery_start)
    FROM ExampleTable P1
    WHERE 1641288602 < P1.delivery_end
);

整个查询需要 5-6 秒,这对我的应用程序来说太长了。一个接一个地运行这些查询只需要大约 800 毫秒:

SELECT MIN(P1.delivery_start)
FROM ExampleTable P1
WHERE 1641288602 < P1.delivery_end;
SELECT P2.*
FROM ExampleTable P2
WHERE P2.delivery_start >= 1641286800;

我正在使用 Mariadb 10.2,并且在 delivery_startdelivery_end 上都有索引。

我尝试过的

我使用了 CTE 而不是导致相同性能的子查询。将变量与 SET 一起使用会产生与分别运行两个查询类似的结果,所以我暂时将使用它。

我对所有 3 个查询都运行了 EXPLAIN

1。用子查询查询

id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY P2 ALL delivery_start NULL NULL NULL 6388282 Using where
2 SUBQUERY P1 range delivery_end delivery_end 4 NULL 36378 Using index condition

2。单独查询

id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE P1 range delivery_end delivery_end 4 NULL 36432 Using index condition
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE P2 range delivery_start delivery_start 4 NULL 35944 Using index condition

问题

我认为问题出现在第一个EXPLAIN 表中,因为它的类型为ALL,这意味着数据库执行全表扫描。我的问题很简单:为什么?优化器是否无法确定子查询会产生一个我们只需要 range 类型查询的数字吗?为什么它不使用任何索引?

【问题讨论】:

  • 子查询的谓词1641288602 &lt; P1.delivery_end选择了多少行(平均百分比)?
  • 您在搜索条件中使用了不等式,这会导致大量数据读取。我会假设这是一个非交互式过程,所以 5 秒应该相当不错。现在,如果您想以交互方式处理如此大量的数据,那么您就有了设计问题。
  • @TheImpaler 该表包含大约 600 万个条目,但我认为它不应该相关,因为一个接一个地运行两个查询(即使与 SET 变量非交互)运行速度比 5 秒快得多。
  • @BobMcFry 这里存在架构问题。该查询可能适合非交互式使用,但您似乎希望以交互方式使用它。如果是这种情况,您将需要执行预处理(或使用冗余)以大幅减少非活动读取的数量。
  • @TheImpaler 我不想以交互方式使用它。我只想知道为什么性能比我在程序中正确知道的 2 个查询要差得多。

标签: mysql sql mariadb subquery query-optimization


【解决方案1】:

MariaDB 文档中描述了该问题:

在所有剩余的情况下,当 NULL 不能用 FALSE 代替时,它 无法使用索引查找。这不是限制 服务器,但是 ANSI SQL 中的 NULL 语义的结果 标准。

这里有一个完整的考试: https://mariadb.com/kb/en/non-semi-join-subquery-optimizations/

如果没有找到任何行,子查询的结果可能会返回 NULL。因此,MariaDB 不能将索引用于父查询。

您必须重写您的子查询,使其始终返回带有非 NULL 标量的行或坚持使用两个单独的查询。但是,如果您的第一个查询返回 NULL,会发生什么?使用复合语句,您可以在第二个查询周围放置一个 if,如果第一个返回 NULL,则甚至不执行它。

【讨论】:

  • 能否请您添加指向此断言的链接。
  • 我已经添加了链接。
【解决方案2】:

替换这些

INDEX(delivery_start)
INDEX(delivery_end)

这些:

INDEX(delivery_start, delivery_end)
INDEX(delivery_end, delivery_start)

第二个对子查询有很大帮助。然后第一个可能有助于外部查询。

(如果这些没有帮助,请添加SHOW CREATE TABLEEXPLAIN SELECT ... 和表格大小。)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-15
    • 2016-06-05
    • 2020-09-07
    相关资源
    最近更新 更多