Oracle Ask Tom分区学习系列: 面向开发者的分区(Partitioning)教程

Oracle Partitioning: A Step-by-Step Introduction for DevelopersOracle数据库开发者课程之一。

在这里插入图片描述

Development with Oracle Partitioning/使用 Oracle 分区进行开发

Partitioning in the database reflects the same way we handle large tasks in the real world. When a task gets too large to tackle in one hit, whether it be trimming a tree, taking a long drive, or washing the dishes, we can split the task up into smaller pieces to make them more manageable. It can be as simple as trying to relocate one’s catalog of Oracle technical books!
数据库中的分区反映了我们在现实世界中处理大型任务的方式。 当一项任务变得太大而无法一次解决时,无论是修剪树木、长途驾驶还是洗碗,我们都可以将任务拆分成更小的部分,以使其更易于管理。 它可以像尝试重新定位一个人的 Oracle 技术书籍目录一样简单!

视频

在这个视频里,作者Connor Mcdonald可以将书按出版社分,按年代分,按Oracle数据库版本分…

Partitioning is that same thought applied to data stored in the database. As the demands to store more and more data in the database increase, the performance of operations against those large tables can suffer. And in applying the Pareto principle to the storage of data, typically it is only a subset of the entire dataset that is actively worked upon to satisfy the day to day needs of our businses users. Using the Partitioning option in the Oracle Database, data can be segmented into smaller, more manageable chunks to make maintenance tasks easier for Database Administrators, but also give scope for better application performance via more efficient execution of the queries being issued by Application Developers.
分区是应用于存储在数据库中的数据的相同思想。 随着在数据库中存储越来越多数据的需求增加,对这些大表的操作性能可能会受到影响。 在将 Pareto 原则应用于数据存储时,通常只有整个数据集的一个子集被积极处理以满足我们业务用户的日常需求。 使用 Oracle 数据库中的分区选项,可以将数据分割成更小、更易于管理的块,使数据库管理员的维护任务更容易,而且还可以通过更高效地执行应用程序开发人员发出的查询来提供更好的应用程序性能。

Pareto principle

The Pareto principle states that for many outcomes, roughly 80% of consequences come from 20% of causes (the “vital few”).[1] Other names for this principle are the 80/20 rule, the law of the vital few, or the principle of factor sparsity.[2][3]

Partitioning Options/分区选项

There are different types of partitioning options available to cater for specific business requirements. There might be a requirement to break up SALES data into each calendar year. Or there might be information being gathered on popular SPORTS that will be separated because minimal cross-sport queries will ever be run. Or a table of cell phone CALLS might just be so large that it needs to be evenly scattered across smaller segments to keep them at a manageable size. All such options are possible with the Oracle Partitioning option.
有不同类型的分区选项可用于满足特定的业务需求。 可能需要将 SALES 数据分解为每个日历年。 或者可能会收集有关流行运动(如NBA, NFL, MLB, NHL, 即篮球,橄榄球,棒球和曲棍球)的信息,这些信息将被分开,因为跨运动查询比较少见。 或者,手机呼叫表可能太大,需要将其均匀分散在更小的段中,以将它们保持在可管理的大小。 所有这些选项都可以通过 Oracle Partitioning 选项实现。

There are other partitioning strategies as well for more esoteric requirements, including partitioning strategies between tables linked by referential integrity, and multi-dimensional forms of partitioning (partitions of partitions).
对于更深奥的需求,还有其他分区策略,包括通过参照完整性链接的表之间的分区策略,以及多维形式的分区(分区的分区)。

Getting started with Partitioning/分区入门

Get an Environment/获取环境

您只需转到Oracle 提供的名为livesql.oracle.com的免费服务即可开始使用。此服务允许您运行 SQL 并创建数据库对象,除了您的浏览器之外不需要任何软件。还有数百个关于众多主题的示例脚本和教程,可帮助您了解 Oracle 数据库

Oracle 分区也作为所有 Oracle 数据库云服务和内部部署 Oracle 数据库企业版的完全支持的特性提供。以下是 Oracle 副总裁 Mike Hichwa 对 LiveSQL 的快速入门:

视频

Oracle LiveSQL 特性:

  • 免费:LiveSQL 对任何用途都是完全免费的。注册免费、快速且简单。
  • 脚本:您在 LiveSQL 中编写的 SQL 可以进行元数据标记、保存、与他人共享或与整个 Oracle 社区共享。
  • 教程:LiveSQL 包含数百个由 Oracle Corporation 内部和外部专家编写的教程,可帮助您快速提高工作效率。
  • 最新版本:LiveSQL 在最新版本的 Oracle 数据库上运行,因此您可以在升级自己的系统之前安全地测试新功能。

以下命令获取数据库当前版本:

select * from v$version;

在这里插入图片描述

A First Look at Partitioning Syntax/分区语法初探

Perhaps the most common example of partitioning in Oracle databases is to divide up a large data into partitions based on a time attribute. The largest tables in your applications are often a time-based record of critical business activities. It could be sales transactions for a retail business, mobile phone calls for a telecommunications business, or deposits and withdrawals for a banking institution. In all of these cases, there are a couple of common elements, namely each transaction (row) in the table has a time stamp of when the transaction occurred, and typically the volume of such transactions is high, making the table large in size in a short period of time. Partitioning is a natural fit for such tables, because queries often want to only peruse(随便翻阅,浏览) time-based subsets of the data, for example, transactions for the current month, or current week. Also, breaking the large table into smaller more manageable sized pieces is useful for adminstrators from the perspective of maintenance. Time is a continuous (analog) measurement, and thus, a large table would be segmented into time-based ranges, hence the term used for this opertaion is range based partitioning. This video walks you through the creation a simple range partitioned table.

视频

