【问题标题】:Tuning of complex query复杂查询的调优
【发布时间】:2014-09-04 07:48:23
【问题描述】:

我有一个需要很长时间才能执行的查询。我尝试使用提示并在 where 子句中的列上创建索引,但没有帮助。根据解释计划,聚合函数需要更多成本,对我来说,拥有它们是绝对必要的。反正我可以调吗?

 INSERT
  INTO tab3
  (CDE,
  SOURCE,
  SCENARIO,
  ID_COUNT,
  AMOUNT)
SELECT /*+ parallel(t,8) */ 'BENEFICIARY' AS CDE, 'MTS' AS SOURCE,'Match on Value' AS SCENARIO,COUNT(T.BA1) AS ID_COUNT,SUM(T.AMT) AS AMOUNT
FROM tab1 E
JOIN tab2 T
ON E.AA1 = T.BA1
WHERE (CASE WHEN E.AF1 = 'Y'
        THEN replace_word(E.AF2)
        ELSE replace_word(E.AF3)
    END) = UPPER(TRIM(T.BF1))
AND E.AF5 = '001'
AND E.AF6 = 'Y'
AND T.BF2 = '001';

如果我将此查询作为过程的一部分并选择查询作为游标,然后使用批量收集插入到 tab3,会有帮助吗?在此先感谢您的时间。我们的数据库是 Oracle 11g。

编辑: 添加上述查询的解释计划:

-------------------------------------------------------------------------------------------------------------------------

| Id  | Operation                      | Name                           | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |

-------------------------------------------------------------------------------------------------------------------------

|   0 | INSERT STATEMENT               |                                |     1 |   127 |       |  1458K  (1)| 04:51:40 |

|   1 |  LOAD TABLE CONVENTIONAL       | tab3                           |       |       |       |            |          |

|   2 |   SORT AGGREGATE               |                                |     1 |   127 |       |            |          |

|*  3 |    HASH JOIN                   |                                | 10005 |  1240K|    76M|  1458K  (1)| 04:51:40 |

|*  4 |     TABLE ACCESS BY INDEX ROWID| tab1                           |  1000K|    64M|       |   432K  (1)| 01:26:33 |

|*  5 |      INDEX RANGE SCAN          | IDX_AF5                        |  2000K|       |       |  4483   (1)| 00:00:54 |

|*  6 |     TABLE ACCESS FULL          | AF5                            |  3538K|   199M|       |  1009K  (1)| 03:22:00 |

-------------------------------------------------------------------------------------------------------------------------


Predicate Information (identified by operation id):

---------------------------------------------------


   3 - access(CASE "E"."AF1" WHEN 'Y' THEN "REPLACE_WORD"("E"."AF2") ELSE 

              "REPLACE_WORD"("E"."AF3") END =UPPER(TRIM("T"."BF1")) AND 

              "E"."AA1"="T"."BA1")

   4 - filter("E"."AF6"='Y')

   5 - access("E"."AF5"='001')

   6 - filter("T"."BF2"='001')

【问题讨论】:

  • 请显示replace_word函数的代码
  • 永远记住,SQL 比 PL/SQL 快。所以,不要考虑在 PL/SQL 中执行当前的 SQL。正如 zaratustra 已经询问过用户定义的函数 replace_word ,此外,您真的需要使用函数 TRIM 和 UPPER 吗?如有任何与性能相关的问题,请务必发布执行计划。
  • @zaratustra replace_word 是一个非常简单的删除垃圾字符的函数,而且不消耗资源。
  • @Sid,请发布执行计划。
  • 您发布了显示您估计的解释计划。要获得真正的执行计划,请执行以下操作:“alter session set statistics_level = all”,运行您的插入语句,然后执行“select * from table(dbms_xplan.display_cursor(null,null,'ALLSTATS LAST'))”

标签: sql oracle performance


【解决方案1】:

性能调优需要大量信息。这个问题包含完整的查询和执行计划,比大多数性能问题要多得多。但正如 Rob van Wijk 所提到的,了解实际时间和返回的行数很重要,而不仅仅是估计。令人惊讶的是,当真实数字如此接近时,有多少人只根据估计进行猜测。 Rob 的方法会起作用,虽然我更喜欢使用 select dbms_sqltune.report_sql_monitor(sql_id => 'your sql_id') from dual;

