这与我们所面临的性能问题有关,该问题涉及一个涉及表和视图(具有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))
答案 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和内联视图之间运行查询。为了防止视图合并,我在查询中添加了join
和distinct
,以使其难以集成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")