Oracle 哈希连接查询性能问题

问题描述 投票:0回答:2

我正在使用 Oracle 19c,并通过简单的查询面临奇怪的性能问题。 这是我的查询:

SELECT 
TU.ID AS RecordID
FROM TABLE_U TU
JOIN (SELECT to_date('11-04-2001','DD-MM-YYYY') AS PROCESS_DATE FROM DUAL
UNION ALL SELECT to_date('10-04-2001','DD-MM-YYYY') AS PROCESS_DATE FROM DUAL
)  PDATE ON (1=1)
JOIN TABLE_T t ON (t.PROCESSOR = TU.PROCESSOR AND TU.TTD = t.ID AND t.FROMDATE <=PDATE.PROCESS_DATE AND t.TODATE >= PDATE.PROCESS_DATE)
LEFT JOIN TABLE_TD TD ON (TD.PROCESSOR = t.PROCESSOR AND TD.ABC = t.ID)

WHERE t.CREATED <=PDATE.PROCESS_DATE
AND TU.FROMDATE <= PDATE.PROCESS_DATE

这是它的执行计划

Plan hash value: 1872580574
 
---------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                               | Name                            | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                        |                                 |  3745 |   369K| 35213   (1)| 00:00:02 |
|*  1 |  HASH JOIN OUTER                        |                                 |  3745 |   369K| 35213   (1)| 00:00:02 |
|   2 |   NESTED LOOPS                          |                                 |  3745 |   303K| 34957   (1)| 00:00:02 |
|   3 |    NESTED LOOPS                         |                                 | 77970 |   303K| 34957   (1)| 00:00:02 |
|   4 |     NESTED LOOPS                        |                                 |  7797 |   388K|  3853   (1)| 00:00:01 |
|   5 |      VIEW                               |                                 |     2 |    18 |     4   (0)| 00:00:01 |
|   6 |       UNION-ALL                         |                                 |       |       |            |          |
|   7 |        FAST DUAL                        |                                 |     1 |       |     2   (0)| 00:00:01 |
|   8 |        FAST DUAL                        |                                 |     1 |       |     2   (0)| 00:00:01 |
|   9 |      TABLE ACCESS BY INDEX ROWID BATCHED| TABLE_T                         |  3899 |   159K|  1924   (1)| 00:00:01 |
|* 10 |       INDEX RANGE SCAN                  | TABLE_T_FROMDATE                |    23 |       |  1916   (1)| 00:00:01 |
|* 11 |     INDEX RANGE SCAN                    | PK_TABLE_U                      |    10 |       |     3   (0)| 00:00:01 |
|* 12 |    TABLE ACCESS BY INDEX ROWID          | TABLE_U                         |     1 |    32 |     4   (0)| 00:00:01 |
|  13 |   INDEX STORAGE FAST FULL SCAN          | TABLE_TD_IDX                    |   231K|  4066K|   256   (1)| 00:00:01 |
---------------------------------------------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   1 - access("TD"."PROCESSOR"(+)="T"."PROCESSOR" AND "TD"."ABC"(+)="T"."ID")
  10 - access("T"."TODATE">="PDATE"."PROCESS_DATE" AND "T"."FROMDATE"<="PDATE"."PROCESS_DATE" AND 
              "T"."CREATED"<="PDATE"."PROCESS_DATE")
       filter("T"."CREATED"<="PDATE"."PROCESS_DATE" AND "T"."TODATE">="PDATE"."PROCESS_DATE")
  11 - access("TU"."TTD"="T"."ID" AND "T"."PROCESSOR"="TU"."PROCESSOR")
       filter("T"."PROCESSOR"="TU"."PROCESSOR")
  12 - filter("TU"."FROMDATE"<="PDATE"."PROCESS_DATE")
 
Hint Report (identified by operation id / Query Block Name / Object Alias):
Total hints for statement: 1 (E - Syntax error (1))
---------------------------------------------------------------------------
 
   0 -  SEL$6
         E -  USEX_NL

(统计数据是最新的) 我对此有两个问题:

  • 查询执行时间超过 30 分钟
  • 我不明白为什么oracle要考虑TABLE_TD上的连接 知道它是左 joi 并且它也没有被起诉 选定的列也不在 where 条件中。

我强制使用嵌套循环提示,如下所示:

SELECT /*+ USE_NL(t TD)*/
TU.ID AS RecordID
FROM TABLE_U TU
JOIN (SELECT to_date('11-04-2001','DD-MM-YYYY') AS PROCESS_DATE FROM DUAL
UNION ALL SELECT to_date('10-04-2001','DD-MM-YYYY') AS PROCESS_DATE FROM DUAL
)  PDATE ON (1=1)
JOIN TABLE_T t ON (t.PROCESSOR = TU.PROCESSOR AND TU.TTD = t.ID AND t.FROMDATE <=PDATE.PROCESS_DATE AND t.TODATE >= PDATE.PROCESS_DATE)
LEFT JOIN TABLE_TD TD ON (TD.PROCESSOR = t.PROCESSOR AND TD.ABC = t.ID)

WHERE t.CREATED <=PDATE.PROCESS_DATE
AND TU.FROMDATE <= PDATE.PROCESS_DATE

新计划更正确,查询执行时间不到1秒!!:

Plan hash value: 3532208304
 
