问题描述
如何确保使用 optimizer_mode = ALL_ROWS
刷新物化视图?
背景:我正在将 mview 从 ALL_ROWS 数据库迁移到 FirsT_ROWS 数据库,并且不想丢失设置,因为与 ALL_ROWS / 哈希连接相比,FirsT_ROWS / 嵌套循环的刷新时间要长几个数量级。
mview 构建在视图之上,并且在 PL/sql 过程中刷新了几个类似的 mview。
我已经尝试了一些最小的例子,看起来优先级是
这是正确的吗?
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;
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 */
,对吗?
解决方法
您是对的,优化器设置的优先级按优先级递增的顺序是:
- 系统参数
- 会话设置
- 查询块级提示
- 语句级或父查询块级提示
如果在最外层查询中设置了 /*+ 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
提示总是有效的。