Oracle开发者性能课第6课(如何创建物化视图)实验

Posted dingdingfish

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Oracle开发者性能课第6课(如何创建物化视图)实验相关的知识,希望对你有一定的参考价值。

概述

本实验参考DevGym中的实验指南

创建环境

创建表bricks和索引:

exec dbms_random.seed ( 0 );
create table bricks ( 
  brick_id not null constraint bricks_pk primary key,
  colour   not null,
  shape    not null,
  weight   not null,
  insert_date not null,
  junk     default lpad ( 'x', 50, 'x' ) not null 
) as
  with rws as (
    select level x from dual
    connect by level <= 10000
  )
    select rownum brick_id, 
           case ceil ( rownum / 2500 )
             when 4 then 'red'
             when 1 then 'blue'
             when 2 then 'green'
             when 3 then 'yellow'
           end colour, 
           case mod ( rownum, 4 )
             when 0 then 'cube'
             when 1 then 'cylinder'
             when 2 then 'pyramid'
             when 3 then 'prism'
           end shape,
           round ( dbms_random.value ( 1, 10 ) ),
           date'2020-01-01' + ( rownum/24 ) + ( mod ( rownum, 24 ) / 36 ) insert_date,
           lpad ( 'x', 50, 'x' )
    from   rws;

查看数据,bricks表有10000行:

SQL> select count(*) from bricks;

   COUNT(*)
___________
      10000


SQL>
SELECT
    brick_id,
    colour,
    shape,
    weight,
    insert_date
FROM
    bricks
WHERE
    ROWNUM <= 9;

   BRICK_ID    COLOUR       SHAPE    WEIGHT    INSERT_DATE
___________ _________ ___________ _________ ______________
          1 blue      cylinder            2 01-JAN-20
          2 blue      pyramid             8 01-JAN-20
          3 blue      prism               3 01-JAN-20
          4 blue      cube                3 01-JAN-20
          5 blue      cylinder            4 01-JAN-20
          6 blue      pyramid             2 01-JAN-20
          7 blue      prism               5 01-JAN-20
          8 blue      cube               10 01-JAN-20
          9 blue      cylinder            9 01-JAN-20

9 rows selected.

数百万行的聚合

报表查询通常将许多行聚合到少数行。例如:

select /*+ gather_plan_statistics */colour, count(*) 
from   bricks
group  by colour;

   COLOUR    COUNT(*)
_________ ___________
green            2500
red              2500
yellow           2500
blue             2500

select * from   table(dbms_xplan.display_cursor( format => 'iosTATS LAST'));

                                                                         PLAN_TABLE_OUTPUT
__________________________________________________________________________________________
SQL_ID  8qjfk54pd67ya, child number 0
-------------------------------------
select /*+ gather_plan_statistics */colour, count(*)  from   bricks
group  by colour

Plan hash value: 1511612015

---------------------------------------------------------------------------------------
| Id  | Operation          | Name   | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
---------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |        |      1 |        |      4 |00:00:00.01 |     119 |
|   1 |  HASH GROUP BY     |        |      1 |      4 |      4 |00:00:00.01 |     119 |
|   2 |   TABLE ACCESS FULL| BRICKS |      1 |  10000 |  10000 |00:00:00.01 |     119 |
---------------------------------------------------------------------------------------


15 rows selected.

此时可以创建摘要表,以提高查询效率:

create table colour_summary as 
  select colour, count(*)  c
  from   bricks
  group  by colour;

select /*+ gather_plan_statistics */*
from   colour_summary ;

select * from   table(dbms_xplan.display_cursor( format => 'IOSTATS LAST'));

                                                                                         PLAN_TABLE_OUTPUT
__________________________________________________________________________________________________________
SQL_ID  2j0426s6cky3v, child number 0
-------------------------------------
select /*+ gather_plan_statistics */* from   colour_summary

Plan hash value: 241215016

-------------------------------------------------------------------------------------------------------
| Id  | Operation         | Name           | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |
-------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |                |      1 |        |      4 |00:00:00.01 |       3 |      5 |
|   1 |  TABLE ACCESS FULL| COLOUR_SUMMARY |      1 |      4 |      4 |00:00:00.01 |       3 |      5 |
-------------------------------------------------------------------------------------------------------


