简单表:部门和员工,一对多等
复杂视图:Employee_contract_properties(EMPL_ID,PROPERTY_NAME,PROPERTY_VALUE)200行代码,3 000 000行数据等
立即执行。
SELECT e.id
FROM department d INNER JOIN employee e ON e.dept_id = d.id AND a.job = 'DBA'
WHERE d.name = 'IT'
结果: 5,7,13等
立即执行。
SELECT p.property_value
FROM employee_contract_properties p
WHERE p.empl_id IN (
5, 7, 13, etc
)
AND p.property_name = 'empl_cat_name';
但由于Oracle混乱了查询执行顺序,因此执行了5分钟。
SELECT p.property_value
FROM employee_contract_properties p
WHERE p.empl_id IN (
SELECT e.id
FROM department d INNER JOIN employee e ON e.dept_id = d.id AND a.job = 'DBA'
WHERE d.name = 'IT'
)
AND p.property_name = 'empl_cat_name';
我需要这样的东西,却找不到合适的提示。有这样的事吗?
SELECT p.property_value
FROM employee_contract_properties p
WHERE /*+ RESPECT_MY_AUTHORITAH */ p.empl_id IN (
SELECT e.id
FROM department d INNER JOIN employee e ON e.dept_id = d.id AND a.job = 'DBA'
WHERE d.name = 'IT'
)
AND p.property_name = 'empl_cat_name';
答案 0 :(得分:1)
在分析子查询时,Oracle 11g优化器非常聪明(有时候太聪明了)。它可能决定在内部将带有WHERE内部子查询的SELECT转换为平面连接。
这取决于很多优化器和视图背后的查询。您需要重写查询并查看哪些有效。
你可以试试这个:
SELECT p.property_value
FROM employee_contract_properties p
WHERE EXISTS (
SELECT 1
FROM department d INNER JOIN employee e ON e.dept_id = d.id AND a.job = 'DBA'
WHERE d.name = 'IT'
AND e.id + 0 = p.empl_id
)
AND p.property_name = 'empl_cat_name';
e.id + 0希望欺骗优化器,并认为没有理由将视图与子查询中的两个表连接起来。
你也可以试试这个:
SELECT p.property_value
FROM employee_contract_properties p,
(SELECT e.id + 0
FROM department d INNER JOIN employee e ON e.dept_id = d.id AND a.job = 'DBA'
WHERE d.name = 'IT') inl
WHERE p.empl_id = inl.id
AND p.property_name = 'empl_cat_name';
同样,e.id + 0希望欺骗优化器并阻止它加入视图和两个表。
答案 1 :(得分:0)
ROWNUM
是一种快速,简便的方法,可以防止Oracle优化器组合查询块。由于ROWNUM
用于Top-N报告的方式,它在执行计划的后期应用,这有助于禁用特定查询块的优化程序转换。
有一些提示可以实现相同的目标,也许是SELECT /*+ NO_UNNEST */ ...
。提示可能非常棘手,添加ROWNUM
通常更容易:
SELECT p.property_value
FROM employee_contract_properties p
WHERE p.empl_id IN (
SELECT id
FROM
(
SELECT e.id, rownum /* rownum added for performance */
FROM department d INNER JOIN employee e ON e.dept_id = d.id AND a.job = 'DBA'
WHERE d.name = 'IT'
)
)
AND p.property_name = 'empl_cat_name';
下面的代码演示了ROWNUM
如何用于停止优化器转换。
示例架构和数据
--drop table employee;
--drop table department;
create table department(dept_id number primary key, name varchar2(100));
create table employee(id number primary key, name varchar2(100), dept_id number,
constraint employee_if foreign key (dept_id) references department(dept_id));
insert into department select level, level
from dual connect by level <= 100;
insert into employee select level, level, mod(level, 100)+1
from dual connect by level <= 100000;
begin
dbms_stats.gather_table_stats(user, 'department');
dbms_stats.gather_table_stats(user, 'employee');
end;
/
查询受益于转化
下面是一个非常倒退的写作方式“给我一个属于部门'2的员工1'。”而不是将employee.id = 1
放在EMPLOYEE表旁边的外部查询块中,而是将它放在子查询中。这演示了优化器转换如何通常可以帮助您自己。
explain plan for
select *
from employee
where dept_id in
(
select dept_id
from department
where department.dept_id = employee.dept_id
and employee.id = 1 --primary key predicate
and department.name = '2'
);
优化器足够智能,可以将上述查询重新编写为简单连接,并首先应用主键谓词。
select * from table(dbms_xplan.display(format => '-bytes'));
Plan hash value: 4183381282
-------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 3 (0)| 00:00:01 |
| 1 | NESTED LOOPS | | 1 | 3 (0)| 00:00:01 |
| 2 | TABLE ACCESS BY INDEX ROWID| EMPLOYEE | 1 | 2 (0)| 00:00:01 |
|* 3 | INDEX UNIQUE SCAN | SYS_C0013597 | 1 | 1 (0)| 00:00:01 |
|* 4 | TABLE ACCESS BY INDEX ROWID| DEPARTMENT | 1 | 1 (0)| 00:00:01 |
|* 5 | INDEX UNIQUE SCAN | SYS_C0013596 | 1 | 0 (0)| 00:00:01 |
-------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("EMPLOYEE"."ID"=1)
4 - filter("DEPARTMENT"."NAME"='2')
5 - access("DEPT_ID"="DEPT_ID")
防止转型
添加ROWNUM
,这也需要添加另一个内嵌视图,因为in
只需要一个值。请务必添加注释以解释看似不必要的列的用途。
explain plan for
select *
from employee
where dept_id in
(
select dept_id
from
(
select dept_id, rownum /* rownum added for performance */
from department
where department.dept_id = employee.dept_id
and employee.id = 1
and department.name = '2'
)
);
既然阻止了子查询取消,执行计划必须遍历EMPLOYEE中的每一行并将子查询应用于它。在这种情况下,新计划要昂贵得多,但它证明了这一概念。
select * from table(dbms_xplan.display(format => '-bytes'));
Plan hash value: 532877948
----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 92867 (1)| 00:00:04 |
|* 1 | FILTER | | | | |
| 2 | TABLE ACCESS FULL | EMPLOYEE | 100K| 114 (1)| 00:00:01 |
|* 3 | VIEW | | 1 | 1 (0)| 00:00:01 |
| 4 | COUNT | | | | |
|* 5 | FILTER | | | | |
|* 6 | TABLE ACCESS BY INDEX ROWID| DEPARTMENT | 1 | 1 (0)| 00:00:01 |
|* 7 | INDEX UNIQUE SCAN | SYS_C0013596 | 1 | 0 (0)| 00:00:01 |
----------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter( EXISTS (SELECT 0 FROM (SELECT "DEPT_ID" "DEPT_ID",ROWNUM
"ROWNUM/*ROWNUMADDEDFORPERFORMA" FROM "DEPARTMENT" "DEPARTMENT" WHERE :B1=1 AND
"DEPARTMENT"."DEPT_ID"=:B2 AND "DEPARTMENT"."NAME"='2') "from$_subquery$_002"
WHERE "DEPT_ID"=:B3))
3 - filter("DEPT_ID"=:B1)
5 - filter(:B1=1)
6 - filter("DEPARTMENT"."NAME"='2')
7 - access("DEPARTMENT"."DEPT_ID"=:B1)
或使用ROWNUM
查找提示
即使您不打算在最终代码中使用此ROWNUM
技巧,它仍然会有所帮助。使用和不使用explain plan for ...
运行ROWNUM
。然后使用此语句比较每个版本的完整提示集:
select * from table(dbms_xplan.display(format => '+alias +outline'));
结果将包含许多复杂和未记录的提示。希望他们中的一些人能够提供关于哪些提示真正必要的线索。