【发布时间】:2022-01-13 15:42:36
【问题描述】:
我想要完成的是从一个表中获取与基于特定过滤器的另一个表不匹配的行。这两个表比较大,所以我试图根据一定的时间范围过滤它们。
到目前为止我所经历的步骤。
- 从“T1”获取过去 3 天的 ID
SELECT
id
FROM T1
WHERE STARTTIME BETWEEN '3 days ago' AND 'now';
执行时间4.5s。
- 从“T2”获取过去 3 天的 ID
SELECT
id
FROM T2
WHERE STARTTIME BETWEEN '3 days ago' AND 'now';
执行时间2.5s。
- 现在我尝试使用
NOT EXISTS将两个语句的结果合并为一个
SELECT
CID
FROM T1
WHERE STARTTIME BETWEEN '3 days ago' AND 'now'
AND NOT EXISTS (
SELECT NULL FROM T2
WHERE T1.ID = T2.ID
AND STARTTIME BETWEEN '3 days ago' AND 'now'
);
执行时间23s。
我还尝试了 this answer 中的 INNER JOIN 逻辑,认为它是有道理的,但我没有得到任何结果,所以我无法正确评估。
有没有更好的方法来构造这个可能导致更快执行时间的语句?
19.01.2022 - 基于 cmets 的更新
-
预期结果可以包含 1 到 10 000 之间的任意行数
-
使用的列具有以下索引:
CREATE INDEX IX_T1_CSTARTTIME
ON T1 (CSTARTTIME ASC)
TABLESPACE MYHOSTNAME_DATA1;
CREATE INDEX IX_T2_CSTARTTIME
ON T2 (CSTARTTIME ASC)
TABLESPACE MYHOSTNAME_DATA2;
注意:刚刚注意到索引位于不同的表空间,这也可能是一个潜在问题吗?
-
继 Marmite Bomber 的优秀 cmets 之后,以下是语句的执行计划:
--------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time | -------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 21773 | 2019K| | 1817K (1)| 00:01:12 | |* 1 | HASH JOIN RIGHT ANTI| | 21773 | 2019K| 112M| 1817K (1)| 00:01:12 | |* 2 | TABLE ACCESS FULL | T2 | 2100K| 88M| | 1292K (1)| 00:00:51 | |* 3 | TABLE ACCESS FULL | T1 | 2177K| 105M| | 512K (1)| 00:00:21 | --------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - access("T2"."ID"="T1"."ID") 2 - filter("STARTTIME">=1642336690000 AND "T2"."ID" IS NOT NULL AND "STARTTIME"<=1642595934000) 3 - filter("STARTTIME">=1642336690000 AND "STARTTIME"<=1642595934000) Column Projection Information (identified by operation id): ----------------------------------------------------------- 1 - (#keys=1; rowset=256) "T1"."ID"[CHARACTER,38] 2 - (rowset=256) "T2"."ID"[CHARACTER,38] 3 - (rowset=256) "ID"[CHARACTER,38]
【问题讨论】:
-
问题本身可能导致反连接的时间更长(“不存在”条件)。例如,如果每个表有 100 万行,并且每个查询选择 10,000 行(每个表中的最后三天),则每个表上的三天过滤器可能需要几秒钟。但是,反连接步骤必须将第一个表中的 10,000 行与第二个表中的 10,000 行进行比较;也就是 1 亿次比较,这当然会比最初的过滤花费更长的时间。
-
您没有告诉我们两个表上存在哪些索引(如果有的话)。 ID 列和日期列上的单独索引可能会有所帮助;日期列上的索引可能会使初始步骤更快,但真正重要的索引将是 ID 列上的索引,因为它们会影响反连接的执行。
-
顺便说一句:将您的 NOT EXISTS 条件重写为 NOT IN 条件或联接(加上一些条件)不会使您的查询更快。 Oracle 使用最有效的方法(或者优化器“认为”是最有效的方法,无论如何)将您的条件(无论您使用哪种语法)重新写入其自己的连接版本。这是错误的看法;您的查询很好,就像现在一样。查找索引、统计信息 - 它们是最新的等等,而不是查询的结构。
-
@Yanis Petras,您可以尝试结合 LEFT JOIN 和 WHERE 子句:SELECT T1.CID FROM T1 LEFT JOIN T2 ON T1.ID = T2.ID AND T2.STARTTIME BETWEEN '3 天前' AND 'now' WHERE T1.STARTTIME BETWEEN '3 days ago' AND 'now' AND T2.ROWID 为 NULL
-
就像我说的(并且您刚刚确认),使用不同的语法编写相同的查询将无济于事。 Oracle 在内部将它们全部重写为它发送执行的同一查询。关于索引:看看你是否通过在 ID 上添加索引得到有意义的改进。 (在大型组织中,您会先在开发环境中执行此操作,然后再要求您的 DBA 允许您在生产环境中创建相同的索引。)
标签: sql oracle join query-optimization