Oracle Database 12c Attribute Cluster 和 Zone Map 高阶实验

Posted dingdingfish

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Oracle Database 12c Attribute Cluster 和 Zone Map 高阶实验相关的知识,希望对你有一定的参考价值。

本文为参照此教程的实验过程。

此实验设计得非常好,感谢作者Nigel Bayliss和Veerabhadra Rao Putrevu。

术语翻译

  • Attribute Cluster:属性聚类
  • Clustering:聚类
  • Zone Map:区域地图

Overview

本教程介绍了 Oracle Database 12c 属性集群和区域映射,这些特性旨在显着减少查询的 IO,否则这些查询将执行全表扫描。时长约45分钟。

属性聚类是一种表级指令,它根据某些列的内容对数据进行聚类,使其物理上接近。将逻辑上属于在一起的数据存储在物理上接近的位置可以大大减少要处理的数据量,并可以提高工作负载中某些查询的性能。

区域地图是可以为表构建的独立访问结构。在表和索引扫描期间,区域地图使您能够根据表列上的谓词修剪表的磁盘块(可能是分区表的完整分区)。区域地图通过为表格中的每个区域(或范围)维护一个最小和最大列值列表来实现这一点。区域映射也为分区和子分区执行此操作。如果具有共同属性的列聚集在一起,则可以最小化需要扫描以找到特定谓词匹配的区域数量。出于这个原因,如果使用属性聚类将行聚集在一起,或者如果它们在加载时手动排序(例如,使用包含排序的 ETL 过程),则区域映射的有效性会得到提高。区域地图可以在有或没有属性聚类的情况下使用

与传统的聚类方法相比,属性聚类具有基于维度表属性值对事实表中的数据进行聚类的能力。这具有广泛的应用场景,但它在数据仓库、星型模式环境中特别有用。在筛选维度属性值(包括维度属性值层次结构)的联结期间,可以显著减少需要扫描的事实表块的数量。区域地图可用作位图索引的替代方法。

本实验需要Oracle数据库和Exadata,Exadata可使用以下隐含参数模拟:

alter system set "_exadata_feature_on"=TRUE scope=spfile;
shutdown immediate
startup

此外,本实验需要1GB的USERS表空间,并提前下载实验脚本12c_aczm.zip

注意,此实验所用脚本是为non-CDB设计的,如果使用CDB,请在脚本中加入类似于以下的语句:

alter session set container=orclpdb1;

脚本在云环境下执行,可能对口令有复杂性要求,可以使用以下脚本避免:

alter profile default limit PASSWORD_VERIFY_FUNCTION null;

本教程实现了一个简单的场景,以演示 Oracle Database 12c 中的特性如何设计为显著减少查询 IO,否则这些查询将执行全表扫描。您可以在下面找到本教程中要执行的任务的简要概述:

  • 创建 SALES_SOURCE 事实表。
  • 创建两个维度表:PRODUCTS 和 LOCATIONS。
  • 创建一个名为 SALES_AC 的联结属性集群表,使用产品和位置属性值对其进行聚簇。
  • 出于比较目的,创建一个名为 SALES 的非属性聚簇表。
  • 在不使用区域映射的情况下检查属性聚集事实表上的索引范围扫描的行为。
  • 删除事实表索引并使用区域映射观察 IO 修剪。
  • 创建分区表 SALES_P,并观察区域和分区修剪(例如,对未包含在分区键中的列使用谓词)。
  • 无效和刷新区域地图。
$ curl -O https://www.oracle.com/webfolder/technetwork/tutorials/obe/db/12c/r1/12c_aczm/files/12c_aczm.zip

$ unzip 12c_aczm.zip
Archive:  12c_aczm.zip
  inflating: 01_setup12c.sql
  inflating: 02_table_create.sql
  inflating: 03_dim_fill.sql
  inflating: 04_source_fill.sql
  inflating: 05_create_fact.sql
  inflating: 06_ac_only.sql
  inflating: 07_zm_lin_inter.sql
  inflating: 08_zm_prune.sql
  inflating: 09_part_zm.sql
  inflating: 10_zm_maint.sql

依次运行以下脚本:

  • 01_setup12c.sql:创建实验用户aczm12c:1
  • 02_table_create.sql:创建事实表sales_source,维度表locations和products
  • 03_dim_fill.sql:填充维度表locations(3143行)和products(28行),并更新统计信息
  • 04_source_fill.sql:填充事实表sales_source(1952102行),并更新统计信息

Attribute Clustering

本节实验涉及脚本05_create_fact.sql 和 06_ac_only.sql。

目前为止,我们已有3个表。SALES_SOURCE仅为提供数据使用,以下将称其为母表:

SQL> select table_name from user_tables;

TABLE_NAME
------------------------------------------------------------------
SALES_SOURCE
LOCATIONS
PRODUCTS