13 rows selected.

但问题是,基表改变时,摘要表也需要相应改变。

使用物化视图创建摘要

注意执行计划中的MAT_VIEW ACCESS FULL

create materialized view brick_colours_mv
as
  select colour, count(*) 
  from   bricks
  group  by colour;

select /*+ gather_plan_statistics */* from brick_colours_mv;

SQL> select * from   table(dbms_xplan.display_cursor( format => 'IOSTATS LAST'));

                                                                                     PLAN_TABLE_OUTPUT
______________________________________________________________________________________________________
SQL_ID  2v361dp2652xg, child number 0
-------------------------------------
select /*+ gather_plan_statistics */* from brick_colours_mv

Plan hash value: 3196177224

---------------------------------------------------------------------------------------------------
| Id  | Operation            | Name             | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
---------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |                  |      1 |        |      4 |00:00:00.01 |       3 |
|   1 |  MAT_VIEW ACCESS FULL| BRICK_COLOURS_MV |      1 |      4 |      4 |00:00:00.01 |       3 |
---------------------------------------------------------------------------------------------------


13 rows selected.

和逻辑视图不同,物化视图就是物理表:

SQL> select segment_type from user_segments where segment_name = 'BRICK_COLOURS_MV';

   SEGMENT_TYPE
_______________
TABLE

可是目前为止,我们创建的物化视图还不能与基表同步。例如我们可以把表删除:

SQL> drop table bricks;

Table BRICKS dropped.

SQL> select /*+ gather_plan_statistics */* from brick_colours_mv;

   COLOUR    COUNT(*)
_________ ___________
green            2500
red              2500
yellow           2500
blue             2500

好吧,我们利用最初的语句恢复bricks表,并重建物化视图。

自动使用物化视图:查询重写(Query Rewrite)

Query Rewrite是优化器的一项功能,目的是为了不修改代码。如果运行了物化视图中使用的查询,优化器可以检测到这一点。并将查询更改为使用MV而不是基表!

需要启用Query Rewrite:

SQL> alter materialized view brick_colours_mv enable query rewrite;

Materialized view BRICK_COLOURS_MV altered.

然后就生效了,注意执行计划中的MAT_VIEW REWRITE

select /*+ gather_plan_statistics */colour, count(*) c
from   bricks
group  by colour;

select * 
from   table(dbms_xplan.display_cursor( format => 'IOSTATS LAST'));

                                                                                             PLAN_TABLE_OUTPUT
______________________________________________________________________________________________________________
SQL_ID  76wkhk51yu99d, child number 0
-------------------------------------
select /*+ gather_plan_statistics */colour, count(*) c from   bricks
group  by colour

Plan hash value: 1470497824

-----------------------------------------------------------------------------------------------------------
| Id  | Operation                    | Name             | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
-----------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |                  |      1 |        |      4 |00:00:00.01 |       3 |
|   1 |  MAT_VIEW REWRITE ACCESS FULL| BRICK_COLOURS_MV |      1 |      4 |      4 |00:00:00.01 |       3 |
-----------------------------------------------------------------------------------------------------------


14 rows selected.

对于可以由物化视图的结果派生出的其它查询,优化器也可以使用查询重写。例如:

select /*+ gather_plan_statistics */count(*) row#
from   bricks
where  colour = 'red'
group  by colour;

select * from   table(dbms_xplan.display_cursor( format => 'IOSTATS LAST'));

数据更改的影响

默认情况下,只有物化视图和基表的数据一致时,优化器才会考虑使用查询重写。

例如,对基表新增1行,优化器不再使用查询重写。

insert into bricks values ( 0, 'red', 'cube', 100, sysdate, default );
commit;

SQL>
 select /*+ gather_plan_statistics */colour, count(*) num_rows
  from   bricks
  group  by colour;

   COLOUR    NUM_ROWS
_________ ___________
green            2500
red              2501
yellow           2500
blue             2500

