如何在Oracle中为物化视图设置ALL_ROWS?

How to set ALL_ROWS for a materialized view in Oracle?

如何确保使用 optimizer_mode = ALL_ROWS 刷新实体化视图?

背景:我正在将 mview 从 ALL_ROWS 数据库迁移到 FIRST_ROWS 数据库并且不想丢失设置,因为 [= 刷新需要更长的数量级54=] / 嵌套循环与 ALL_ROWS / 散列连接相比。

mview 建立在视图之上,并且在 PL/SQL 过程中刷新了几个相似的 mview。

我尝试了一些最小的例子,看起来优先级是

  1. 在物化视图中提示/*+ ALL_ROWS */
  2. 如果未设置,则会在视图中观察到提示
  3. 如果mview和view都没有这样的提示,则观察session设置

这是正确的吗?

我已经在不带提示的情况下尝试了这些视图:

CREATE OR REPLACE VIEW v_def   AS SELECT /*+            */ * FROM all_objects;
CREATE OR REPLACE VIEW v_all   AS SELECT /*+ ALL_ROWS   */ * FROM all_objects;
CREATE OR REPLACE VIEW v_first AS SELECT /*+ FIRST_ROWS */ * FROM all_objects;

还有五个不带提示的 mviews:

CREATE MATERIALIZED VIEW mv_def       BUILD DEFERRED AS SELECT /*+            */ * FROM v_def;
CREATE MATERIALIZED VIEW mv_all       BUILD DEFERRED AS SELECT /*+            */ * FROM v_all;
CREATE MATERIALIZED VIEW mv_first     BUILD DEFERRED AS SELECT /*+            */ * FROM v_first;
CREATE MATERIALIZED VIEW mv_all_first BUILD DEFERRED AS SELECT /*+ ALL_ROWS   */ * FROM v_first;
CREATE MATERIALIZED VIEW mv_first_all BUILD DEFERRED AS SELECT /*+ FIRST_ROWS */ * FROM v_all;

当我使用程序刷新 mviews 时...

CREATE OR REPLACE PROCEDURE p_def IS 
BEGIN
  dbms_mview.refresh('mv_def',       atomic_refresh=>FALSE); 
  dbms_mview.refresh('mv_all',       atomic_refresh=>FALSE);
  dbms_mview.refresh('mv_first',     atomic_refresh=>FALSE);
  dbms_mview.refresh('mv_all_first', atomic_refresh=>FALSE);
  dbms_mview.refresh('mv_first_all', atomic_refresh=>FALSE);
END;
/

CREATE OR REPLACE PROCEDURE p_all IS 
BEGIN
  EXECUTE IMMEDIATE 'ALTER SESSION SET optimizer_mode = ALL_ROWS';
  p_def;
END;
/

CREATE OR REPLACE PROCEDURE p_first IS 
BEGIN
  EXECUTE IMMEDIATE 'ALTER SESSION SET optimizer_mode = FIRST_ROWS';
  p_def;
END;
/

...我得到以下结果:

mview          p_dev        p_all        p_first
-------------  ----------   ----------   ----------
MV_DEF         first_rows     all_rows   first_rows
MV_ALL           all_rows     all_rows     all_rows
MV_FIRST       first_rows   first_rows   first_rows
MV_ALL_FIRST     all_rows     all_rows     all_rows
MV_FIRST_ALL   first_rows   first_rows   first_rows

optimizer_mode的设置来自查询:

SELECT e.value as optimizer_mode, c.sql_id, substr(c.sql_text,1,100) as sql
  FROM v$sql c 
  LEFT JOIN v$sql_optimizer_env e 
    ON e.sql_id = c.sql_id ANd e.name = 'optimizer_mode'
 WHERE regexp_like(c.sql_text, 'BYPASS.*(v_def|v_all|v_first)');

所以,我需要保护 mviews 不受数据库设置 FIRST_ROWS 的影响,对吧? 我可以在使用 ALTER SESSION 语句刷新 mviews 的 PL/SQL 过程中执行此操作,希望没有其他人会直接刷新 mviews。或者我更改 mviews 的查询,添加提示 /*+ ALL_ROWS */,对吗?

你是对的,优化器设置的优先顺序是:

  1. 系统参数
  2. 会话设置
  3. 查询块级提示
  4. 语句级或父查询块级提示

如果在最外层查询中设置了 /*+ ALL ROWS*/ 提示,该提示将覆盖其他提示和设置。

我们如何证明优先规则是正确的?

虽然我在官方文档中找不到对上述规则的明确引用,但大多数规则都是相当明显的。前三个规则是有道理的,我们之前可能都见过它们的实际应用。在较高级别设置和应用配置,然后选择性地在较低级别覆盖配置是有意义的:首先针对整个系统,然后针对特定会话,最后针对单个查询。

不寻常的优先级是最后一个,高级语句提示覆盖低级查询块提示。幸运的是,我们可以使用 19c 提示报告来证明这个规则是正确的。

简单提示测试

以下测试用例显示 FIRST_ROWS 提示正在使用并显示在执行计划的“提示报告”部分。

--drop table test_table;
create table test_table(a number);

explain plan for select /*+ first_rows */ * from test_table;

select * from table(dbms_xplan.display(format => 'basic +hint_report'));
Plan hash value: 3979868219
 
----------------------------------------
| Id  | Operation         | Name       |
----------------------------------------
|   0 | SELECT STATEMENT  |            |
|   1 |  TABLE ACCESS FULL| TEST_TABLE |
----------------------------------------
 
Hint Report (identified by operation id / Query Block Name / Object Alias):
Total hints for statement: 1
---------------------------------------------------------------------------
 
   0 -  STATEMENT
           -  first_rows

父提示覆盖子提示

虽然您已经创建了一个测试用例,其中父查询块中的 ALL_ROWS 提示覆盖了子块中的 FIRST_ROWS 提示,但以下测试用例清楚地表明该行为不只是意外。 “提示报告”非常清楚地解释了“first_rows / 提示被父查询块中的另一个覆盖”。

explain plan for select /*+ all_rows */ * from (select /*+ first_rows */ * from test_table);

select * from table(dbms_xplan.display(format => 'basic +hint_report'));
Plan hash value: 3979868219
 
----------------------------------------
| Id  | Operation         | Name       |
----------------------------------------
|   0 | SELECT STATEMENT  |            |
|   1 |  TABLE ACCESS FULL| TEST_TABLE |
----------------------------------------
 
Hint Report (identified by operation id / Query Block Name / Object Alias):
Total hints for statement: 2 (U - Unused (1))
---------------------------------------------------------------------------
 
   0 -  STATEMENT
         U -  first_rows / hint overridden by another in parent query block
           -  all_rows

虽然这个答案不是行为的明确证据,但我相信它足以让我们相信在最外层查询中使用 ALL_ROWS 提示将始终有效。