关键点:

  • 按范围分区:定义表已分区的关键字是 PARTITION BY,它遵循普通表列定义。BY RANGE 子句指定表将使用的分区方案的类型。
  • 不包含上边界:范围分区没有给出下限和上限,只有上限。下限隐式定义为前一个分区的上限。一个分区可以包含不超过但不包括在 VALUES LESS THAN 子句中指定的值的值。(有点绕,其实就是大于等于下边界,并且小于上边界
  • 至少 1 个分区:在 PARTITION BY 子句之后,必须始终至少定义一个分区。(对于范围分区是这样的,而其它类型的分区不一定适用,如自动分区,但也是少数情况
  • USER_TAB_PARTITIONS:数据字典跟踪表的所有已定义分区。USER_TAB_PARTITIONS 为架构中的每个分区表的每个定义分区显示一行。

Performance Benefits/性能优势

Even with just a simple range partitioning example, we have enough tools at our disposal(由我们支配,任我们处置) to examine the potential performance benefits possible by partitioning a table. When SQL queries consume too much I/O, often the only resolution considered is to create indexes on the table. The premise(前提) of indexing is simple: locate data more quickly and avoid scanning data unnecessarily. Partitioning a table takes the same approach via a concept known as “pruning” or “elimination”. If a query on a partitioned table contains appropriately phrased predicates that include the partitioning column(s), the optimizer can generate an execution plan that will bypass those partitions that by definition, could not contain data relevant to the query. The following video shows a demonstration of this, including a comparison of the cost of partition pruning versus a conventional indexing strategy(索引策略指的是分区类型,例如范围分区,列表分区,哈希分区等).

视频

关键点:

  • 分区修剪:如果优化器可以消除分区的考虑,查询性能可以得到显着提高。在后面的视频中,您将看到如何解释优化器执行计划输出以确定是否对给定的 SQL 查询进行分区消除。
  • 生成测试数据:您可以使用视频中的 DUAL 技术为任何表、分区表或其他表生成任意测试数据。根据视频,将单个 DUAL CONNECT BY 查询生成的行数保持在数万以内,如果需要扩展,请使用笛卡尔连接。请参阅Tanel Poder 的博客文章,了解这些查询如何影响 PGA 内存,了解您不应走极端的原因。
  • 减少索引:在某些情况下,对表进行分区允许合并或删除现有索引,这可以减少整体数据库大小,并提高插入、更新和删除性能。
create table SALES
(
  tstamp    timestamp(6) not null,
  sales_id  number(10) not null,
  amount    number(12, 2) not null 
);

-- timestamp之后的6表示秒的小数点的位数,6是默认值。

insert into sales
select
    timestamp '2010-01-01 00:00:00' +
    numtodsinterval(rownum*5, 'SECOND'),
    rownum,
    dbms_random.value(1,20)
from
    (select 1 from dual connect by level <= 10000),
    (select 1 from dual connect by level <= 10000)
where rownum <= 6000000;

commit;

-- 6000000 * 5 可换算为347.22天,所以时间戳都在2010年内,最大时间戳在12月中旬

set autotrace on
select max(amount)
from sales
where tstamp >= timestamp '2010-06-01 00:00:00'
and tstamp <= timestamp '2010-08-01 00:00:00';

输出如下,注意此时的consistent gets为19326:

MAX(AMOUNT)
-----------
         20


Execution Plan
----------------------------------------------------------
Plan hash value: 1047182207

----------------------------------------------------------------------------
| Id  | Operation          | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |       |     1 |    26 |  5265   (1)| 00:00:01 |
|   1 |  SORT AGGREGATE    |       |     1 |    26 |            |          |
|*  2 |   TABLE ACCESS FULL| SALES |  1062K|    26M|  5265   (1)| 00:00:01 |
----------------------------------------------------------------------------

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

   2 - filter("TSTAMP">=TIMESTAMP' 2010-06-01 00:00:00.000000000' AND
              "TSTAMP"<=TIMESTAMP' 2010-08-01 00:00:00.000000000')

Note
-----
   - dynamic statistics used: dynamic sampling (level=2)


Statistics
----------------------------------------------------------
         41  recursive calls
         13  db block gets
      19326  consistent gets
          2  physical reads
       2576  redo size
        553  bytes sent via SQL*Net to client
        485  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
          1  rows processed

创建索引:

create index sales_ix on sales(tstamp);

再次查询,其执行计划和统计信息如下。发现索引并没有帮助,仍然使用全表扫描:

MAX(AMOUNT)
-----------
         20


Execution Plan
----------------------------------------------------------
Plan hash value: 1047182207

----------------------------------------------------------------------------
| Id  | Operation          | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |       |     1 |    26 |  5265   (1)| 00:00:01 |
|   1 |  SORT AGGREGATE    |       |     1 |    26 |            |          |
|*  2 |   TABLE ACCESS FULL| SALES |  1062K|    26M|  5265   (1)| 00:00:01 |
----------------------------------------------------------------------------

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

   2 - filter("TSTAMP">=TIMESTAMP' 2010-06-01 00:00:00.000000000' AND
              "TSTAMP"<=TIMESTAMP' 2010-08-01 00:00:00.000000000')

Note
-----
   - dynamic statistics used: dynamic sampling (level=2)


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

修改为分区表:

alter table sales
modify partition by range (tstamp)
(
    partition p00 values less than (timestamp '2010-01-01 00:00:00'),
    partition p01 values less than (timestamp '2010-02-01 00:00:00'),
    partition p02 values less than (timestamp '2010-03-01 00:00:00'),
    partition p03 values less than (timestamp '2010-04-01 00:00:00'),
    partition p04 values less than (timestamp '2010-05-01 00:00:00'),
    partition p05 values less than (timestamp '2010-06-01 00:00:00'),
    partition p06 values less than (timestamp '2010-07-01 00:00:00'),
    partition p07 values less than (timestamp '2010-08-01 00:00:00'),
    partition p08 values less than (timestamp '2010-09-01 00:00:00'),
    partition p09 values less than (timestamp '2010-10-01 00:00:00'),
    partition p10 values less than (timestamp '2010-11-01 00:00:00'),
    partition p11 values less than (timestamp '2010-12-01 00:00:00'),
    partition p12 values less than (timestamp '2011-01-01 00:00:00')
);

再次执行查询,分区裁剪生效了。consistent gets降为5075:

---------------------------------------------------------------------------------------------------
| Id  | Operation                 | Name  | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
---------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT          |       |     1 |    15 |  1415   (1)| 00:00:01 |       |       |
|   1 |  SORT AGGREGATE           |       |     1 |    15 |            |          |       |       |
|   2 |   PARTITION RANGE ITERATOR|       |  1054K|    15M|  1415   (1)| 00:00:01 |     7 |     9 |
|*  3 |    TABLE ACCESS FULL      | SALES |  1054K|    15M|  1415   (1)| 00:00:01 |     7 |     9 |
---------------------------------------------------------------------------------------------------

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

   3 - filter("TSTAMP"<=TIMESTAMP' 2010-08-01 00:00:00.000000000')


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

最后,清理表:

drop table sales purge;

如果嫌之前的范围分区语法复杂,也可以用间隔分区。间隔分区是范围分区的扩展:

alter table sales
modify partition by range (tstamp) interval (numtoyminterval(1, 'MONTH'))
(partition p00 values less than (timestamp '2010-01-01 00:00:00'));

查看分区信息:

-- 对于间隔分区,PARTITION_COUNT总是1048575
col TABLE_NAME for a20
col name for a20
col column_name for a20
set lines 140
col PARTITION_NAME for a20
col HIGH_VALUE for a40
set pages 9999

select TABLE_NAME, PARTITIONING_TYPE, PARTITION_COUNT, STATUS from USER_PART_TABLES;

TABLE_NAME           PARTITION PARTITION_COUNT STATUS
-------------------- --------- --------------- --------
SALES                RANGE             1048575 VALID

exec dbms_stats.gather_table_stats(null, 'SALES');

-- 必须搜集统计信息,NUM_ROWS才会有显示
select PARTITION_NAME, HIGH_VALUE, NUM_ROWS from USER_TAB_PARTITIONS where TABLE_NAME='SALES';

PARTITION_NAME       HIGH_VALUE                                 NUM_ROWS
-------------------- ---------------------------------------- ----------
P00                  TIMESTAMP' 2010-01-01 00:00:00'                   0
SYS_P27320           TIMESTAMP' 2010-02-01 00:00:00'              535679
SYS_P27321           TIMESTAMP' 2010-03-01 00:00:00'              483840
SYS_P27322           TIMESTAMP' 2010-04-01 00:00:00'              535680
SYS_P27323           TIMESTAMP' 2010-05-01 00:00:00'              518400
SYS_P27324           TIMESTAMP' 2010-06-01 00:00:00'              535680
SYS_P27325           TIMESTAMP' 2010-07-01 00:00:00'              518400
SYS_P27326           TIMESTAMP' 2010-08-01 00:00:00'              535680
SYS_P27327           TIMESTAMP' 2010-09-01 00:00:00'              535680
SYS_P27328           TIMESTAMP' 2010-10-01 00:00:00'              518400
SYS_P27329           TIMESTAMP' 2010-11-01 00:00:00'              535680
SYS_P27330           TIMESTAMP' 2010-12-01 00:00:00'              518400
SYS_P27331           TIMESTAMP' 2011-01-01 00:00:00'              228481

13 rows selected.

select * from USER_PART_KEY_COLUMNS;

NAME                 OBJEC COLUMN_NAME          COLUMN_POSITION COLLATED_COLUMN_ID
-------------------- ----- -------------------- --------------- ------------------
SALES                TABLE TSTAMP                             1

Multi-column Range Partitioning/多列范围分区

可以指定多个列作为分区键,列的顺序很重要。

视频

create table SALES_DATA
(
    yyyy number(4) not null,
    mm number(2) not null,
    sales_id varchar2(10) not null,
    amount number(10, 2)
)
partition by range (yyyy, mm)
(
    partition p2010_q1 values less than (2010, 04),
    partition p2010_q2 values less than (2010, 07),
    partition p2010_q3 values less than (2010, 10),
    partition p2010_q4 values less than (2011, 01),
    partition p2011_q1 values less than (2011, 04),
    partition p2011_q2 values less than (2011, 07),
    partition p2011_q3 values less than (2011, 10),
    partition p2011_q4 values less than (2012, 01)
);

insert into sales_data values(2010, 03, 'Shoes', 27.10);
insert into sales_data values(2010, 02, 'Belt', 17.99);
insert into sales_data values(2010, 04, 'Hat', 42.40);
insert into sales_data values(2010, 09, 'Coffee', 3.50);
insert into sales_data values(2010, 10, 'Biscuits', 2.60);

exec dbms_stats.gather_table_stats('', 'SALES_DATA');

select partition_name, num_rows
from user_tab_partitions
where table_name = 'SALES_DATA';

输出为:

PARTITION_NAME	NUM_ROWS
P2010_Q1	2
P2010_Q2	1
P2010_Q3	1
P2010_Q4	1
P2011_Q1	0
P2011_Q2	0
P2011_Q3	0
P2011_Q4	0

8 rows selected.

再看另一个例子:

create table mobile_phone
(
    start_day date not null,
    end_day date not null,
    account_id varchar2(10) not null,
    calls number(10)
)
partition by range(start_day, end_day)
(
    partition p01 values less than (date '2010-02-01', date '2010-03-01'), 
    partition p02 values less than (date '2010-03-01', date '2010-04-01'), 
    partition p03 values less than (date '2010-04-01', date '2010-05-01') 
);

insert into mobile_phone values('07-FEB-2010', '12-FEB-2010', 'Acct#1', 100);
insert into mobile_phone values('12-FEB-2010', '13-APR-2010', 'Acct#1', 175);
exec dbms_stats.gather_table_stats('', 'MOBILE_PHONE');

select partition_name, num_rows
from user_tab_partitions
where table_name = 'MOBILE_PHONE';

输出为:

PARTITION_NAME	NUM_ROWS
P01	0
P02	2
P03	0

3 rows selected.

所有数据都进入第2个分区,这显然不是我们想要的。

关键点:

  • 决胜局(tie-breaker)不是多维度的
    分区定义中的第二和第三列仅用作“决胜局”值。将行插入多列范围分区表时,分区键中的第一列用于确定将行存储到的分区。如果多个分区的第一列具有相同的值,则使用第二个分区列(也就是加时赛或决胜局,以进一步确定存储的分区),依此类推。分区键中的多列不是“矩阵”或“n 维”结构的分区。
  • 将日期存储为数字
    视频中的示例之一使用 NUMBER 数据类型来存储基于日期的信息。正如视频所述,这通常是一个糟糕的设计理念。请参阅Richard Foote 的Storing Dates以获取更多示例,了解为什么您可能想要在数据库中重新考虑这种方法。
  • 词典视图
    与 USER_TABLES 包含各种列以反映当前优化器统计信息的方式相同,USER_TAB_PARTITIONS 也包含分区级别的此信息。因此,您可以使用 NUM_ROWS、BLOCKS 等来获取有关每个分区的卷的信息,准确性仅限于使用 DBMS_STATS 收集统计信息的时间/粒度

以上两个例子的区别在于,例一的分区键是时间点,例二则是时间段,并且时间段有重叠,所以出现了问题。

Hash Partitioning/哈希分区

有时分区不是像范围分区那样基于表的属性对表进行逻辑分段。 当任何数据库表变得庞大时,管理员就更难管理,因为维护此类表通常会导致业务应用程序的停机时间更长。 哈希分区允许您根据应用于表的一个或多个列的哈希函数将表分割成大小相等的块。

创建哈希分区表很容易,但指定的哈希分区数很关键。

视频

哈希分区对于DBA很重要;而哈希索引分区则对开发者很重要。

关键点:

  • 2的幂
    为使分区大小接近,分区数应为 2 的幂。
  • ORA_HASH
    哈希算法未记录在案,但观察到 ORA_HASH 函数返回的结果与哈希分区发生的数据分段一致。
  • 分裂
    可以使用 ALTER TABLE SPLIT PARTITION 命令拆分分区,该任务通常由数据库管理员完成。拆分分区是一项资源密集型活动,因为可能会移动整个分区的数据。
create table T
(
    x number(10)
) partition by hash(x)
partitions 8;

select partition_name
from user_tab_partitions
where table_name = 'T';

输出为:

PARTITION_NAME
SYS_P492050
SYS_P492051
SYS_P492052
SYS_P492053
SYS_P492054
SYS_P492055
SYS_P492056
SYS_P492057

8 rows selected.

插入10万条数据:

insert into T
select level from dual 
connect by level <= 100000;

查看rowid:

select rowid from T where rownum = 1;

输出如下,rowid中实际包含了对象ID:

ROWID
AJUIk+ADDAAABSTAAA

因此通过以下SQL可以得到每个分区的实际行数:

select dbms_rowid.rowid_object(rowid) ptn_obj, count(*)
from T
group by dbms_rowid.rowid_object(rowid)
order by 2;

输出如下,可以看出数据接近平均分布:

PTN_OBJ	COUNT(*)
156272962	12342
156272963	12381
156272965	12382
156272961	12508
156272959	12575
156272960	12581
156272958	12603
156272964	12628

8 rows selected.

可以通过ORA_HASH函数验证,其中7表示从0到8:

select ora_hash(x, 7), count(*)
from t
group by ora_hash(x, 7)
order by 2;

输出如下:

ORA_HASH(X,7)	COUNT(*)
4	12342
5	12381
7	12382
3	12508
1	12575
2	12581
0	12603
6	12628

8 rows selected.

哈希分区数应为2的幂,例如2,4,8,16…,否则分布可能会不均匀。

drop table t purge;

create table T
(
    x number(10)
) partition by hash(x)
partitions 5;

insert into T
select level from dual 
connect by level <= 100000;

select dbms_rowid.rowid_object(rowid) ptn_obj, count(*)
from T
group by dbms_rowid.rowid_object(rowid)
order by 2;

可以看到分布并不均匀:

   PTN_OBJ   COUNT(*)
---------- ----------
    107682      12342
    107678      12603
    107681      24890
    107679      24956
    107680      25209

5 rows selected.

如何将5个分区调整为8个分区?先来看传统的做法,大多数行(本例接近88%)都需移动:

select count(*) from T
where ora_hash(x, 4) != ora_hash(x, 7);

COUNT(*)
87564

select count(*) from T
where ora_hash(x, 4) != ora_hash(x, 5);

COUNT(*)
83224

Oracle采用了更智能的算法,先看从5个分区变为6个分区:

alter table T add partition;

select dbms_rowid.rowid_object(rowid) ptn_obj, count(*)
from T
group by dbms_rowid.rowid_object(rowid)
order by 2;

PTN_OBJ	COUNT(*)
156275768	12342
156276447	12381
156276446	12575
156275764	12603
156275767	24890
156275766	25209

6 rows selected.

可以看到添加哈希分区的效果,相对于拆分某一哈希分区。本例中,即将24956拆分为12381和12575。

重复以上过程,可以看到数据分布趋于平衡:

alter table T add partition;
select dbms_rowid.rowid_object(rowid) ptn_obj, count(*)
from T
group by dbms_rowid.rowid_object(rowid)
order by 2;

PTN_OBJ	COUNT(*)
156275768	12342
156276447	12381
156276446	12575
156276791	12581
156275764	12603
156276792	12628
156275767	24890

7 rows selected.

alter table T add partition;
select dbms_rowid.rowid_object(rowid) ptn_obj, count(*)
from T
group by dbms_rowid.rowid_object(rowid)
order by 2;

PTN_OBJ	COUNT(*)
156275768	12342
156276447	12381
156276843	12382
156276842	12508
156276446	12575
156276791	12581
156275764	12603
156276792	12628

8 rows selected.

哈希分区只能一个一个添加。添加分区的过程相当于split,也是数据重新哈希的过程。

减少哈希分区是通过coalesce操作实现的。

select dbms_rowid.rowid_object(rowid) ptn_obj, count(*)
from T
group by dbms_rowid.rowid_object(rowid)
order by 2;

   PTN_OBJ   COUNT(*)
---------- ----------
    107682      12342
    107684      12381
    107688      12382
    107687      12508
    107683      12575
    107685      12581
    107678      12603
    107686      12628

8 rows selected.

alter table T COALESCE PARTITION;

-- 数据库自动选择了107687和107687进行coalesce
select dbms_rowid.rowid_object(rowid) ptn_obj, count(*)
from T
group by dbms_rowid.rowid_object(rowid)
order by 2;

   PTN_OBJ   COUNT(*)
---------- ----------
    107682      12342
    107684      12381
    107683      12575
    107685      12581
    107678      12603
    107686      12628
    107689      24890

7 rows selected.

List Partitioning/列表分区

Range partitioning, as the name suggests, is about carving up data that is analog(模拟) in nature, that is, a continuous range of values(连续的值). This is why dates are a natural candidate for a range-based partitioning scheme.
But sometimes the column you might want to partition on contains a discrete(离散) set of values, which is when LIST partitioning is the best solution. Creating a LIST partitioned table requires nominating the discrete values for each partition, or relying on the new AUTOMATIC clause in 12c Release 2.

视频

列表分区适合于distinct值较少,而范围分区比较繁琐,哈希分区分布又不均匀的情形。

关键点:

  • 一个或多个值
    单个分区可以包含一个或多个离散值。
  • 默认
    可以使用 VALUES 语句中的 DEFAULT 子句定义“全部捕获”分区。空值也进入这个分区。

示例:

create table sports
(
    sport varchar2(3)
);

insert into sports values('NHL');
insert into sports values('MLB');
insert into sports values('NBA');
insert into sports values('NFL');

alter table sports modify partition by hash(sport) partitions 4;

select sport, ora_hash(sport, 3) hash from sports;

输出如下,可以看到分布并不均匀:

SPORT	HASH
NHL		0
NFL		0
MLB		1
NBA		1

4 rows selected.

直接改为列表分区:

alter table sports modify partition by list(sport)
(
    partition NHL values ('NHL'),
    partition MLB values ('MLB'),
    partition NBA values ('NBA'),
    partition NFL values ('NFL')
);

alter table sports add partition OTHERS values ('LAC', 'TEN');

insert into sports values('XYZ');

报错为:

ORA-14400: inserted partition key does not map to any partition ORA-06512: at "SYS.DBMS_SQL", line 1721

加一个默认分区就好了:

alter table sports add partition ALL_OTHERS values (DEFAULT);

insert into sports values('XYZ');

指定分区的查询:

select * from sports partition(ALL_OTHERS);

Partitions of Partitions/分区的分区

即复合分区。最多两级,允许的组合为[Interval | Range | List | Hash]-[Range | List | Hash]

Even once partitioned, a table may still be extremely large. Or the partitioning scheme may result in partitions that are not equally sized on disk. For example, archiving off of older data might mean that historical partitions are far smaller than the current partitions (这里假设业务是不断增长的,因此今年的数据多于去年). Equi-sized partitions might be beneficial in particular when it comes to performing operations in paralle on a per-partition basis.
Partitions can be segmented further into subpartitions. Each partition can have it’s own subpartitioning scheme, or all partitions can share a common schema.

关键点:

  • 灵活性
    子分区方案可以不同于父分区方案。
  • 每个分区定义
    每个分区都可以有自己的子分区定义。分区没有子分区也是有效的。也就是说,对于同一张表,可以允许有的分区没有子分区,而有的分区有子分区。或者不同的分区可以有不同的子分区策略
  • 顺序
    USER_TAB_SUBPARTITIONS 中的 SUBPARTITION_POSITION 列指的是子分区在父分区中的相对位置。
  • 模板
    子分区模板可以轻松地为所有分区实施通用方案并减少 DDL 脚本的大小。
  • 逻辑/物理
    子分区的存在决定了一个分区是磁盘上的物理段,还是仅仅是物理子分区段的逻辑集合。无子分区时是前者,有子分区时是后者,因为数据实际存放于子分区中。这里所说的分区指父分区更为准确

Interval Partitions/间隔分区

For incoming data, a partition must already exist that the incoming partitioning key would map to. If a partition does not exist, transactions will fail with ORA-14400. In earlier versions of Oracle Database, this meant that administrators had to take extreme care to ensure that partitions for future data were defined or risk application outages. Interval partitioning (introduced in Oracle Database 11g) removes this risk by automatically creating new partitions on a partitioned table as required when new data arrives.

Interval partitioned tables are different from range partition in that logical “gaps” in the partition coverage are permitted.

间隔分区是范围分区的一种。

视频

关键点:

  • 自动命名
    由于分区是动态创建的,因此系统生成的名称会分配给新分区。它们可以重命名以满足现有的业务标准。
  • 允许有间隙
    分区的分区键范围是固定的,即区间的大小。与范围分区不同,上限和下限由间隔定义,而不是由相邻分区。换言之,范围分区是连续的,有限的;间隔分区是无限的,中间允许有空洞
  • FOR语法
    如果不知道间隔分区的名称,可以使用 FOR ( key-value ) 语法

对于范围分区,由于在DDL建立的分区是有限的,因此在新数据到来之前需要预先建立好分区。例如在除夕就需要建立为新年数据的分区,否则insert时就会报错,这对DBA是个负担。因此需要一种自动的方式,这就是间隔分区。

create table sales
(
    tstamp  date    not null,
    empno   number(10)  not null,
    ename   varchar(2)  not null,
    deptno  varchar(2)  not null
)
partition by range (tstamp)
interval (numtoyminterval(1, 'YEAR'))
(
    partition p00 values less than (DATE '2010-01-01')
);

select partition_name, high_value from user_tab_partitions
where table_name = 'SALES';

PARTITION_NAME	HIGH_VALUE
--------------  ----------
P00				TO_DATE(' 2010-01-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIAN')

insert into sales values (to_date(''12-DEC-2011), 100, 'ME', 'EA');

select partition_name, high_value from user_tab_partitions
where table_name = 'SALES';

PARTITION_NAME	HIGH_VALUE
--------------  ----------
P00				TO_DATE(' 2010-01-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIAN')
SYS_P492130		TO_DATE(' 2012-01-01 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIAN')

系统自动创建了分区,其名字以SYS_P开头,后续可以重命名。

PARTITION FOR语法,通过数据引用分区:

alter table SALES move partition for (DATE '2011-02-01');

select * from SALES partition for (DATE '2011-02-01');

PARTITION FOR是通用的语法,不限于间隔分区。对于哈希分区也可用:

select count(*) from T partition for (100);

select * from T partition(p1, p2, ...);

Converting to Interval Partitions/转换为间隔分区

Existing range partitioned tables can be converted into interval partition tables with a simple conversion command, requiring no downtime or data migration. The existing partitions in the range partitioned table remained defined as “range partitions” whilst new partitions created dynamically are “interval partitions”. Thus a table that was not initially created as an interval partitioned table is a hybrid between the two, containing both types of partitions. Because intervals are defined as an offset from an initial fixed range partition boundary, you cannot drop all of the range partitions in an interval partitioned table. At least one must always remain. In Oracle Database 12c Release 2, the database will automatically convert the oldest interval partition into a range partition if no initial range partition boundary can be found.

视频

关键点:

  • 转换
    只需通过 ALTER TABLE … SET INTERVAL 指定所需的间隔即可将范围分区表转换为间隔分区表。
  • 混合分区
    在转换表上,USER_TAB_PARTITIONS 上的 INTERVAL 列指示每个分区是范围分区还是间隔分区。
  • 最小范围边界
    表中必须至少保留一个范围分区。在 Oracle Database 12c 第 2 版之前,重新运行 SET INTERVAL 命令会将现有的间隔分区标记为范围分区。

示例:

create table SALES
(
  tstamp    timestamp(6) not null,
  sales_id  number(10) not null,
  amount    number(12, 2) not null 
);

alter table sales
modify partition by range (tstamp)
(
    partition p00 values less than (timestamp '2010-01-01 00:00:00'),
    partition p01 values less than (timestamp '2010-02-01 00:00:00')
);

insert into sales values(timestamp '2009-12-02 00:00:00', 100, 100);
insert into sales values(timestamp '2010-01-02 00:00:00', 200, 200);

alter table sales set interval(numtoyminterval(1, 'MONTH'));

insert into sales values(timestamp '2010-02-02 00:00:00', 300, 300);

col pname for a20
col high_value for a40

select
    partition_name pname,
    partition_position pos, 
    high_value,
    interval
from user_tab_partitions
where table_name = 'SALES';

输出为:

PNAME                       POS HIGH_VALUE                               INT
-------------------- ---------- ---------------------------------------- ---
P00                           1 TIMESTAMP' 2010-01-01 00:00:00'          NO
P01                           2 TIMESTAMP' 2010-02-01 00:00:00'          NO
SYS_P27344                    3 TIMESTAMP' 2010-03-01 00:00:00'          YES

3 rows selected.

interval为NO表示范围分区,YES表示间隔分区。

范围分区的上限作为标记,后续的间隔分区均相对于它而言。因此你不能删除所有的范围分区。但是,这一限制被打破了,显然软件是改进了。在12.2之后,如果你删除所有的范围分区,则余下的间隔分区会自动转换为范围分区:

alter table sales drop partition p00;
alter table sales drop partition p01;
col pname for a20
col high_value for a40

select
    partition_name pname,
    partition_position pos, 
    high_value,
    interval
from user_tab_partitions
where table_name = 'SALES';

PNAME                       POS HIGH_VALUE                               INT
-------------------- ---------- ---------------------------------------- ---
SYS_P27344                    1 TIMESTAMP' 2010-03-01 00:00:00'          NO

Interval Partitions for Lists/列表的间隔分区

INTERVAL partitioning is great for range partitioning to avoid the need for regular maintenance by a DBA to ensure that all values can be stored. The same facility is available for LIST partitioned tables from Oracle Database 12c Release 2 onwards. In this way, you can ensure that legal data values that have not been defined as partition keys can automatically create partitions on the fly.

视频

关键点:

  • 一个静态分区
    仍然必须定义至少一个静态分区
  • 允许空值
    如果需要,仍然可以定义一个分区来保存空值
  • 明智地使用
    自动分区最适合一组有限的不同值。不要落入百万(即太多的不同值)分区的陷阱

自动列表分区适合于有限的不同值,如省市自治区,州。但又不能太少,如性别。

create table people(
    id int,
    name varchar2(100),
    gender varchar2(2)
);


alter table people
modify partition by list(gender) automatic
(
    partition male values ('M'),
    partition female values ('F')
);

insert into people values(100, 'VOID', 'U');

select partition_name from user_tab_partitions
where table_name = 'PEOPLE';

输出为:

PARTITION_NAME
--------------
FEMALE
MALE
SYS_P492154

3 rows selected.

Reference Partitioning/参考分区

Tables in a relational database do not work in isolation, and link them via declarative referential integrity. For this reason, a large table might not contain the column upon which we wish to partition it by. For example, a SALES table may be partitioned by a SALES_DATE, but a child table (say) SALES_ITEMS may only have a foreign back to the the parent SALES table, and thus no SALES_DATE column to partition on. Reference partitioning can be used to handle these more complex designs.
SALES_ITEMS中的数据比SALES会更多,因为是多对一的关系

视频

关键点:

  • 声明性的
    子表上的外键可定义分区键
  • 级联截断
    如果需要截断父项及其子项,TRUNCATE 命令中的 CASCADE 选项可用于绕过通常的外键检查
  • 强绑定
    由于数据的紧密耦合性,表被分区引用时有一些限制

示例:

create table PARENT
(
    dte date    not null,
    pk  number(10)  not null,
    pad char(10)
)
partition by range(dte)
(
    partition p1 values less than (to_date('01-JAN-2010')),
    partition p2 values less than (to_date('01-FEB-2010')),
    partition p3 values less than (to_date('01-MAR-2010')),
    partition p4 values less than (to_date('01-APR-2010')),
    partition p5 values less than (to_date('01-MAY-2010')),
    partition p6 values less than (to_date('01-JUN-2010')),
    partition p7 values less than (to_date('01-JUL-2010')),
    partition p8 values less than (to_date('01-AUG-2010')),
    partition p9 values less than (to_date('01-SEP-2010'))
);

alter table PARENT add primary key(pk);

create table CHILD
(
    p number(10) not null,
    c number(10) not null,
    constraint CHILD_FK foreign key (p) references PARENT(pk)
    on delete cascade
)
partition by reference (CHILD_FK);

insert into PARENT select to_date('01-JAN-2010')+rownum, rownum, rownum
from dual connect by level <= 100;

insert into CHILD select rownum, rownum
from dual connect by level <= 100;

exec dbms_stats.gather_table_stats(null, 'CHILD');
select partition_name, num_rows from user_tab_partitions
where table_name = 'CHILD';

输出如下,可以看到子表也分区了:

PARTITION_NAME	NUM_ROWS
P1	0
P2	30
P3	28
P4	31
P5	11
P6	0
P7	0
P8	0
P9	0
Download CSV
9 rows selected.

Indexes on Partitioned Tables/分区表上的索引

An index normally points to a row in a physical segment (a table). But a partitioned table consists of multiple physical segments, so the index structures and the data contained within the index need to be slightly different for partitioned tables. If the index spans all partitions, what happens when we perform maintenance on a single partition, such as dropping it? What happens to the index entries that reference the now dropped partition?

视频

关键点:

  • 全局的
    索引跨越所有分区,这意味着每个索引条目的 ROWID 更大,以引用分区(数据库对象)以及行在该分区中的位置
  • 维护
    修改表分区(截断、拆分、合并、删除等)可以将索引标记为 UNUSABLE 状态。默认情况下,发生这种情况时不会引发任何错误,您的查询计划只是不再使用索引。UPDATE INDEXES 子句在这里可能是有益的
  • 异步
    在 12c 及更高版本中,DROP 和 TRUNCATE 分区维护对全局索引的影响较小,因为索引清理是在后台完成的。此特性对大量数据删除有用

分区表如果使用传统的索引,即全局索引,则删除分区时,索引状态为变为UNUSABLE。
你可以在删除分区同时更新索引:

alter table <table_name> drop partition <partition_name> update index;

跳过不可用索引默认设置为TRUE:

SQL> show parameter skip

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
skip_unusable_indexes                boolean     TRUE

全局索引不仅要记录rowid,还需记录分区所在的segment ID。即extended rowid。

ROWID是虚拟列,占用10个字节:

SQL> select rowid, vsize(rowid), dump(rowid) from part where rownum <2;

ROWID              VSIZE(ROWID)
------------------ ------------
DUMP(ROWID)
--------------------------------------------------------------------------------
AAASNxAAMAAAAGDAAA           10
Typ=69 Len=10: 0,1,35,113,3,0,1,131,0,0

Locally Partitioned Indexes/本地分区索引

An index can be equipartitioned with its underlying table. Such an index is known as a local index(可以与global ). Local indexes can have significantly benefits when it comes to Information Lifecycle Management (ILM). Because table partitions operations such as TRUNCATE and DROP and isolated to a single index partition, the remainder of the index remains available and never needs unncessary maintenance. Local indexes are also lend themselves to easier partition exchange which is a useful technique for either archiving old data or introducing new data into a partitioned table with zero downtime.

全局分区索引(或全局索引)是一个index segment映射多个table partition segment。而本地分区索引(或本地索引)则是index segment与table partition segment一一对应。本地索引相相当于对索引进行了分区。

如果此时删除一个分区,则相应的本地分区索引也被删除,其他的分区和索引均无影响。

create table sales
(
    tstamp  date    not null,
    empno   number(10)  not null,
    amount  number(10)  not null
)
partition by range (tstamp)
interval (numtoyminterval(1, 'YEAR'))
(
    partition p2009 values less than (DATE '2010-01-01'),
    partition p2010 values less than (DATE '2011-01-01'),
    partition p2011 values less than (DATE '2012-01-01'),
    partition p2012 values less than (DATE '2013-01-01')
);

create index sales_ix on sales(tstamp) local;

insert into sales values(DATE '2010-01-01', 100, 100);
insert into sales values(DATE '2011-01-01', 200, 200);
insert into sales values(DATE '2012-01-01', 300, 300);

alter table sales drop partition p2009;

select partition_name, status from user_ind_partitions
where index_name = 'SALES_IX';

输出如下,可以看到删除分区后,其它分区的索引仍可用:

PARTITION_NAME	STATUS
P2010			USABLE
P2011			USABLE
P2012			USABLE

3 rows selected.

有了本地索引,ILM会变得更方便:


alter table sales move partition p2010 compress;

alter table sales move partition p2010 tablespace ts_archive;

-- 这样RMAN就不会反复备份
alter tablespace ts_lowperformance read only;

alter table sales modify partition p2010 unusable local indexes;

alter table sales modify partition p2010 indexing off;

将较老的分区禁止本地索引,是因为这些老的数据大多用于分析。当然也有将当前分区禁止索引的,这适用于使用Database In-Memory的情形。

exchange partition就不介绍了。

Local versus Global Indexes/本地与全局索引

The previous two videos discuss several apparent limitations of global indexes and plenty of advantages of local indexes. This seems to suggest that local indexes should always be used in preference to global indexes. This is not the case, and each indexing technique should be employed to best match the application requirements you have. A local index can be the absolutely wrong choice for particular kinds of queries. However, sometimes you may need to choose some design compromises if you want to get the best of both worlds - partition independence but still with solid declarative database design.

本节讲述如何在本地和全局索引间进行选择。两者都有各自适用的场景。

视频

关键点:

  • 读取乘数(Read Mulitplier)
    请注意,索引查找不会因错误选择本地索引分区策略而降低性能。
  • 设计折衷
    有时将分区键带入主键的物理设计可能是有益的,即使逻辑数据库设计不需要它。
  • 12c及以上
    全局索引的异步清理使得本地索引和全局索引之间的区别不太明显。

选择本地还是全局索引取决于分区裁剪是否有效,也就是在谓词中是否包含了分区键。

例如对于本地索引,如果查询条件中无分区键,并且索引键也不在分区键中,那么就需要遍历所有分区,此操作非常昂贵,对于复合分区尤甚。此时用全局索引就比较合适。

如果过滤谓词中有分区键可以利用,则本地索引也可以排上用场。本地索引对于ILM非常适合,而全局索引则便于快速查找,具体用谁,要看应用场景。

Special Indexing Use-cases/特殊索引用例

This tutorial series has covered the common partitioning strategies adopted by developers to build successful applications on large volumes of data. However, there are niche cases(特殊情况) that also need to be considered. For example, you could have an index that partitioned on a table that is not partitioned. Similarly, the index partitioning strategy might not align with the partitioning strategy for the underlying table. Such examples are typically rare, however there is one important case where hash partitioning an index is critical to achieve extreme levels of OLTP performance.

视频

关键点:

  • 灵活性
    索引的分区不需要与其所基于的表的分区相匹配,但这种情况很少见。
  • 争用
    分区是将活动分布到多个段以减少对公共数据块的争用的有效方法。
  • GLOBAL 关键字
    虽然大多数分区语法只是简单的 PARTITION BY,但要对索引进行分区,您需要使用 GLOBAL 关键字作为前缀。

对于非分区表,没有本地索引的概念,但索引仍可以分区,语法如下:

create index idx1 on sales(id) 
GLOBAL PARTITION BY ...

另一个特殊用例是hash partitioned index。
对于高频插入,由于插入时按序的,写操作会集中在索引的leading leaf block,从而带来并发性问题。
如果使用hash partitioned index,则会将热点分散开(此时会有8个索引的leading leaf block):

create index idx1 on sales(id) 
GLOBAL PARTITION BY HASH(txn_id) partitions 8;

Querying Partitioned Tables/查询分区表

Perhaps the most attractive benefit of partitioning is that performance improvements that can be achieved when querying partitioned tables. Partition pruning is the term used to describe when the predicates of a query are such that the entire table does not need to be scanned, but only a subset of the partitions. Detecting partition pruning relies on understanding the execution plan for a given query.

视频

关键点:

  • Pstart/Pstop
    执行计划中的 Pstart/Pstop 列显示扫描的分区范围。当显示 KEY 时,这意味着决定是在执行时而不是在解析时做出的。
  • 绑定
    分区修剪适用于绑定变量以及分区键谓词的文字值(literal values)。
  • 间隔
    由于间隔分区表已在逻辑上定义了所有 100 万个(分区数量的上限)分区,因此 Pstart/Pstop 值可能会产生误导。
  • 强大
    修剪可以与相等谓词、范围谓词、in-list表达式和许多其他排列一起发生。

查询效率=需要的数据 / 扫描的数据

drop table DEMO purge;

create table DEMO
(
    tstamp timestamp not null,
    empno   number(10) not null,
    ename   varchar2(10) not null,
    deptno  varchar2(10) not null
)
partition by range(tstamp)
(
    partition p00 values less than (timestamp '2010-01-01 00:00:00'),
    partition p01 values less than (timestamp '2010-02-01 00:00:00'),
    partition p02 values less than (timestamp '2010-03-01 00:00:00'),
    partition p03 values less than (timestamp '2010-04-01 00:00:00'),
    partition p04 values less than (timestamp '2010-05-01 00:00:00'),
    partition p05 values less than (timestamp '2010-06-01 00:00:00'),
    partition p06 values less than (timestamp '2010-07-01 00:00:00'),
    partition p07 values less than (timestamp '2010-08-01 00:00:00'),
    partition p08 values less than (timestamp '2010-09-01 00:00:00'),
    partition p09 values less than (timestamp '2010-10-01 00:00:00'),
    partition p10 values less than (timestamp '2010-11-01 00:00:00'),
    partition p11 values less than (timestamp '2010-12-01 00:00:00'),
    partition p12 values less than (timestamp '2011-01-01 00:00:00')
);

insert /*+ APPEND */ into DEMO
select 
    trunc(date '2010-01-01', 'YYYY') + mod(rownum, 360),
    rownum,
    rownum,
    mod(rownum, 1000)
from dual
connect by level <= 1000000;

commit;

查看执行计划,PARTITION RANGE SINGLE表示只查询了单个分区:

set lines 140
set autotrace on
select count(*) from DEMO where tstamp = to_date('01-JUN-2010');

------------------------------------------------------------------------------------------------
| Id  | Operation               | Name | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT        |      |     1 |    11 |    91   (2)| 00:00:01 |       |       |
|   1 |  SORT AGGREGATE         |      |     1 |    11 |            |          |       |       |
|   2 |   PARTITION RANGE SINGLE|      |  2778 | 30558 |    91   (2)| 00:00:01 |     7 |     7 |
|*  3 |    TABLE ACCESS FULL    | DEMO |  2778 | 30558 |    91   (2)| 00:00:01 |     7 |     7 |
------------------------------------------------------------------------------------------------

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

   3 - filter("TSTAMP"=TIMESTAMP' 2010-06-01 00:00:00')

由于使用了函数,以下SQL无法使用分区修剪,因此是PARTITION RANGE ALL:

set lines 140
set autotrace on
select count(*) from DEMO where trunc(tstamp) = to_date('01-JUN-2010');

---------------------------------------------------------------------------------------------
| Id  | Operation            | Name | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
---------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |      |     1 |    11 |  1176   (4)| 00:00:01 |       |       |
|   1 |  SORT AGGREGATE      |      |     1 |    11 |            |          |       |       |
|   2 |   PARTITION RANGE ALL|      | 10000 |   107K|  1176   (4)| 00:00:01 |     1 |    13 |
|*  3 |    TABLE ACCESS FULL | DEMO | 10000 |   107K|  1176   (4)| 00:00:01 |     1 |    13 |
---------------------------------------------------------------------------------------------

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

   3 - filter(TRUNC(INTERNAL_FUNCTION("TSTAMP"))=TO_DATE(' 2010-06-01 00:00:00',
              'syyyy-mm-dd hh24:mi:ss'))

绑定变量也可以使用分区修剪,KEY关键字表示分区修剪是在运行时决定的,而非解析时:

set lines 140
set autotrace on

SQL> variable b1 varchar2(20);
SQL> begin
  2  :b1 := '01-JUN-2010';
  3  end;
  4  /

PL/SQL procedure successfully completed.

SQL> print b1

B1
--------------------------------------------------------------------------------------------
01-JUN-2010

SQL> select count(*) from DEMO where tstamp = :b1;

  COUNT(*)
----------
         0


Execution Plan
----------------------------------------------------------
Plan hash value: 1642956652

------------------------------------------------------------------------------------------------
| Id  | Operation               | Name | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT        |      |     1 |    11 |    91   (2)| 00:00:01 |       |       |
|   1 |  SORT AGGREGATE         |      |     1 |    11 |            |          |       |       |
|   2 |   PARTITION RANGE SINGLE|      |  2778 | 30558 |    91   (2)| 00:00:01 |   KEY |   KEY |
|*  3 |    TABLE ACCESS FULL    | DEMO |  2778 | 30558 |    91   (2)| 00:00:01 |   KEY |   KEY |
------------------------------------------------------------------------------------------------

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

   3 - filter("TSTAMP"=TO_TIMESTAMP(:B1))

PARTITION RANGE OR的场景,即KEY(OR)

select count(*) from DEMO
where 
	tstamp between to_date('12-JAN-2010') and to_date('07-FEB-2010')
or 
	tstamp between to_date('03-JUN-2010') and to_date('06-AUG-2010');

--------------------------------------------------------------------------------------------
| Id  | Operation           | Name | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
--------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |      |     1 |    11 |     3   (0)| 00:00:01 |       |       |
|   1 |  SORT AGGREGATE     |      |     1 |    11 |            |          |       |       |
|   2 |   PARTITION RANGE OR|      |   247K|  2658K|     3   (0)| 00:00:01 |KEY(OR)|KEY(OR)|
|*  3 |    TABLE ACCESS FULL| DEMO |   247K|  2658K|     3   (0)| 00:00:01 |KEY(OR)|KEY(OR)|
--------------------------------------------------------------------------------------------

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

   3 - filter("TSTAMP">=TIMESTAMP' 2010-06-03 00:00:00' AND "TSTAMP"<=TIMESTAMP'
              2010-08-06 00:00:00' OR "TSTAMP"<=TIMESTAMP' 2010-02-07 00:00:00' AND
              "TSTAMP">=TIMESTAMP' 2010-01-12 00:00:00')

PARTITION RANGE INLIST的场景:

select count(*) from DEMO
where 
	tstamp in (
	to_date('12-JAN-2010'),
	to_date('07-FEB-2010')
	);

------------------------------------------------------------------------------------------------
| Id  | Operation               | Name | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT        |      |     1 |    11 |     2   (0)| 00:00:01 |       |       |
|   1 |  SORT AGGREGATE         |      |     1 |    11 |            |          |       |       |
|   2 |   PARTITION RANGE INLIST|      |  5556 | 61116 |     2   (0)| 00:00:01 |KEY(I) |KEY(I) |
|*  3 |    TABLE ACCESS FULL    | DEMO |  5556 | 61116 |     2   (0)| 00:00:01 |KEY(I) |KEY(I) |
------------------------------------------------------------------------------------------------

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

   3 - filter("TSTAMP"=TIMESTAMP' 2010-01-12 00:00:00' OR "TSTAMP"=TIMESTAMP'
              2010-02-07 00:00:00')

看一个数据不在分区中的执行计划:

select count(*) from DEMO where tstamp = to_date('01-JUN-2022');
-----------------------------------------------------------------------------------------------
| Id  | Operation              | Name | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
-----------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT       |      |     1 |    11 |     2   (0)| 00:00:01 |       |       |
|   1 |  SORT AGGREGATE        |      |     1 |    11 |            |          |       |       |
|   2 |   PARTITION RANGE EMPTY|      |     1 |    11 |     2   (0)| 00:00:01 |INVALID|INVALID|
|*  3 |    TABLE ACCESS FULL   | DEMO |     1 |    11 |     2   (0)| 00:00:01 |INVALID|INVALID|
-----------------------------------------------------------------------------------------------

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

   3 - filter("TSTAMP"=TIMESTAMP' 2022-06-01 00:00:00')


间隔分区比较特殊,Pstop为1048575,即1024乘以1024减1:

drop table demo purge;

create table DEMO
(
    tstamp timestamp not null,
    empno   number(10) not null,
    ename   varchar2(10) not null,
    deptno  varchar2(10) not null
)
partition by range(tstamp)
interval (numtoyminterval(1, 'MONTH'))
(
	partition p00 values less than
	(timestamp '2010-01-01 00:00:00')
);

select * from demo;

no rows selected


Execution Plan
----------------------------------------------------------
Plan hash value: 2349549400

--------------------------------------------------------------------------------------------
| Id  | Operation           | Name | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
--------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |      |     1 |    40 |     2   (0)| 00:00:01 |       |       |
|   1 |  PARTITION RANGE ALL|      |     1 |    40 |     2   (0)| 00:00:01 |     1 |1048575|
|   2 |   TABLE ACCESS FULL | DEMO |     1 |    40 |     2   (0)| 00:00:01 |     1 |1048575|
--------------------------------------------------------------------------------------------

Note
-----
   - dynamic statistics used: dynamic sampling (level=2)

-- 最后看一个数据不在分区中的执行计划
select count(*) from DEMO where tstamp = to_date('01-JUN-2022');

------------------------------------------------------------------------------------------------
| Id  | Operation               | Name | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT        |      |     1 |    13 |     2   (0)| 00:00:01 |       |       |
|   1 |  SORT AGGREGATE         |      |     1 |    13 |            |          |       |       |
|   2 |   PARTITION RANGE SINGLE|      |     1 |    13 |     2   (0)| 00:00:01 |   151 |   151 |
|*  3 |    TABLE ACCESS FULL    | DEMO |     1 |    13 |     2   (0)| 00:00:01 |   151 |   151 |
------------------------------------------------------------------------------------------------

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

   3 - filter("TSTAMP"=TIMESTAMP' 2022-06-01 00:00:00')

Partition Queries with Joins/带联结的分区查询

Partitioned tables are rarely queried in isolation, and thus gaining the benefits of partition pruning even when a partitioned table is joined to another table, or involved with a subquery is a critical feature of the Oracle Database. By utilising a memory structure know as a Bloom filter, the database can quickly identify which partitions will be needed to satisfy a given query.

视频

关键点:

  • BF前缀
    执行计划中的 :BF[digit] 条目表示正在使用布隆过滤器,例如:BF0001
  • 高效的
    布隆过滤器使用高效的内存结构来识别哪些分区可以被修剪。
  • 负面/正面
    使用 Bloom 过滤器,可能会出现误报(false positive),因此连接操作可能仍会多处理少许数据,但不可能出现漏报(false negatives),因此您的查询不会出现不正确的结果。

Bloom Filter在维基中的定义:

A Bloom filter is a space-efficient probabilistic data structure, conceived by Burton Howard Bloom in 1970, that is used to test whether an element is a member of a set. False positive matches are possible, but false negatives are not

对于false positive和false negative,作者举了个例子。买电影票,网站说有票,但实际没票,这就是前者;网站说没票,那就确实没票了,不可能出现后者。

Other Performance Opportunities/其它性能机会

When two tables have identical partitioning definitions, the database can take advantage of this knowledge. If we had tables of animals, then commonsense tells us we will never match breeds between the dogs partition in one table and the cats partition in the other table. A join should be able to detect when two disparate partitions could never have a matching row, and therefore be eliminated from a join operation. This is known as a partition-wise join(基于分区的join或分区join).

关键点:

  • 完全相同的
    分区定义必须完全对齐才能发生完整的分区连接
  • 参考
    参考分区是分区连接的完美候选者,因为两个相关表之间的分区根据定义是相同的
  • 计划层级
    了解 JOIN 和 PARTIION 行在执行计划中的位置以检测分区连接。

视频
作者给了个袜子配对的例子,但我没看懂。

select ... from SOCKS n, SOCKS s
where n.style_size = s.style_size;

经过2天时间,终于学完了。

猜你喜欢

转载自blog.csdn.net/stevensxiao/article/details/127836162
今日推荐