3 rows selected.

脚本05_create_fact.sql 的拆解过程如下:

-- 基于母表sales_source创建空表sales
CREATE TABLE sales
AS
SELECT * FROM sales_source
WHERE 1 = -1
/

-- 基于母表sales_source创建空表sales_ac
CREATE TABLE sales_ac
AS
SELECT * FROM sales_source
WHERE 1 = -1
/

-- 为表sales_ac启用线性有序属性聚类。
-- 我们将简单地按 location_id、product_id 对行进行排序。
-- 为单纯查看属性聚类的效果,我们不会创建区域地图。
ALTER TABLE sales_ac
ADD CLUSTERING BY LINEAR ORDER (location_id, product_id)
WITHOUT MATERIALIZED ZONEMAP
/

-- 基于母表插入数据到未启用属性聚类的表。
-- APPEND hint提示优化器执行direct path插入
-- 因为,属性聚类只影响direct path插入操作。
INSERT /*+ APPEND */ INTO sales SELECT * FROM sales_source
/
1952102 rows created.

Elapsed: 00:00:15.41

-- 从执行计划看,这是一个简单的插入
SELECT * FROM TABLE(dbms_xplan.display_cursor)
/

PLAN_TABLE_OUTPUT
-------------------------------------
SQL_ID  6magg9dfwqvjt, child number 0
-------------------------------------
INSERT /*+ APPEND */ INTO sales SELECT * FROM sales_source

Plan hash value: 1422891252

-------------------------------------------------------------------------------------------------
| Id  | Operation                        | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------------------
|   0 | INSERT STATEMENT                 |              |       |       |  4518 (100)|          |
|   1 |  LOAD AS SELECT                  | SALES        |       |       |            |          |
|   2 |   OPTIMIZER STATISTICS GATHERING |              |  1952K|   100M|  4518   (1)| 00:00:01 |
|   3 |    TABLE ACCESS FULL             | SALES_SOURCE |  1952K|   100M|  4518   (1)| 00:00:01 |
-------------------------------------------------------------------------------------------------

-- 基于母表插入数据到已启用属性聚类的表。
-- APPEND hint提示优化器执行direct path插入,因为,属性聚类只影响direct path插入操作。
-- 耗时17秒,比之前的插入多2秒
-- 在实际系统中,您可能会分多批插入:每批插入都将适当排序。 
-- 之后如果您想将所有行重新排序为紧密分组的区域,可以使用分区和 MOVE PARTITION 来执行此操作。
INSERT /*+ APPEND */ INTO sales_ac SELECT * FROM sales_source
/
1952102 rows created.

Elapsed: 00:00:17.07

-- 注意执行计划中的SORT ORDER BY,说明对数据进行了排序
SELECT * FROM TABLE(dbms_xplan.display_cursor)
/

PLAN_TABLE_OUTPUT
-------------------------------------
SQL_ID  8wzqwqgwwynya, child number 0
-------------------------------------
INSERT /*+ APPEND */ INTO sales_ac SELECT * FROM sales_source

Plan hash value: 3351020411

---------------------------------------------------------------------------------------------------------
| Id  | Operation                        | Name         | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------------------------
|   0 | INSERT STATEMENT                 |              |       |       |       | 30377 (100)|          |
|   1 |  LOAD AS SELECT                  | SALES_AC     |       |       |       |            |          |
|   2 |   OPTIMIZER STATISTICS GATHERING |              |  1952K|   100M|       | 30377   (1)| 00:00:02 |
|   3 |    SORT ORDER BY                 |              |  1952K|   100M|   149M| 30377   (1)| 00:00:02 |
|   4 |     TABLE ACCESS FULL            | SALES_SOURCE |  1952K|   100M|       |  4518   (1)| 00:00:01 |
---------------------------------------------------------------------------------------------------------

-- 最后更新统计信息
EXECUTE dbms_stats.gather_table_stats(ownname=>NULL,tabname=>'sales');
EXECUTE dbms_stats.gather_table_stats(ownname=>NULL,tabname=>'sales_ac');

可以在没有区域地图的情况下使用属性聚类。 就其本身而言,没有扫描 IO 修剪(通过 Exadata 存储索引除外)。 但是,索引范围扫描可以从索引列与属性簇列匹配的改进性能中受益。 在许多情况下,属性聚类将相同数据值聚集在一起并使它们彼此本地化。 这尤其有利于基于行压缩的压缩率。

当与区域地图、Exadata 存储索引和内存中最小/最大值修剪(Database In-Memory存储索引)结合使用时,可以实现属性聚类的全部潜力。 同时,它们也改进了索引聚类。接下来使用脚本 06_ac_only.sql来进行演示。

脚本 06_ac_only.sql的拆解执行过程如下:

