【问题标题】:Slow inner join in OracleOracle中的慢内部连接
【发布时间】:2021-02-15 07:03:19
【问题描述】:

我有一个 Oracle 数据库,其中一个主表包含 9 000 000 行,第二个包含 19 000 000 行。

当我这样做时:

SELECT *
FROM main m
INNER JOIN second s ON m.id = s.fk_id AND s.cd = 'E' AND s.line = 1

获取结果的第一部分需要 45 秒,即使所有索引都在下面:

CREATE INDEX IDX_1 ON SECOND (LINE, CD, FK_ID, ID);
CREATE INDEX IDX_1 ON SECOND (LINE, CD);
MAIN (ID) AS PRIMARY KEY

知道如何更快地做到这一点吗?我尝试了一些索引,重建但总是需要 45 秒

这是执行计划:

------------------------------------------------------------------------------------------------
| Id  | Operation            | Name                 | Rows    | Bytes      | Cost   | Time     |
------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |                      | 8850631 | 2133002071 | 696494 | 00:00:28 |
| * 1 |   HASH JOIN          |                      | 8850631 | 2133002071 | 696494 | 00:00:28 |
| * 2 |    TABLE ACCESS FULL | SECOND               | 8850631 |  646096063 | 143512 | 00:00:06 |
|   3 |    TABLE ACCESS FULL | MAIN                 | 9227624 | 1550240832 | 153363 | 00:00:06 |
------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
------------------------------------------
* 1 - access("M"."ID"="S"."FK_ID")
* 2 - filter("S"."CD"='D' AND "S"."LINE"=1)

谢谢

【问题讨论】:

  • 您的数据是否可以为这四列而不是当前索引添加唯一(或主)键?如果可能,您可能会获得性能提升。
  • 发布查询计划。您的谓词的选择性如何?
  • 好吧,如果只有一个表被索引,您将不得不full table scan 另一个 - 这解释了我们的 45 秒。如果您想快速查看第一条记录,您必须在second 表上建立索引才能访问s.cd = 'E' AND s.line = 1,并在main 上建立索引才能访问连接列。请参阅here 您应提供的其他信息。
  • 重要提示 - 有多少行匹配谓词s.cd = 'E' AND s.line = 1?您预计连接将返回多少行?
  • @MarmiteBomber 不幸的是我同意。

标签: sql oracle indexing


【解决方案1】:

如果您想快速查看第一行,您必须启用 Oracle 以使用 NESTED LOOP 连接。

这需要second 上的索引,其中包含您在查询中约束的两列,以及连接列id 上的main 上的索引

create index second_idx on second(line,cd);
create index main_idx on main(id);

您会看到类似于下面的执行计划

--------------------------------------------------------------------------------------------
| Id  | Operation                     | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |            |    87 |  8178 |   178   (0)| 00:00:03 |
|   1 |  NESTED LOOPS                 |            |       |       |            |          |
|   2 |   NESTED LOOPS                |            |    87 |  8178 |   178   (0)| 00:00:03 |
|   3 |    TABLE ACCESS BY INDEX ROWID| SECOND     |    87 |  2523 |     4   (0)| 00:00:01 |
|*  4 |     INDEX RANGE SCAN          | SECOND_IDX |     1 |       |     3   (0)| 00:00:01 |
|*  5 |    INDEX RANGE SCAN           | MAIN_IDX   |     1 |       |     1   (0)| 00:00:01 |
|   6 |   TABLE ACCESS BY INDEX ROWID | MAIN       |     1 |    65 |     2   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   4 - access("S"."LINE"=1 AND "S"."CD"='E')
   5 - access("M"."ID"="S"."FK_ID")

您将通过索引访问 second 中请求的 line 和 cd(计划第 4 行和第 3 行)中的所有行,并且对于每个这样的行,您将通过索引访问 main 表(第 5 行和第 5 行) 6)

这将提供对前几行的即时访问,如果second 表中的行数较少且带有所选行和 cd,则可以正常工作。在其他情况下(当有大量带有 s.cd = 'E' AND s.line = 1 的行时 - 比如说 10k+),您仍然会很快看到第一个结果行,但您会等待很长时间才能看到 最后一个 行(完成查询需要更多的 45 秒)。

如果这是一个问题,您必须使用HASH JOIN(您现在可能会这样做)。

散列连接通常不使用索引并产生以下执行计划

-----------------------------------------------------------------------------
| Id  | Operation          | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |        | 10182 |  1153K|   908   (1)| 00:00:11 |
|*  1 |  HASH JOIN         |        | 10182 |  1153K|   908   (1)| 00:00:11 |
|*  2 |   TABLE ACCESS FULL| SECOND | 10182 |    99K|   520   (2)| 00:00:07 |
|   3 |   TABLE ACCESS FULL| MAIN   | 90000 |  9316K|   387   (1)| 00:00:05 |
-----------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   1 - access("M"."ID"="S"."FK_ID")
   2 - filter("S"."LINE"=1 AND "S"."CD"='E')

总结

要使用nested loops,索引必须如上所述

nested loops 和 hash join 之间的切换由 Oracle 数据库 (CBO) 完成 - 前提是您的表统计信息和数据库配置良好。

【讨论】:

  • 谢谢,查询结果返回 9 229 434 行。我将执行计划添加到主要问题中。我无法创建指定的 2 索引,因为列已编入索引..
  • @Bosshoss 将来自 second 的 880 万行与来自 main 的 920 万行连接起来,hash join 和 full table scan(如您发布的执行计划中所示)是更好的选择。您可以使用 parallel 选项加快一点速度。
猜你喜欢
  • 2010-12-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-20
  • 1970-01-01
  • 2022-01-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多