【问题标题】:Let Oracle transform OR-connected predicates into UNION ALL operations让 Oracle 将 OR 连接的谓词转换为 UNION ALL 操作
【发布时间】:2018-07-25 06:31:08
【问题描述】:

UNIONUNION ALL 查询在某些情况下可以胜过使用OR 连接谓词的等效查询。据我所知,这部分是因为UNION 子选择可以并行执行,因此它们可以有自己的“子计划”特定于OR-connected 谓词的每个部分,这可能是最优的,因为更简单的适用查询转换。

但是编写OR-connected 谓词通常更具可读性和简洁性,即使将子查询分解应用于UNION ALL 解决方案也是如此。我的问题是:有没有办法向 Oracle 表明,一个单一的、昂贵的 OR-connected 谓词应该转换为 UNION ALL 操作?如果有这样的提示/方法,在什么情况下可以应用(例如,谓词所涉及的列上是否需要存在任何约束等)?一个例子:

CREATE TABLE a AS
  SELECT 1 x, 2 y FROM DUAL UNION ALL
  SELECT 2 x, 1 y FROM DUAL;

-- This query...
SELECT * FROM a
WHERE x = 1 OR y = 1

-- Is sometimes outperformed by this one, for more complex table sources...
-- Note: in my case, I can safely apply UNION ALL. I know the two predicates to
-- be mutually exclusive.
SELECT * FROM a
WHERE x = 1
UNION ALL
SELECT * FROM a
WHERE y = 1

注意,我知道/*+ USE_CONCAT */ 提示:

SELECT /*+ USE_CONCAT */ * FROM a
WHERE x = 1 OR y = 1

但它似乎没有产生我需要的东西(执行计划中没有强制UNION ALL操作):

-------------------------------------------
| Id  | Operation         | Name | E-Rows |
-------------------------------------------
|   0 | SELECT STATEMENT  |      |        |
|*  1 |  TABLE ACCESS FULL| A    |      2 |
-------------------------------------------

也许,这个提示有一些限制?我有 Oracle 11g2 可用于此。

【问题讨论】:

  • 条件x = 1 or y = 1 将返回多少行(占所有行的百分比)(在真实表中)?在“或”查询中使用PARALLEL 提示怎么样?
  • @a_horse_with_no_name:实际上,(真实的)条件是(flag_function() = 1 and condition1) or (flag_function() = 0 and condition2) 的形式。这两个子条件相互排斥,具体取决于 PL/SQL flag_function()。我注意到 Oracle 在使用 UNION ALL 而不是使用 OR 时为此创建了一个更好的计划。 PARALLEL 可能不会有太大帮助,因为在这种情况下数据量不是很大,但计划很复杂......此外,这个查询在用户会话中运行得非常频繁。我不想用PARALLEL 提示占用太多资源
  • 这些查询是不等价的。您应该使用 UNION 而不是 UNION ALL。当 x 和 y 都等于 1 的行时,您会得到不同的结果。
  • @GriffeyDog:是的。我知道数据并以它们等效的方式定制查询(具有先验知识)。我会更新问题

标签: sql oracle optimization union union-all


【解决方案1】:

我相信这可能与您在 OR 谓词中使用的列上存在的索引有关。

我在 11gR2 中使用以下内容进行了测试。

create table scott.test as 
select level l, 
       decode(mod(level,2), 1, 1, 2) x, 
       decode(mod(level,2), 1, 2, 1) y, 
       dbms_random.value(1, 3) z from dual 
connect by level < 1000;
/

begin
   dbms_stats.gather_table_stats('scott', 'test');
end;
/

然后我在 TOAD 中解释了以下查询,(EXPLAIN PLAN FOR)

select x, y, z from scott.test
    where (floor(z) = 1 and x = 1) or (floor(z) = 2 and y = 1)
    ;

SELECT STATEMENT Optimizer Mode=ALL_ROWS        10          4                                
  TABLE ACCESS FULL COS_DM.TEST 10      280     4   

select /*+ USE_CONCAT */ x, y, z from scott.test
where (floor(z) = 1 and x = 1) or (floor(z) = 2 and y = 1)
;

SELECT STATEMENT Optimizer Mode=ALL_ROWS        10          4                                
  TABLE ACCESS FULL COS_DM.TEST 10      280     4                                


select x, y, z from test where (floor(z) = 1 and x = 1)
union all
select x, y, z from test where (floor(z) = 2 and y = 1)
;

SELECT STATEMENT Optimizer Mode=ALL_ROWS        10          8                                
  UNION-ALL                                              
    TABLE ACCESS FULL   COS_DM.TEST 5   140     4                                
    TABLE ACCESS FULL   COS_DM.TEST 5   140     4                                

所以提示似乎不起作用。然后我在 x & y 列中添加了一个索引:

create index test_x on test (x, y);

begin
   dbms_stats.gather_table_stats('scott', 'test');
end;
/

现在重新运行查询:

select x, y, z from scott.test
    where (floor(z) = 1 and x = 1) or (floor(z) = 2 and y = 1)
    ;

SELECT STATEMENT Optimizer Mode=ALL_ROWS        10          4                                
  TABLE ACCESS FULL COS_DM.TEST 10      280     4   

select /*+ USE_CONCAT */ x, y, z from scott.test
where (floor(z) = 1 and x = 1) or (floor(z) = 2 and y = 1)
;

SELECT STATEMENT Optimizer Mode=ALL_ROWS        10          8                                
  CONCATENATION                                              
    TABLE ACCESS FULL   COS_DM.TEST 5   140     4                                
    TABLE ACCESS FULL   COS_DM.TEST 5   140     4                                

select x, y, z from test where (floor(z) = 1 and x = 1)
union all
select x, y, z from test where (floor(z) = 2 and y = 1)
;

SELECT STATEMENT Optimizer Mode=ALL_ROWS        10          8                                
  UNION-ALL                                              
    TABLE ACCESS FULL   COS_DM.TEST 5   140     4                                
    TABLE ACCESS FULL   COS_DM.TEST 5   140     4                                

似乎在添加索引后(即使它没有被使用),优化器最终还是决定使用提示!

也许你可以试试这个?

【讨论】:

  • 非常感谢您的分析!实际上,xy 上(由其表示的实际列)没有索引。我会尝试在x 上添加索引。但是,y 不能在实际查询中轻松索引,因为它源自 LEFT OUTER JOIN...
  • 优化器对这些东西很挑剔。您可能会发现它也可能会尝试使用位图将位图转换为行 ID,或者如果您独立索引每一列。
  • 好的,我明白了,我会尽力跟进这些事情。经过一番调查,事实证明 CONCATENATION 真的 is 应用于真正的查询(我犯了一个错误)。但是您仍然回答了我的简化问题,这似乎过于简化了... :)
【解决方案2】:

在某些情况下,优化器会忽略提示,您必须以不同的方式编写查询。使用 UNION ALL 重写所有查询的替代方法,您可以只重写子句:

SELECT * FROM a /* ... you can put here more joins with many tables ... */
WHERE a.rowid in (
    select innerQry.rowid from a innerQry where /*your first clause of OR*/innerQry.x = 1
    union all
    select innerQry.rowid from a innerQry where /*your second clause of OR*/innerQry.y = 1
)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-06-20
    • 2015-07-03
    • 1970-01-01
    • 2021-10-21
    • 2016-06-17
    • 2017-06-11
    • 1970-01-01
    • 2013-10-07
    相关资源
    最近更新 更多