-- 在两个实例表的location_id列均建立索引
CREATE INDEX sales_loc_i ON sales (location_id)
/

CREATE INDEX sales_ac_loc_i ON sales_ac (location_id)
/

-- 观察属性聚类表 SALES_AC 的“Average Data Blocks Per Key”的值。 
-- 这将导致索引范围扫描中表查找的一致性获取更少。
-- AVG_DATA_BLOCKS_PER_KEY表示索引中不同值所指向的表中数据块的平均数,越小表示数据分布越集中。
SELECT index_name, clustering_factor,avg_data_blocks_per_key
FROM   user_indexes
WHERE  index_name LIKE 'SALES%LOC%'
ORDER BY index_name
/
INDEX_NAME                               CLUSTERING_FACTOR AVG_DATA_BLOCKS_PER_KEY
---------------------------------------- ----------------- -----------------------
SALES_AC_LOC_I                                       16113                       5
SALES_LOC_I                                        1915545                     609

-- 两张表的行数均为1952102,数据块数均为16280。
-- 参考https://docs.oracle.com/en/database/oracle/oracle-database/21/refrn/ALL_INDEXES.html
-- CLUSTERING_FACTOR的含义为,如果该值接近块数,则该表的排序非常好。 
-- 在这种情况下,单个叶块中的索引条目往往指向相同数据块中的行。
-- 如果该值接近行数,则该表的排序非常随机。 
-- 在这种情况下,同一叶块中的索引条目不太可能指向同一数据块中的行。
SQL> select blocks from user_tables where table_name = 'SALES_AC';

    BLOCKS
----------
     16280

SQL> select blocks from user_tables where table_name = 'SALES';

    BLOCKS
----------
     16280

SQL> select count(*) from sales;

  COUNT(*)
----------
   1952102

-- 运行以下2个查询,从执行计划中确保使用了INDEX RANGE SCAN。
-- 使用了INDEX hint,是因为表较小,Exadata可能会使用Bloom filter。
SELECT /*+ INDEX(sales sales_loc_i) */ SUM(amount)
FROM   sales
JOIN   locations  ON (sales.location_id = locations.location_id)
WHERE  locations.state  = 'California'
AND    locations.county = 'Alpine County'
/
SELECT * FROM TABLE(dbms_xplan.display_cursor);

SUM(AMOUNT)
-----------
  600213.11

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------------------------------

---------------------------------------------------------------------------------------------
| Id  | Operation                     | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |             |       |       |   623 (100)|          |
|   1 |  SORT AGGREGATE               |             |     1 |    39 |            |          |
|   2 |   NESTED LOOPS                |             |    20 |   780 |   623   (0)| 00:00:01 |
|   3 |    NESTED LOOPS               |             |   621 |   780 |   623   (0)| 00:00:01 |
|*  4 |     TABLE ACCESS FULL         | LOCATIONS   |     1 |    30 |    11   (0)| 00:00:01 |
|*  5 |     INDEX RANGE SCAN          | SALES_LOC_I |   621 |       |     2   (0)| 00:00:01 |
|   6 |    TABLE ACCESS BY INDEX ROWID| SALES       |   621 |  5589 |   612   (0)| 00:00:01 |
---------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):

PLAN_TABLE_OUTPUT
---------------------------------------------------------------------

   4 - filter(("LOCATIONS"."COUNTY"='Alpine County' AND
              "LOCATIONS"."STATE"='California'))
   5 - access("SALES"."LOCATION_ID"="LOCATIONS"."LOCATION_ID")

Note
-----
   - this is an adaptive plan

SELECT /*+ INDEX(sales_ac sales_ac_loc_i) */ SUM(amount)
FROM   sales_ac
JOIN   locations  ON (sales_ac.location_id = locations.location_id)
WHERE  locations.state  = 'California'
AND    locations.county = 'Alpine County'
/
SELECT * FROM TABLE(dbms_xplan.display_cursor);

SUM(AMOUNT)
-----------
  600213.11


PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------

------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name           | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |                |       |       |    19 (100)|          |
|   1 |  SORT AGGREGATE               |                |     1 |    39 |            |          |
|   2 |   NESTED LOOPS                |                |    20 |   780 |    19   (0)| 00:00:01 |
|   3 |    NESTED LOOPS               |                |   621 |   780 |    19   (0)| 00:00:01 |
|*  4 |     TABLE ACCESS FULL         | LOCATIONS      |     1 |    30 |    11   (0)| 00:00:01 |
|*  5 |     INDEX RANGE SCAN          | SALES_AC_LOC_I |   621 |       |     2   (0)| 00:00:01 |
|   6 |    TABLE ACCESS BY INDEX ROWID| SALES_AC       |   621 |  5589 |     8   (0)| 00:00:01 |
------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------

   4 - filter(("LOCATIONS"."COUNTY"='Alpine County' AND
              "LOCATIONS"."STATE"='California'))
   5 - access("SALES_AC"."LOCATION_ID"="LOCATIONS"."LOCATION_ID")

