在大型表的情况下提高NOT EXISTS的性能



我试图实现的是根据特定的过滤器从一个表中获取与另一个表不匹配的行。这两个表相对较大,所以我试图根据特定的时间范围对它们进行筛选。

到目前为止我走过的步骤。

  1. 从";T1";过去3天
SELECT
id 
FROM T1
WHERE STARTTIME BETWEEN '3 days ago' AND 'now';

执行时间4.5s

  1. 从";T2";过去3天
SELECT
id 
FROM T2
WHERE STARTTIME BETWEEN '3 days ago' AND 'now';

执行时间2.5s

  1. 现在我尝试使用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

我还尝试了这个答案中的INNER JOIN逻辑,认为这是有道理的,但我没有得到任何结果,所以我无法正确评估。

是否有更好的方法来构造此语句,从而可能加快执行时间?

2022年1月19日-根据评论更新

  1. 预期结果可以包含1到10000 之间的任意行数

  2. 使用的列具有以下索引:

CREATE INDEX IX_T1_CSTARTTIME
ON T1 (CSTARTTIME ASC)
TABLESPACE MYHOSTNAME_DATA1;
CREATE INDEX IX_T2_CSTARTTIME
ON T2 (CSTARTTIME ASC)
TABLESPACE MYHOSTNAME_DATA2;

注意:刚刚注意到索引位于不同的表空间,这可能也是一个潜在的问题吗?

  1. 根据Marmite Bomber的精彩评论,以下是声明的执行计划:

    ---------------------------------------------------------------------------------------------
    | 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]
    

是否有更好的方法来构造此语句,从而加快执行时间?

您的基本职责是编写SQL staement,Oracle的基本职责是提供执行计划

如果不满意(但应该知道使用NOT EXISTS的两个源的组合将花费比从源中提取数据的时间总和更长的时间(,则您的第一步应该是验证执行计划(而不是试图重写语句(。

查看更多有关如何在此处进行的详细信息

EXPLAIN PLAN  SET STATEMENT_ID = 'stmt1' into   plan_table  FOR
SELECT
PAD
FROM T1
WHERE STARTTIME BETWEEN date'2021-01-11' AND date'2021-01-13'
AND NOT EXISTS (
SELECT NULL FROM T2
WHERE T1.ID = T2.ID 
AND STARTTIME BETWEEN date'2021-01-11' AND date'2021-01-13'
);
SELECT * FROM table(DBMS_XPLAN.DISPLAY('plan_table', 'stmt1','ALL'));

这是你应该看到的

-----------------------------------------------------------------------------
| Id  | Operation            | Name | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |      |  1999 |   150K| 10175   (1)| 00:00:01 |
|*  1 |  HASH JOIN RIGHT ANTI|      |  1999 |   150K| 10175   (1)| 00:00:01 |
|*  2 |   TABLE ACCESS FULL  | T2   |  2002 | 26026 |  4586   (1)| 00:00:01 |
|*  3 |   TABLE ACCESS FULL  | T1   |  4002 |   250K|  5589   (1)| 00:00:01 |
-----------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------

1 - access("T1"."ID"="T2"."ID")
2 - filter("STARTTIME"<=TO_DATE(' 2021-01-13 00:00:00', 'syyyy-mm-dd 
hh24:mi:ss') AND "STARTTIME">=TO_DATE(' 2021-01-11 00:00:00', 
'syyyy-mm-dd hh24:mi:ss'))
3 - filter("STARTTIME"<=TO_DATE(' 2021-01-13 00:00:00', 'syyyy-mm-dd 
hh24:mi:ss') AND "STARTTIME">=TO_DATE(' 2021-01-11 00:00:00', 
'syyyy-mm-dd hh24:mi:ss'))

注意,hash join(由于not exists,此处为anti(是连接两个大行源的最佳方式。还请注意,计划不使用索引。原因是一样的——要访问大数据,您不想越过索引。

与低基数行源(OTPL(的情况相反,您希望看到索引访问NESTED LOOPS ANTI

有时Oracle会对感到困惑(例如,当看到过时的统计信息时(,并决定采用NESTED LOOP方式,即使是对于大数据也是如此,这会导致耗时过长。

这至少可以帮助你决定自己是否有问题。

也许一个简单的MINUS操作就能完成您想要的:

select id 
from ( select id 
from t1
where starttime between '3 days ago' and 'now' 
MINUS
select id 
from t2
where starttime between '3 days ago' and 'now' 
);

然而,您实际上定义了starttime between '3 days ago' and 'now'。这实际上是按原样使用当前查询。MINUS操作从第一个查询中删除第二个查询中存在的值,并返回结果。请参阅此处的MINUS演示。

最新更新