强制优化器独立执行视图

时间:2014-07-10 15:40:38

标签: sql view oracle11g query-optimization hint

简单表:部门和员工,一对多等

复杂视图: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';

2 个答案:

答案 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'));

结果将包含许多复杂和未记录的提示。希望他们中的一些人能够提供关于哪些提示真正必要的线索。

相关问题