Note
-----
   - this is an adaptive plan

-- 多次运行以下语句,确保数据全部被缓存(即physical reads为0),然后查看其consistent gets值
SELECT SUM(amount)
FROM   sales
JOIN   locations  ON (sales.location_id = locations.location_id)
WHERE  locations.state  = 'California'
AND    locations.county = 'Alpine County'
/

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
        624  consistent gets
          0  physical reads
          0  redo size
        574  bytes sent via SQL*Net to client
         52  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

SELECT SUM(amount)
FROM   sales_ac
JOIN   locations  ON (sales_ac.location_id = locations.location_id)
WHERE  locations.state  = 'California'
AND    locations.county = 'Alpine County'
/

Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
         48  consistent gets
          0  physical reads
          0  redo size
        574  bytes sent via SQL*Net to client
         52  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

-- 最后,删除测试用索引
DROP INDEX sales_loc_i
/

DROP INDEX sales_ac_loc_i
/

Linear and Interleaved Zone Maps

本实验涉及脚本07_zm_lin_inter.sql。在本节中,您将探索区域地图如何在不使用索引的情况下减少 IO。 您还将发现使用区域地图优于传统方法的好处。

以下为脚本07_zm_lin_inter.sql的拆解执行过程。

-- 确保索引已经删除(上一节最后已删)
-- 删除属性聚类
ALTER TABLE sales_ac DROP CLUSTERING
/

-- 重新创建属性聚类,但这一次带区域地图。
-- 我们不需要重新组织或移动表中的数据,因为我们使用与以前相同的聚类。
ALTER TABLE sales_ac
ADD CLUSTERING BY LINEAR ORDER (location_id, product_id)
WITH MATERIALIZED ZONEMAP
/

-- 执行查询,查看执行计划。
-- 注意执行计划中的WITH ZONEMAP和SYS_ZMAP_FILTER关键字
SELECT SUM(amount)
FROM   sales
WHERE  location_id = 50
/

SELECT * FROM TABLE(dbms_xplan.display_cursor);

PLAN_TABLE_OUTPUT
-------------------------------------
SQL_ID  d4538r3rcjr6t, child number 0
-------------------------------------
SELECT SUM(amount) FROM   sales WHERE  location_id = 50

Plan hash value: 1047182207

----------------------------------------------------------------------------
| Id  | Operation          | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |       |       |       |  4434 (100)|          |
|   1 |  SORT AGGREGATE    |       |     1 |     9 |            |          |
|*  2 |   TABLE ACCESS FULL| SALES |   621 |  5589 |  4434   (1)| 00:00:01 |
----------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter("LOCATION_ID"=50)

SELECT SUM(amount)
FROM   sales_ac
WHERE  location_id = 50
/

SELECT * FROM TABLE(dbms_xplan.display_cursor);

PLAN_TABLE_OUTPUT
-------------------------------------
SQL_ID  319c3616hqy3f, child number 0
-------------------------------------
SELECT SUM(amount) FROM   sales_ac WHERE  location_id = 50

Plan hash value: 1269548508

--------------------------------------------------------------------------------------------
| Id  | Operation                       | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                |          |       |       |  4434 (100)|          |
|   1 |  SORT AGGREGATE                 |          |     1 |     9 |            |          |
|*  2 |   TABLE ACCESS FULL WITH ZONEMAP| SALES_AC |   621 |  5589 |  4434   (1)| 00:00:01 |
--------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter((SYS_ZMAP_FILTER('/* ZM_PRUNING */ SELECT zm."ZONE_ID$", CASE WHEN
              BITAND(zm."ZONE_STATE$",1)=1 THEN 1 ELSE CASE WHEN (zm."MIN_1_LOCATION_ID" > :1 OR
              zm."MAX_1_LOCATION_ID" < :2) THEN 3 ELSE 2 END END FROM "ACZM12C"."ZMAP$_SALES_AC"
              zm WHERE zm."ZONE_LEVEL$"=0 ORDER BY zm."ZONE_ID$"',SYS_OP_ZONE_ID(ROWID),50,50)<3
              AND "LOCATION_ID"=50))

-- 然后来比较执行统

以上是关于Oracle Database 12c Attribute Cluster 和 Zone Map 高阶实验的主要内容,如果未能解决你的问题,请参考以下文章

Oracle Database 12c Release 2安装详解

Oracle Database 12c安装

Oracle Database 12c Release 1下载安装(自身经历)

Oracle 12C pluggable database自启动

甲骨文大学网络直播:聚焦Oracle Database 12c新特性

Mac OSX 上 VM 中的 Oracle Database 12c