【问题标题】:Forcing the optimizer to execute view independently强制优化器独立执行视图
【发布时间】:2014-09-01 01:53:11
【问题描述】:

简单表:Department 和 Employee,一对多等

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

但这执行了 5 分钟,因为 Oracle 搞乱了查询执行顺序。

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';

【问题讨论】:

    标签: sql view oracle11g query-optimization hint


    【解决方案1】:

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

    结果将包含许多复杂且未记录的提示。希望其中一些能提供线索,说明哪些提示是真正必要的。

    【讨论】:

      【解决方案2】:

      Oracle 11g 优化器在分析子查询时非常聪明(有时有点过于聪明)。它可能决定在内部将带有子查询的 SELECT 在 WHERE 中转换为平面连接。

      这取决于很多优化器和视图背后的查询。你需要重写你的查询,看看有什么用。

      你可以试试这个:

      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 是希望欺骗优化器并阻止它加入视图和两个表。

      【讨论】:

        猜你喜欢
        • 2015-01-25
        • 1970-01-01
        • 1970-01-01
        • 2018-06-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-04-28
        相关资源
        最近更新 更多