Oracle查询性能:VIEW PUSHED PREDICATE导致对内部查询的多次执行

时间:2018-07-13 18:10:45

标签: oracle performance join view predicate

这与我们所面临的性能问题有关,该问题涉及一个涉及表和视图(具有70+百万条记录)的联接的查询。

在对不同环境下的执行计划进行了广泛的分析之后,我可以将其指向其中一个联接的“查看推送的预测”分支。

  

执行次数(执行计划的开始列)等于   在驱动程序/外部表上返回的行数-可能是   评估外部结果集上每个匹配项的视图。

由于此处涉及的表具有数百万条记录, %CPU和总体执行时间变得非常糟糕。如果我添加了不推送谓词的提示,则不是这种情况(no_push_pred);处决只有1个。

  

这是VIEW PUSHED PREDICATE所期望的,还是我错过了?   有什么想法吗?

Oracle数据库版本:12c企业版12.1.0.2.0


我尝试使用一个简单的查询来模拟问题(或行为)-请在下面查看详细信息。

注意:此处添加了no_merge提示,以确保Optimizer在联接期间不会合并视图,因此该计划与我的实际查询的计划相同。

查询:

SELECT 
v.STATUS_CODE,
    a1.STATUS_DESC
    FROM STATUS_DETAIL a1,
    (select   /*+ no_merge push_pred */
        a2.STATUS_CODE
          from STATUS a2
             where  a2.STATUS_CODE < 50) v
  where a1.STATUS_CODE = v.STATUS_CODE;

执行计划(使用TABLE(DBMS_XPLAN.display_cursor)提取):

  

我指的是计划中的3号和4号线-“开始”列具有   值70(等于2号线的A-行列的值-驱动   表访问)

 -----------------------------------------------------------------------------------------------------------------------------------                                                                                                                                                                         
| Id  | Operation               | Name          | Starts | E-Rows |E-Bytes| Cost (%CPU)| E-Time   | A-Rows |   A-Time   | Buffers |                                                                                                                                                                         
-----------------------------------------------------------------------------------------------------------------------------------                                                                                                                                                                         
|   0 | SELECT STATEMENT        |               |      1 |        |       |   213 (100)|          |     22 |00:00:00.01 |     350 |                                                                                                                                                                         
|   1 |  NESTED LOOPS           |               |      1 |     13 |   533 |   213   (0)| 00:00:01 |     22 |00:00:00.01 |     350 |                                                                                                                                                                         
|   2 |   TABLE ACCESS FULL     | STATUS_DETAIL |      1 |     70 |  1960 |     3   (0)| 00:00:01 |     70 |00:00:00.01 |       7 |                                                                                                                                                                         
|   3 |   VIEW PUSHED PREDICATE |               |     70 |      1 |    13 |     3   (0)| 00:00:01 |     22 |00:00:00.01 |     343 |                                                                                                                                                                         
|*  4 |    FILTER               |               |     70 |        |       |            |          |     22 |00:00:00.01 |     343 |                                                                                                                                                                         
|*  5 |     TABLE ACCESS FULL   | STATUS        |     49 |      1 |     4 |     3   (0)| 00:00:01 |     22 |00:00:00.01 |     343 |                                                                                                                                                                         
----------------------------------------------------------------------------------------------------------------------------------- 



Predicate Information (identified by operation id):                                                                                                                                                                                                                                                         
---------------------------------------------------                                                                                                                                                                                                                                                         

   4 - filter("A1"."STATUS_CODE"<50)                                                                                                                                                                                                                                                                        
   5 - filter(("A2"."STATUS_CODE"="A1"."STATUS_CODE" AND "A2"."STATUS_CODE"<50))

1 个答案:

答案 0 :(得分:3)

您是正确的,VIEW PUSHED PREDICATE操作的意思是“视图因此变得相关,因此必须为外部查询块的每一行进行评估”。