SQL> select * from   table(dbms_xplan.display_cursor( format => 'IOSTATS LAST'));

                                                                         PLAN_TABLE_OUTPUT
__________________________________________________________________________________________
SQL_ID  dp1b2x0mw27tv, child number 1
-------------------------------------
select /*+ gather_plan_statistics */colour, count(*) num_rows from
bricks group  by colour

Plan hash value: 1511612015

---------------------------------------------------------------------------------------
| Id  | Operation          | Name   | Starts | E-Rows | A-Rows |   A-Time   | Buffers |
---------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |        |      1 |        |      4 |00:00:00.01 |     121 |
|   1 |  HASH GROUP BY     |        |      1 |      4 |      4 |00:00:00.01 |     121 |
|   2 |   TABLE ACCESS FULL| BRICKS |      1 |  10000 |  10001 |00:00:00.01 |     121 |
---------------------------------------------------------------------------------------


15 rows selected.

使用陈旧数据

在某些情况下,您可能仍然希望使用过时的物化视图。例如,当查询旧数据时,您知道物化视图中的数据是正确的。或者,与其等很长时间等待结果,不如尽快得到过时的答案。

通过调整会话设置query_rewrite_integrity,您可以允许优化器在这些情况下使用过时的物化视图。

以下为示例:

SQL> show parameter query_rewrite_integrity
NAME                    TYPE   VALUE
----------------------- ------ --------
query_rewrite_integrity string enforced

SQL> alter session set query_rewrite_integrity = stale_tolerated;

Session altered.

SQL> select /* stale */count(*) c
  2  from   bricks;

       C
________
   10000

SQL> select * from   table(dbms_xplan.display_cursor( format => 'IOSTATS LAST'));

                                                                   PLAN_TABLE_OUTPUT
____________________________________________________________________________________
SQL_ID  guxqvbznnfh9f, child number 0
-------------------------------------
select /* stale */count(*) c from   bricks

Plan hash value: 2494147771

-------------------------------------------------------------------
| Id  | Operation                     | Name             | E-Rows |
-------------------------------------------------------------------
|   0 | SELECT STATEMENT              |                  |        |
|   1 |  SORT AGGREGATE               |                  |      1 |
|   2 |   MAT_VIEW REWRITE ACCESS FULL| BRICK_COLOURS_MV |      4 |
-------------------------------------------------------------------

Note
-----
   - Warning: basic plan statistics not available. These are only collected when:
       * hint 'gather_plan_statistics' is used for the statement or
       * parameter 'statistics_level' is set to 'ALL', at session or system level


20 rows selected.

将query_rewrite_integrity修改为默认,此时的查询结果就是最新的了。

SQL> alter session set query_rewrite_integrity = enforced;

Session altered.

SQL> select /* enforced */count(*)
  2  from   bricks;

   COUNT(*)
___________
      10001

使物化视图与基表保持一致

可以手动刷新,C表示Complete,也就是全量刷新。等于重新运行物化视图中的查询:

SQL> exec dbms_mview.refresh ( 'brick_colours_mv', 'C' );

PL/SQL procedure successfully completed.

物化视图快速刷新

也称为增量刷新。需要建立物化视图日志。其默认表名为MLOG$_<tablename>

create materialized view log 
  on bricks 
  with primary key, rowid, sequence, commit scn ( 
    colour, shape, weight, insert_date 
  )
  including new values;

Materialized view log BRICKS created.

修改基础表,然后可以看到日志中的修改记录:

update bricks
set    colour = 'green'
where  brick_id = 1;

delete bricks
where  brick_id = 2;

insert into bricks values ( -2, 'red', 'cube', 100, sysdate, default );

commit;

select * from mlog$_bricks;

   BRICK_ID    COLOUR       SHAPE    WEIGHT    INSERT_DATE               M_ROW$$    SEQUENCE$$    DMLTYPE$$    OLD_NEW$$    CHANGE_VECTOR$$               XID$$