根据所提供的信息,以下是一些提示:

  1. 语句级并行性。 将提示 SELECT /*+ parallel(t,8) */ ... 替换为 SELECT /*+ parallel(8) */ ...。当前查询使用对象级提示,它仅指示优化器考虑使用并行性进行一次全表扫描。语句级提示告诉优化器对整个语句使用并行性。这可能会启用并行索引访问。
  2. 并行 DML。 如果插入了很多行,数据不需要立即恢复,没有锁定问题等,INSERT /*+ APPEND PARALLEL(8) */ ... 之类的提示可能会有很大帮助。将LOAD TABLE CONVENTIONAL 更改为直接路径插入LOAD AS SELECT,有时可以将性能提高几个数量级。让它工作可能很棘手,会话还需要alter session enable parallel dml;,表可能需要在NOLOGGING,可能需要禁用外键等。
  3. replace_word。 自定义函数可能会导致 SQL 语句出现许多问题。如果多次调用上下文切换,可能会降低性能。根据您的other question,该函数将阻止并行性,因为它不是用PARALLEL_ENABLE 定义的。并且优化器无法估计函数的选择性,尽管associate statistics 有时会有所帮助。如果可能的话,用内联视图或其他一些声明性方法替换查询可能会更好。此外,您肯定想删除异常处理程序,它只会掩盖错误的行号。
  4. 收集统计数据。 正如其他人所说,优化器统计数据需要准确,Oracle 才能构建准确的计划。希望数据库仍然启用了默认的统计信息收集任务。否则,通常调用这样的过程就足够了:exec dbms_stats.gather_table_stats('schema_name', 'E');。即使这没有任何改变,问题应该始终说明“统计数据是最新的”,因为它通常是首先要查看的内容。

【讨论】:

    【解决方案2】:

    摆脱从查询中调用函数:

    create or replace function fun1(
      p_var in varchar2
    )
    return varchar2
    is
    begin
      return replace(p_var, 'a', 'aaa');
    end fun1;
    
    declare
      l_start number := dbms_utility.get_cpu_time;
    begin
      for i in (select fun1(object_name) from all_objects where rownum < 10000)
      loop
        null;
      end loop;
      DBMS_OUTPUT.put_line((dbms_utility.get_cpu_time - l_start) || ' hsec');
    end;
    
    302 hsec
    
    
    declare
      l_start number := dbms_utility.get_cpu_time;
    begin
      for i in (select replace(object_name, 'a', 'aaa') from all_objects where rownum < 10000)
      loop
        null;
      end loop;
      DBMS_OUTPUT.put_line((dbms_utility.get_cpu_time - l_start) || ' asdf');
    end;
    
    268 hsec
    

    如您所见,我有大约 0.3 秒的利润,这是针对 10K 条记录的。我每次都跑了好几次来蠕动缓冲区缓存,

    【讨论】:

      【解决方案3】:

      你至少可以做两件事:

      1. 函数replace_word的缓存结果,使用RESULT_CACHE IS (http://www.oracle-developer.net/display.php?id=504)

      2. 在 UPPER(TRIM(T.BF1)) 上制作 函数索引

      根据 (http://www.dba-oracle.com/oracle_tips_null_idx.htm) 索引空列,您可以这样:

      create index
      func_idx on
      tab2 T
      (UPPER(TRIM(T.BF1)), 1);
      

      使用 pl/sql 将无济于事,事情可能会变得比以前更糟。

      1. 您可以尝试在插入中附加提示。我推荐这个教程:http://www.akadia.com/services/ora_insert_append.html

      【讨论】:

      • 谢谢。但是 T.BF1 可以有 null 值,所以想知道它是否真的有帮助?
      • 还想知道 PL/SQL 块是否有帮助?
      • 在提出可能的解决方案之前,最好先分析当前花费的时间。
      • 我已经编辑了一个答案。 @RobvanWijk - 部分同意,但有时(尤其是当你没有时间时)最好有任何帮助。
      猜你喜欢
      • 2012-01-07
      • 1970-01-01
      • 1970-01-01
      • 2020-11-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多