aRv*_*Rvi 4 oracle performance join predicate view
这与我们面临的涉及表和视图(具有70+百万条记录)的联接的查询所面临的性能问题有关。
在围绕不同环境下的执行计划进行了广泛的分析之后,我可以将其指向其中一个联接的VIEW PUSHED PREDICATE分支。
执行次数(执行计划的“开始”列)等于驱动/外部表上返回的行数-可能是在评估外部结果集上的每个匹配项的视图。
由于此处涉及的表具有数百万条记录,因此%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;
Run Code Online (Sandbox Code Playgroud)
执行计划(使用TABLE(DBMS_XPLAN.display_cursor)提取):
我指的是计划中的3号和4号线-“开始”列的值为70(等于2号线的A-Rows列的值-驱动表访问)
-----------------------------------------------------------------------------------------------------------------------------------
| 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))
Run Code Online (Sandbox Code Playgroud)
没错,该VIEW PUSHED PREDICATE操作的意思是“因此视图已变得相关,必须针对外部查询块的每一行对其进行评估”。
这是一种特定的谓词推送,即join谓词下推转换。这种转换背后的想法是,可能需要更频繁地执行视图,但是将联接谓词添加到视图可以使其运行更快,因为该视图中的表现在可以使用索引访问。
连接谓词下推没有内在的错误。类似于笛卡尔积,多次执行视图不一定是不好的。有时,大量的快速事物要比少量的缓慢事物好。
那么,为什么Oracle在这里做出错误的选择?没有太多数据很难说。Oracle正在使用近似如下的方程式做出确定:
large number * small amount of time
<
small number * large amount of time
Run Code Online (Sandbox Code Playgroud)
更详细一点:
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
Run Code Online (Sandbox Code Playgroud)
与大多数查询调优一样,请检查基数。
也许Oracle大大低估了“大数字”,并认为外部表返回的行比实际小得多。发生这种情况的原因有很多,例如错误的统计信息,使用了优化器无法估计的许多令人困惑的功能,使用了Oracle无法理解的关联列(除非您创建了多列直方图)等等。
也许Oracle大大低估了“少量时间”。它可能认为视图的索引访问路径比实际速度快得多。可能是由于上述原因之一,也可能是因为有人弄乱了一些关键参数。不幸的是,人们通常会想到:“索引速度很快,我应该通过更改参数默认值来告诉Oracle更频繁地使用它们”。运行此查询,并确保值为默认值0和100。
select * from v$parameter where name in ('optimizer_index_cost_adj', 'optimizer_index_caching');
Run Code Online (Sandbox Code Playgroud)
在实践中,优化器问题几乎总是由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;
/
Run Code Online (Sandbox Code Playgroud)
错误来了。假设有人在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;
Run Code Online (Sandbox Code Playgroud)
使用STATUS在STATUS_DETAIL和内联视图之间运行查询。为了防止视图合并,我在查询中添加了a join和a 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;
Run Code Online (Sandbox Code Playgroud)
以下是错误的执行计划。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")
Run Code Online (Sandbox Code Playgroud)
如果我们收集统计信息并告诉Oracle STATUS表的实际大小,情况将大为不同。100,000次索引扫描是一种访问表中每一行的缓慢方法。相反,新计划哈希将STATUS表连接在一起,然后哈希将结果与STATUS_DETAIL连接。我的电脑上的运行时间从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")
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
2979 次 |
| 最近记录: |