这是一种特殊的谓词推送,即join predicate pushdown转换。这种转换背后的想法是,可能需要更频繁地执行视图,但是将联接谓词添加到视图可以使其运行更快,因为该视图中的表现在可以使用索引访问。

join谓词下推没有内在的错误。类似于笛卡尔积,多次执行视图不一定是不好的。有时,大量的快速事物要比少量的缓慢事物好。

那么,为什么Oracle在这里做出错误的选择?没有太多数据很难说。 Oracle正在使用大致如下的方程式做出决定:

large number * small amount of time
<
small number * large amount of time

更多细节:

rows returned by outer query * time for index-accessed view
<
1 (for a hash join) * read smaller table, create hash function, then read the other table and probe it for matches, potentially writing and reading to temporary tablespace

就像大多数查询调整一样,请检查基数。

也许Oracle大大低估了“大数字”,并认为外部表返回的行比实际小得多。发生这种情况的原因可能很多,例如错误的统计信息,使用了优化器无法估计的许多令人困惑的功能,使用了Oracle无法理解的关联列(除非您创建了多列直方图)等等。

也许Oracle大大低估了“少量时间”。它可能认为视图的索引访问路径比实际速度快得多。可能是由于上述原因之一,也可能是因为有人弄乱了一些关键参数。不幸的是,人们通常会想到:“索引速度很快,我应该通过更改参数默认值来告诉Oracle更频繁地使用它们”。运行此查询并确保值是默认值0和100。

select * from v$parameter where name in ('optimizer_index_cost_adj', 'optimizer_index_caching');

在实践中,优化器问题几乎总是由Oracle低估而不是高估引起的。因此,我将重点放在等式的左侧。 Oracle一直在尝试抛出尽可能多的行,并且将始终寻找将基数降低到1的方法。如果确实存在从表中仅抽取一行的路径,则可以节省很多工作。但是,如果只有看起来像一条通往单行的快速路径的方法,而并非如此,那么它可能会弄乱整个计划。

如果这是一个包含大量详细信息的繁琐查询,那么放弃尝试寻找根本原因并不是没有道理的,只需使用提示或额外的rownum伪列来迫使Oracle停止进行转换。

Oracle提供了大量的数据结构和算法来访问数据。这为优化器提供了许多方法来找到运行查询的更快方法。但这也使它有更多的机会犯错。没有风险就没有回报,但是没有必要对每个查询进行赌博。如果您有一个外部查询和一个内部查询分别工作良好,但不能很好地协同工作,则一定要将它们分开,并且不要让Oracle尝试以一种怪异的方式组合它们。


下面是一个快速示例,它使用与查询中的表相似的表。它显示Oracle错误地使用了VIEW PUSHED PREDICATE操作。

首先,创建一些小表,插入数据并收集统计信息。到目前为止一切都很好。

drop table status_detail;
drop table status;

create table status_detail(status_code number, status_desc varchar2(100));
insert into status_detail select level, level from dual connect by level <= 10;

create table status(status_code number);
create index status_idx on status(status_code);

insert into status select level from dual connect by level <= 100000;
begin
    dbms_stats.gather_table_stats(user, 'status_detail');
    dbms_stats.gather_table_stats(user, 'status');
end;
/

这是错误。假设有人在STATUS_DETAIL表中加载了100,000行,但忘记了重新收集统计信息。 Oracle认为外部表只有10行,但实际上有100,000行。

insert into status_detail select 1, level from dual connect by level <= 100000;
commit;

alter system flush shared_pool;

使用STATUS在STATUS_DETAIL和内联视图之间运行查询。为了防止视图合并,我在查询中添加了joindistinct,以使其难以集成A1和V。

explain plan for
select count(*)
from status_detail a1,
(
    select distinct a2.status_code
    from status a2
    join status a3
        on a2.status_code=a3.status_code
) v
where a1.status_code = v.status_code;

