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'));
结果将包含许多复杂且未记录的提示。希望其中一些能提供线索,说明哪些提示是真正必要的。