--------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                              | Name                            | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                       |                                 |  3745 |   369K| 42448   (1)| 00:00:02 |
|   1 |  NESTED LOOPS OUTER                    |                                 |  3745 |   369K| 42448   (1)| 00:00:02 |
|   2 |   NESTED LOOPS                         |                                 |  3745 |   303K| 34957   (1)| 00:00:02 |
|   3 |    NESTED LOOPS                        |                                 |  7797 |   388K|  3853   (1)| 00:00:01 |
|   4 |     VIEW                               |                                 |     2 |    18 |     4   (0)| 00:00:01 |
|   5 |      UNION-ALL                         |                                 |       |       |            |          |
|   6 |       FAST DUAL                        |                                 |     1 |       |     2   (0)| 00:00:01 |
|   7 |       FAST DUAL                        |                                 |     1 |       |     2   (0)| 00:00:01 |
|   8 |     TABLE ACCESS BY INDEX ROWID BATCHED| TABLE_T                         |  3899 |   159K|  1924   (1)| 00:00:01 |
|*  9 |      INDEX RANGE SCAN                  | TABLE_T_FROMDATE                |    23 |       |  1916   (1)| 00:00:01 |
|* 10 |    TABLE ACCESS BY INDEX ROWID BATCHED | TABLE_U                         |     1 |    32 |     4   (0)| 00:00:01 |
|* 11 |     INDEX RANGE SCAN                   | PK_TABLE_U                      |    10 |       |     3   (0)| 00:00:01 |
|* 12 |   INDEX RANGE SCAN                     | TABLE_TD_IDX                    |     1 |    18 |     2   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   9 - access("T"."TODATE">="PDATE"."PROCESS_DATE" AND "T"."FROMDATE"<="PDATE"."PROCESS_DATE" AND 
              "T"."CREATED"<="PDATE"."PROCESS_DATE")
       filter("T"."CREATED"<="PDATE"."PROCESS_DATE" AND "T"."TODATE">="PDATE"."PROCESS_DATE")
  10 - filter("TU"."FROMDATE"<="PDATE"."PROCESS_DATE")
  11 - access("TU"."TTD"="T"."ID" AND "T"."PROCESSOR"="TU"."PROCESSOR")
       filter("T"."PROCESSOR"="TU"."PROCESSOR")
  12 - access("TD"."ABC"(+)="T"."ID" AND "TD"."PROCESSOR"(+)="T"."PROCESSOR")

有什么解释为什么 Oracle 可以选择第一个执行计划以及如何在不强制提示的情况下解决这个问题?

oracle query-optimization sqlperformance
2个回答
2
投票
  1. 您正在使用

    TD
    而没有意识到。只需加入它,如果每个键找到多个
    TD
    行 (
    processor,abc
    ),您就可以将其他表的行数相乘。仅仅因为您没有在 SELECT 子句中应用过滤器或请求其中的列,并不意味着 join 没有执行任何操作。 Oracle 仍然需要执行连接。

  2. Oracle 认为在达到 TD 之前进行其他连接后,它将有 3,745 行。这刚刚超过了预测哈希连接比嵌套循环更便宜的成本数学阈值。如果您会注意到您暗示的版本,它的计算成本为 42K,而其首选计划为 35K - 数字非常接近,因此它接近拐点。微小的变化可能会导致这两个计划之间来回翻转。这对于 Oracle 来说是很典型的,随着数据随时间的变化,Oracle 总是试图选择它认为最好的计划。当然,它是不完美的,并且经常会在计算中加入不正确的假设,从而导致不正确的成本计算,这意味着糟糕的计划。我们尽力为其提供最好的统计数据,如果仍然存在问题,提示可以在修复问题并保持稳定方面发挥作用。话虽如此,不要认为 Oracle 在这里是错的。

  3. 现在,我怀疑你没有正确安排测试时间。 1s 听起来像是您正在计时出现的第一行(响应时间),而不是计时直到获取最后一行(吞吐量)。从散列连接更改为嵌套循环,以便每个连接都是嵌套循环,绝对可以立即为您提供< 1s response (first row) because that's how nested loops work - you get the first row before it has worked on the next row, etc. If it's nested loops all the way down, you'll see that first row in your client right away. If you have but a single hash join anywhere, you'll have to wait until that hash join has been completed for all rows before you see your first row. If this is what is happening, then you need to change your test. Wrap your query in a

    SELECT COUNT(*) FROM ([query])
    ,以便测试整个 SQL。我怀疑你会发现Oracle原来的计划和你的新计划几乎一样,甚至稍微好一点。

  4. 要实际调整您的查询,您需要找出它把时间花在哪里。运行一段时间后,请查阅 ASH (

    v$active_session_history
    ) 来获取 SQL 的
    sql_id
    ,您可以从
    v$session
    获取该信息。查看
    sql_plan_line_id
    sql_plan_operation
    event
    并计算行数。例如:

     SELECT sql_plan_line_id,sql_plan_operation,event,COUNT(*)
       FROM v$active_session_history
      WHERE sql_id = 'gdvh64xqz9wcm'
        AND sample_time > SYSDATE - 1/24
      GROUP BY sql_plan_line_id,sql_plan_operation,event
      ORDER BY COUNT(*) DESC
    

    这将显示您在哪条计划线和哪个事件上花费了多少秒 (

    COUNT
    )。这将告诉您查询中问题出在哪里。修复它取决于问题是什么。但这应该可以帮助您开始。


0
投票

在某些 Oracle 数据库上的经验与 19C 相同 - 错误地采用哈希连接而不是嵌套循环并导致缓慢。

© www.soinside.com 2019 - 2024. All rights reserved.