___________ _________ ___________ _________ ______________ _____________________ _____________ ____________ ____________ __________________ ___________________
          1 blue      cylinder            2 01-JAN-20      AAASeTAAMAAA6AjAAA                1 U            O            04                    1125964331378063
          1 green     cylinder            2 01-JAN-20      AAASeTAAMAAA6AjAAA                2 U            N            04                    1125964331378063
          2 blue      pyramid             8 01-JAN-20      AAASeTAAMAAA6AjAAB                3 D            O            00                    1125964331378063
         -2 red       cube              100 30-OCT-21      AAASeTAAMAAA6EmAAB                4 I            N            FE                    1125964331378063

有了物化视图日志后,就有可能使用增量刷新了。为什么是有可能?因为这个日志并没有记录所有的变化,例如junk列。因此有时还需要使用全量刷新。

但目前的刷新仍需要手动来做。因为默认的刷新子句为REFRESH FORCE ON DEMAND

可以设置在每次事务提交时做自动刷新,但首先必须做一次全量刷新:

exec dbms_mview.refresh ( 'brick_colours_mv', 'C' );

alter materialized view brick_colours_mv
  refresh fast on commit;

现在,物化视图和基表就可以保持完全一致了:

select *
from   brick_colours_mv;

COLOUR   COUNT(*)
------ ----------
green        2501
red          2502
yellow       2500
blue         2498

insert into bricks values ( -1, 'red', 'cube', 100, sysdate, default );
commit;

select *
from   brick_colours_mv;

COLOUR   COUNT(*)
------ ----------
green        2501
red          2503
yellow       2500
blue         2498

快速刷新的限制

详细的限制见这里,例如不能使用COUNT ( DISTINCT ):

create materialized view bricks_not_fast_mv 
as
  select colour, count(*), count ( distinct shape )
  from   bricks
  group  by colour;

alter materialized view bricks_not_fast_mv 
  refresh fast on commit;

Error starting at line : 1 in command -
alter materialized view bricks_not_fast_mv
  refresh fast on commit
Error report -
ORA-12054: cannot set the ON COMMIT refresh attribute for the materialized view
12054. 00000 -  "cannot set the ON COMMIT refresh attribute for the materialized view"
*Cause:    The materialized view did not satisfy conditions for refresh at
           commit time.
*Action:   Specify only valid options.

使用dbms_mview.explain_mview可以查看允许的刷新方式。

而且对于重度交易系统,快速刷新还会增加事务提交时的开销,刷新会太频繁,需要慎重考虑。

最好是通过查询实现刷新,Oracle Database 12.2通过实时物化视图可以做到。

实时物化视图

本质是在查询时刷新,而非在事务提交时。

实时物化视图允许查询使用过时的数据。在查询运行时,数据库应用物化视图日志中的更改以保证物化视图数据的实时性。此功能要求物化视图:

  • 属性为FAST REFRESH ON DEMAND
  • 使用ENABLE ON QUERY COMPUTATION 子句

可以通过以下SQL设置:

alter materialized view brick_colours_mv
  refresh fast on demand;
  
alter materialized view brick_colours_mv
  enable on query computation;

然后我们修改一些数据,此时物化视图的数据过时。

insert into bricks values ( -3, 'red', 'cube', 100, sysdate, default );
commit;

select * from mlog$_bricks;

   BRICK_ID    COLOUR    SHAPE    WEIGHT    INSERT_DATE               M_ROW$$    SEQUENCE$$    DMLTYPE$$    OLD_NEW$$    CHANGE_VECTOR$$               XID$$
___________ _________ ________ _________ ______________ _____________________ _____________ ____________ ____________ __________________ ___________________
         -3 red       cube           100 30-OCT-21      AAASeqAAMAABD42AAD               13 I            N            FE                    Oracle开发者性能课第4课(如何创建索引)实验

Oracle开发者性能课第9课(如何查找慢 SQL)实验

Oracle开发者性能课第7课(Join如何工作)实验

Oracle开发者性能课第8课(如何更快地进行插入更新和删除)实验

Oracle开发者性能课第3课(我的查询做了多少工作)实验

Oracle开发者性能课第5课(为什么我的查询不使用索引)实验