下面是错误的执行计划。由于错误的优化器统计信息,Oracle 认为 STATUS_DETAIL仅返回10行。该STATUS表很大,将其连接到自身将非常昂贵。 Oracle可以使用联接谓词下推而不是联接大表。通过将STATUS_CODE谓词传递到视图中,现在它可以对大型STATUS表使用简单的INDEX RANGE SCAN操作。 10个小的索引范围扫描听起来比将两个大表进行哈希连接更快。

select * from table(dbms_xplan.display);

Plan hash value: 3172146404

------------------------------------------------------------------------------------------
| Id  | Operation                | Name          | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT         |               |     1 |     5 |    23   (0)| 00:00:01 |
|   1 |  SORT AGGREGATE          |               |     1 |     5 |            |          |
|   2 |   NESTED LOOPS SEMI      |               |    10 |    50 |    23   (0)| 00:00:01 |
|   3 |    TABLE ACCESS FULL     | STATUS_DETAIL |    10 |    30 |     3   (0)| 00:00:01 |
|   4 |    VIEW PUSHED PREDICATE |               |     1 |     2 |     2   (0)| 00:00:01 |
|   5 |     NESTED LOOPS SEMI    |               |     1 |    10 |     2   (0)| 00:00:01 |
|*  6 |      INDEX RANGE SCAN    | STATUS_IDX    |     1 |     5 |     1   (0)| 00:00:01 |
|*  7 |      INDEX RANGE SCAN    | STATUS_IDX    |     1 |     5 |     1   (0)| 00:00:01 |
------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   6 - access("A2"."STATUS_CODE"="A1"."STATUS_CODE")
   7 - access("A3"."STATUS_CODE"="A1"."STATUS_CODE")
       filter("A2"."STATUS_CODE"="A3"."STATUS_CODE")

如果我们收集统计信息并告诉Oracle STATUS表的 real 大小,则情况看起来会大不相同。 100,000次索引扫描是一种访问表中每一行的缓慢方法。相反,新计划哈希将STATUS表连接在一起,然后哈希将结果与STATUS_DETAIL连接。 PC上的运行时间从0.5秒减少到0.1秒。

begin
    dbms_stats.gather_table_stats(user, 'status_detail');
    dbms_stats.gather_table_stats(user, 'status');
end;
/

explain plan for
select count(*)
from status_detail a1,
(
    select distinct a2.status_code
    from status a2
    join status a3
        on a2.status_code=a3.status_code
) v
where a1.status_code = v.status_code;

select * from table(dbms_xplan.display);

Plan hash value: 3579559806

---------------------------------------------------------------------------------------------------
| Id  | Operation                 | Name          | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT          |               |     1 |       |       |   556   (2)| 00:00:01 |
|   1 |  SORT AGGREGATE           |               |     1 |       |       |            |          |
|   2 |   VIEW                    | VM_NWVW_1     |    10 |       |       |   556   (2)| 00:00:01 |
|   3 |    HASH UNIQUE            |               |    10 |   190 |       |   556   (2)| 00:00:01 |
|*  4 |     HASH JOIN             |               |   100K|  1855K|  2056K|   552   (2)| 00:00:01 |
|   5 |      TABLE ACCESS FULL    | STATUS_DETAIL |   100K|   878K|       |    69   (2)| 00:00:01 |
|*  6 |      HASH JOIN SEMI       |               |   100K|   976K|  1664K|   277   (2)| 00:00:01 |
|   7 |       INDEX FAST FULL SCAN| STATUS_IDX    |   100K|   488K|       |    57   (2)| 00:00:01 |
|   8 |       INDEX FAST FULL SCAN| STATUS_IDX    |   100K|   488K|       |    57   (2)| 00:00:01 |
---------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   4 - access("A1"."STATUS_CODE"="A2"."STATUS_CODE")
   6 - access("A2"."STATUS_CODE"="A3"."STATUS_CODE")