一直想系统的聊聊分区。网络上Oracle技术中,讨论的最多的话题之一就是Partition。各种分区类型、分区组合和随之而来的各种优缺点,一直被大家讨论。但是,实际中,我们往往看到很多以偏概全、舍本逐末的分区使用现象。
各种分区类型和创建语句,是很多文章探讨的中心。一些开发设计人员,甚至是DBA也感觉分区成了大表的万灵药。笔者这个系列文章不想讨论各种语法和分区使用,而是集中在几个常见话题难点。从“思想问题”,到“细枝末节”,归纳一下对于分区Partition技术,我们应该了解什么。
本篇作为系列的开篇,首先看看为什么要分区。
1、“大表要分区”
几年前,还经常有朋友在网络或者技术讨论沙龙中跳出来,发表所谓的设计经验集,其中就包括“大表要分区”。是不是数据表一大,我们就要分区,就要用各种手段将其“大卸八块”呢?
笔者的答案是:不一定。
让我们一起先看看Oracle分区技术的出发点和发展轨迹。
Oracle最早的分区版本,就已经定义了分区的原则:段segment的分割。传统的认识中,一个数据表或者索引作为独立的段对象,是占据磁盘存储空间,并且单独进行计量。
而分区Partition技术的出现,改变了这个情况。一个数据表未必是一个段对象,而是多个段对象。当然,一个段也可能承载多个数据表。传统的分区实现了两个功能,一是人为的在数据对象定义层面,将其划分为多个逻辑存储部分,第二是确定了数据记录归属原则,什么样的数据,放在哪个分区中。
下面是一个最简单的范围分区数据表定义。
SQL> create table t
2 partition by range (object_id)
3 ( partition p1 values less than (10000),
4 partition p2 values less than (maxvalue)
5 ) as select * from dba_objects;
Table created
SQL> select partition_name, segment_type from dba_segments where owner='SCOTT' and segment_name='T';
PARTITION_NAME SEGMENT_TYPE
------------------------------ ------------------
P1 TABLE PARTITION
P2 TABLE PARTITION
在定义中,SQL语句确定了分区数据表被分割在多个数据逻辑段segment中,如果条件允许,这些Segment是可以分布在多个表空间、以致到多个物理存储设备上的。分区提供了在定义阶段确定的一种数据存储规划策略。
Oracle分区Partition技术的原始出发点是应对海量数据表,这也就是为什么Partition至今归属在Oracle DW(Data Warehouse)产品技术序列的原因。从根本上看,Oracle Partition技术带给我们的是两方面的好处:性能和管理。
性能提升,可能是大多数开发架构师和数据库设计人员选择Partition的原始初衷。好像一张数据表大了之后,我们只要分区了,就可以多少倍的提升性能。但是,很多时候事与愿违。在Oracle的世界中,同样没有“Silver Bullet”。一项技术的引入,必然有前提和适应范围。如果使用正确,分区配合适当的索引方案的确可以提高性能。
管理优势其实并不算Oracle Partition的初衷。但是随着版本的升级,Oracle对于分区管理手段支持提供了更多的功能特性。管理方面的优势已经在很多方面超越性能,成为我们选择分区技术的首要因素。
管理优势是一个非常大的范围,主要是运维范畴和领域的问题。具体来说包括:管理便捷性(Ease of Administration)、数据删除(Data Purge)、数据归档(Data Archive)、数据全生命周期管理(Data Lifecycle Management)和高效备份(Efficiency Backup)几个层面。
下面笔者将从几个方面分别介绍分区的这些特性优势。
2、环境介绍
笔者选择Oracle 11g来进行试验,中间过程使用之前创建的海量数据表T。
SQL> select * from v$version;
BANNER
--------------------------------------------------------------------------------
Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - Production
PL/SQL Release 11.2.0.1.0 - Production
CORE 11.2.0.1.0 Production
TNS for Linux: Version 11.2.0.1.0 - Production
NLSRTL Version 11.2.0.1.0 - Production
Executed in 0.031 seconds
SQL> select count(*) from t;
COUNT(*)
----------
72761
Executed in 0.016 seconds
3、Partition性能优化
Partition最早的推出,就是为了性能上的优化。传统的Segment概念下,一个数据表对应一个数据段。而Partition的推出,将一个数据对象(数据表或者索引)拆分为多个段对象,进而可以放在不同的表空间里。
Partition对于性能的提升,主要体现在分散IO和分区裁剪(partition pruning)两个方面。
先聊聊分散IO。
我们在过去一些Oracle优化建议中,经常看到一条“秘籍”:将数据表段和索引段分布在不同的表空间里。为什么会有这个概念呢?
表空间是由不同的数据文件构成。一个数据文件只能对应一个表空间,一个表空间可以有多个数据文件进行对应。在同一个表空间里,我们不能控制数据究竟是放在哪个文件上。
将数据段和索引段分布在不同的表空间里,才能保证两者在不同的数据文件里面。这个不是最关键的问题。最关键的问题在于:只有在不同的数据文件里面,我们才可能将其分布在不同的磁盘上。
我们的磁盘是IO的重要设备,性能领域中IO是一直需要关注的方面,也是非常容易形成瓶颈的方面。磁盘存储设备的IO体现在TPS上,这个往往是由于设备的物理上限决定的。通常我们的Oracle调优手段,比如索引、IOT,目的都是为了减少IO量,也就是减少SQL语句对IO的需求量。
但是需求通常是无限的。当我们的应用进行软优化之后,的确需要如并行或者高IO读取的时候,IO就成为应用的底线。一般来说,存储设备的上限TPS是硬件参数,没有过高的超越空间存在。解决的方法之一就是并行,将一个SQL的IO过程分散在多个磁盘设备上进行。多个磁盘设备同时工作的时候,是有并行的效果的。如果总线或者网络条件允许的话,并行合力是可以体现出超过单个IO盘的吞吐量的。
如果我们规划过程,将一个数据表拆分为多个段segment结构,进而放在不同的表空间,最后放在不同的磁盘上。针对数据表不同分区的访问就可以实现IO“分散”的效果。总体合力上就可以实现性能提升。
但是,随着技术的发展,这样的优势已经不存在,或者说已经不需要了。硬件层面,RAID技术的不断发展,特别是条带化(Stripe),已经实现了数据分散在多个存储设备上,获取硬件资源提升。软件层面上,OS提供的Logical Volume、Oracle ASM都是将数据“打散”的技术。
再说说分区裁剪(Partition Pruning)。
Oracle的分区中有一个重要方面是分区规则的确定,一旦确定分区规则,数据行就按照规则“自动”分配到各个分区段Segment中(11g中System Partition例外)。分区裁剪的基本思想在于:原有需要访问全部数据表的数据才能确定的结果,在分区的情况下,借用分区规则带来的规律性,我们可以节省一部分的IO消耗量。
下面是一个分区裁剪的例子。
SQL> create table t_part
2 partition by list (owner)
3 (
4 partition p1 values ('SYS'),
5 partition p2 values ('PUBLIC'),
6 partition p3 values (default)
7 )
8 as select * from t where 1=0;
Table created
SQL> insert into t_part select * from t;
72761 rows inserted
SQL> commit;
Commit complete
SQL> exec dbms_stats.gather_table_stats(user,'T',cascade => true);
PL/SQL procedure successfully completed
SQL> exec dbms_stats.gather_table_stats(user,'T_PART',cascade => true);
PL/SQL procedure successfully completed
我们对t_part采用owner分区策略,之后比较按照owner进行检索的性能差异。
SQL> explain plan for select * from t where owner='SCOTT';
Explained
SQL> select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 1601196873
--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 13 | 1261 | 289 (1)| 00:00:04 |
|* 1 | TABLE ACCESS FULL| T | 13 | 1261 | 289 (1)| 00:00:04 |
--------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("OWNER"='SCOTT')
13 rows selected
SQL> explain plan for select * from t_part where owner='SCOTT';
Explained
SQL> select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 2970683307
--------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 25 | 2450 | 53 (0)| 00:00:01 |
| 1 | PARTITION LIST SINGLE| | 25 | 2450 | 53 (0)| 00:00:01 |
|* 2 | TABLE ACCESS FULL | T_PART | 25 | 2450 | 53 (0)| 00:00:01 |
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("OWNER"='SCOTT')
14 rows selected
从上面的执行计划,我们的确看到了性能上的提升。当我们使用适合分区工作的where条件时,Partition是可以有不错的性能提升的。
当我们没有分区的时候,Oracle检索owner=’SCOTT’的时候,因为是堆表Heap Table,存储是随机的,Server Process需要访问每一个数据表块,才能确认结果集合。在分区的时候,由于分区键和owner有关,Oracle可以明确的判定说,分区p1和p2里面肯定没有owner=’SCOTT’的记录,所以就不用检查p1和p2了,从而能够提高性能,降低成本。
但是,应该看到分区裁剪的两个问题。首先,如果分区表情况下,要使用分区裁剪,就只能(注意是只能)顺应分区键约束。如果应用的SQL语句是和分区键无关的,那么SQL成本通常是更高的。
SQL> explain plan for select * from t;
Explained
SQL> select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 1601196873
--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 72761 | 6892K| 289 (1)| 00:00:04 |
| 1 | TABLE ACCESS FULL| T | 72761 | 6892K| 289 (1)| 00:00:04 |
--------------------------------------------------------------------------
8 rows selected
SQL> explain plan for select * from t_part;
Explained
SQL> select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 2002420342
--------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Ps
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 72761 | 6892K| 267 (1)| 00:00:04 |
| 1 | PARTITION LIST ALL| | 72761 | 6892K| 267 (1)| 00:00:04 |
| 2 | TABLE ACCESS FULL| T_PART | 72761 | 6892K| 267 (1)| 00:00:04 |
--------------------------------------------------------------------------------
9 rows selected
上面的SQL语句中,如果我们的条件中没有owner分区键,就会访问所有的分区,也就是Partition List ALL操作。相同数据量情况下,检索所有分区,进行所有分区的访问成本要高于不分区的数据表。这就形成了我们使用分区表,借助分区裁剪的一个现实条件:SQL语句中,要出现分区键。
我们说:数据是由业务活性的。无论是业务分区、还是时间,很多这种活性都是我们选择分区键,添加入SQL语句的依据。但是困难在于两个方面:一个是分区键的选择,设计人员是否可以“预见”到应用系统中SQL语句必然加入的条件。第二个困难是如何保证所有的开发人员都“自觉”将分区条件加入到SQL语句,即使很多时候不是很需要。
另一方面,使用分区裁剪,大部分情况下需要Local Index的配合。我们在实际中经常遇到这样的现象,分区之后的数据表SQL:要不就出现了分区裁剪,在单个分区中进行FTS(全表扫描),要不就走了创建的索引路径,浪费了分区“地利”。
SQL> create index idx_t_part_id on t_part(object_id);
Index created
SQL> exec dbms_stats.gather_table_stats(user,'T_PART',cascade => true);
PL/SQL procedure successfully completed
SQL> explain plan for select * from t_part where owner='SCOTT' and object_id=20000;
Explained
SQL> select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 2004327352
--------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cos
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 98 |
|* 1 | TABLE ACCESS BY GLOBAL INDEX ROWID| T_PART | 1 | 98 |
|* 2 | INDEX RANGE SCAN | IDX_T_PART_ID | 5 | |
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("OWNER"='SCOTT')
2 - access("OBJECT_ID"=20000)
15 rows selected
这种时候,设计人员就需要额外的数据库知识,就是本地索引。只有借用本地索引,才有可能两者兼得。
SQL> drop index idx_t_part_id;
Index dropped
SQL> create index idx_t_part_idp on t_part(object_id) local;
Index created
SQL> exec dbms_stats.gather_table_stats(user,'T_PART',cascade => true);
PL/SQL procedure successfully completed
SQL> explain plan for select * from t_part where owner='SCOTT' and object_id=20000;
Explained
SQL> select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 1293386573
--------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Co
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 98 |
| 1 | PARTITION LIST SINGLE | | 1 | 98 |
|* 2 | TABLE ACCESS BY LOCAL INDEX ROWID| T_PART | 1 | 98 |
|* 3 | INDEX RANGE SCAN | IDX_T_PART_IDP | 1 | |
--------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("OWNER"='SCOTT')
3 - access("OBJECT_ID"=20000)
16 rows selected
在很多时候,我们的确是需要借用Local Index来同时发挥分区+索引的优势。可能很多朋友会说,在实际中我们需要借助两个的优势吗?笔者的回答是:非常有必要!
分区表对应的通常是海量表,不管创建的初衷是什么。每个分区的体积通常是很大的,生产环境上一个分区都是几个G左右。即使分区了,检索一个分区的FTS也是很可怕的。所以要在优化层面上进行多层次、多角度的优化,才能综合形成结果。所以,真正有效的分区表索引,通常是本地索引Local Index。
在分区表索引问题上,很多时候需要讨论Prefix和Non-Prefix,笔者会在本系列的其他部分进行更详细的介绍。
Partition的提出,最初就是为了性能。但是应该说随着硬件技术的发展,IO方面已经有了很好的提升。而且分区裁剪在侧重一方面性能提升的情况下,是会起身其他访问方式的SQL性能的。相当于无形之中,数据表的使用被人为加入很多限制。而且,分区裁剪在很多时候要求开发设计人员具有一定的基础知识能力。
相对于性能方面的优势,管理方面的优势是近年来更加受到侧重选择的方面。
上篇我们介绍了Oracle分区表在性能方面的优势,本篇集中在管理层面,讨论下管理层面的优势。
4、管理便捷性
相对于一个大数据对象(数据表、索引),分区化首先给我们带来的就是管理便捷。一个很大的数据表,无论是进行数据表重构,还是存储位置操作,都会面临连带负载问题。在生产环境下,这种负载变化尤其是我们希望避免的。
借助分区,我们可以将大对象划分为一系列小对象进行单独处理。也就是将一个突然性的大操作转变为一系列的小规模操作进行,从而减少对其他正在运行作业的影响。
下面是数据表T_PART,对应的本地索引。
SQL> select partition_name, tablespace_name from dba_segments where owner='SYS' and segment_name='T_PART';
PARTITION_NAME TABLESPACE_NAME
------------------------------ ------------------------------
P1 SYSTEM
P2 SYSTEM
P3 SYSTEM
SQL> select partition_name, status from dba_ind_partitions where index_owner='SYS' and index_name='IDX_T_PART_IDP';
PARTITION_NAME STATUS
------------------------------ --------
P1 USABLE
P2 USABLE
P3 USABLE
当我们需要调整一个数据表分区的表空间,到users表空间,我们可以使用单独的命令进行处理,而不会影响到其他数据。
SQL> alter table t_part move partition p1 tablespace users;
Table altered
SQL> select partition_name, tablespace_name from dba_segments where owner='SYS' and segment_name='T_PART';
PARTITION_NAME TABLESPACE_NAME
------------------------------ ------------------------------
P1 USERS
P2 SYSTEM
P3 SYSTEM
针对分区索引,如果出现失效的情况,我们也是可以进行部分rebuild。
SQL> select partition_name, status from dba_ind_partitions where index_owner='SYS' and index_name='IDX_T_PART_IDP';
PARTITION_NAME STATUS
------------------------------ --------
P1 UNUSABLE
P2 USABLE
P3 USABLE
SQL> alter index idx_t_part_idp rebuild partition p1;
Index altered
SQL> select partition_name, status from dba_ind_partitions where index_owner='SYS' and index_name='IDX_T_PART_IDP';
PARTITION_NAME STATUS
------------------------------ --------
P1 USABLE
P2 USABLE
P3 USABLE
此外,Oracle提供的很多针对分区的操作,比如导出特定分区数据、删除分区数据等,都是管理便捷性的体现。
5、数据删除
对大数据表,数据退出生命周期之后,就可以从数据表中进行删除。从数据活跃性理论角度看,任何数据在数据表中都是有生命周期的,一旦经过了业务处理高峰期,就应该会在某个特定标准框架下被删除。
大数据量删除是应用运维领域一个比较麻烦的事情。因为通常情况下,删除数据总数虽然很大,但是往往占到全部数据表比例不高。比如,每次Purge数据,可能都是数据表中最老的一周数据,而数据表中包括了近三个月数据。
一种结合设计的手段是将分区融入到过程中,将一次删除的数据放在相同的分区上,这样有两个好处。一个是可以将活性相同的数据放在相同位置上,避免低活性数据的影响。另一个是可以借助分区层次上的操作手段,快速进行数据删除操作。
SQL> select partition_name, tablespace_name from dba_segments where owner='SYS' and segment_name='T_PART';
PARTITION_NAME TABLESPACE_NAME
------------------------------ ------------------------------
P1 USERS
P2 SYSTEM
P3 SYSTEM
SQL> set timing on;
SQL> alter table t_part drop partition p3;
Table altered
Executed in 0.453 seconds
SQL> select partition_name, tablespace_name from dba_segments where owner='SYS' and segment_name='T_PART';
PARTITION_NAME TABLESPACE_NAME
------------------------------ ------------------------------
P1 USERS
P2 SYSTEM
Executed in 0.015 seconds
注意:Drop Partition操作和普通的delete是有显著性区别的。Delete操作的核心在于“打标记”。Server Process检索所有符合条件的数据记录,标记为删除。这个过程的时间往往和数据量相关。
而Drop Partition是一个DDL过程,这个过程中,数据并没有被删除,而是数据表的分区定义发生了变化,让Oracle“不再承认”分区的存在。这种方法删除数据和Truncate Table有相似之处。优点是速度快,适合大数据分区表的删除动作。
作为提醒注意一下,对于drop partition操作,Global Index和Local Index的行为是有差异的,这一点和上面move操作的结果相同。如果是Local Index,其他分区的变化并不影响本分区的索引作用。所以,Local Index还是存在的。但是,Global Index由于结构上的特殊性,需要进行额外的rebuild操作。
当然,我们可以在drop partition的时候,连带加入update global indexes字句到drop partition命令中,实现自动的global索引重构。
SQL> create index idx_t_part_idg on t_part(object_name);
Index created
Executed in 0.983 seconds
SQL> alter table t_part drop partition p2 update global indexes;
Table altered
Executed in 0.609 seconds
SQL> select status from dba_indexes where owner='SYS' and index_name='IDX_T_PART_IDG';
STATUS
--------
VALID
Executed in 0.031 seconds
6、数据归档
归档Archive和删除是有一些差别的。删除表示的是数据不再需要,可以直接永久删除。实际生产环境中,直接删除的情况是比较少的,大部分情况是需要保留一个备份数据。只是将这个备份数据Offline出系统范围。
这个时候,使用分区数据表技术,可以快速的进行归档动作,卸载数据。先准备数据。
SQL> drop table t_part;
Table dropped
Executed in 1.03 seconds
SQL> create table t_part
2 partition by list (owner)
3 (
4 partition p1 values('SYS'),
5 partition p2 values('PUBLIC'),
6 partition p3 values(default)
7 )
8 as
9 select * from dba_objects;
Table created
Executed in 1.856 seconds
我们希望归档P2数据,可以先创建一个空数据表作为载体。
SQL> create table t_arch_p2 as select * from t_part where 1=0;
Table created
Executed in 0.094 seconds
SQL> alter table t_part exchange partition p2 with table t_arch_p2;
Table altered
Executed in 0.094 seconds
SQL> select count(*) from t_arch_p2;
COUNT(*)
----------
27703
Executed in 0.047 seconds
Exchange partition操作是一种非常强大的工具。语句基于操作定义层面的变化,将数据分区和数据表定义转换。所以,exchange partition操作的速度是非常有优势的。
将分区拆分出数据表t_part,到t_arch_p2独立数据表之后,我们接下来可以将其导出数据库或者备份到其他设备上。
下面我们继续看其他管理方面分区的用法。
我们继续来讨论分区的动机。分区技术Partition已经脱离了原有的性能出发点,而更多的关注海量数据管理问题。本篇会集中在管理中的数据转移、生命周期管理和备份几个角度。
7、数据转移
海量数据一个很常见的模式就是ETL动作。数据分析的基础就是一系列的group汇总,针对交易业务数据的汇总动作。从SQL角度看,group by是一个很消耗资源的动作。而且,group by天然是没有划分这个动作的。
比如,我们基础交易数据t_part,希望将其汇总为t_part_summary,根据owner汇总,以object_id进行加总。
SQL> create table t_part_sum as select owner, sum(object_id) sum_value from t_part where
1=0 group by owner;
Table created
SQL> select count(*) from t_part_sum;
COUNT(*)
----------
0
SQL> desc t_part_sum;
Name Type Nullable Default Comments
--------- ------------ -------- ------- --------
OWNER VARCHAR2(30) Y
SUM_VALUE NUMBER Y
最直接、最传统的做法就是简单的进行group by之后insert。
SQL> set timing on;
SQL> insert into t_part_sum select owner, sum(object_id) from t_part group by owner;
32 rows inserted
Executed in 0.218 seconds
这种方式应该说很简单,一些熟悉Oracle的朋友可能会选择加入append和nologging进行优化。这种手法在数据量少的时候,的确是不错的选择。但是,凡事均有一个适应范围,这种方法潜在两个问题。
首先,直接的insert动作,会产生大量的undo和redo数据,而且是瞬间性的。这个在生产环境下是可能会引起一些性能问题。一般朋友的处理方法就是使用append和nologging,来减少redo数据的生成。此外还可以增大redo log member体积,缓解压力。
第二个问题在于事务的规模。我们一次性的生成数据表全部数据,底层业务数据在group by的全过程中是要一次性读入并且汇总计算。这个操作时间是很难控制的,同时group by动作的调整是和PGA调优、Temp空间调优紧密相关。我们经常遇到的一个场景,就是长时间的group by动作,最后以Temp空间耗尽告终。
笔者有一个论点:SQL语句最大的迷惑之处在于需要开发人员意识到数据量的问题。不同的操作对象选择的操作方法是不同的。
针对第一个问题,也许直接的insert+append+nologging可以暂时满足非功能性需要。针对第二个问题,也许目前系统性能Temp空间可以支持。但是,随着底层业务数据的膨胀,这种状况能持续多久?
解决的方法在于数据操作的平缓化。将一个海量的、不可知总量的操作转化为一系列可控的操作系列是我们处理海量数据的最常规思路。
结合Partition,我们可以实现的更好。
我们首先还是创建汇总表,但是这次的汇总表是一个分区表。
SQL> drop table t_part_sum;
Table dropped
Executed in 1.248 seconds
SQL> create table t_part_summary
2 partition by list (owner)
3 (
4 partition p0 values ('SYS'),
5 partition p1 values ('PUBLIC'),
6 partition p2 values (default)
7 )
8 as select owner, object_type, sum(object_id) sum_value
9 from t_part
10 where 1=0
11 group by owner, object_type;
Table created
Executed in 0.156 seconds
创建出一个临时表,按照分区的方式进行数据部分汇总动作。注意:这边一次性的操作只是在一个分区里面进行,带来的负载是相对小。
SQL> create table t_part_sum_temp as select owner, object_type, sum(object_id) sum_value from t_part where owner='SYS' group by owner, object_type;
Table created
Executed in 0.093 seconds
最后,通过exchange方式进行替换。
SQL> alter table t_part_summary exchange partition p0 with table t_part_sum_temp including indexes;
Table altered
Executed in 0.156 seconds
SQL> select count(*) from t_part_summary;
COUNT(*)
----------
40
Executed in 0.016 seconds
SQL> select count(*) from t_part_sum_temp;
COUNT(*)
----------
0
Executed in 0.031 seconds
将一个insert+group by转化为一系列的group by和exchange partition,是可以适应海量数据维护工作的。
8、数据生命周期
这种管理思路的基础在于一个假设:数据是有生命周期的,是有明显的活性。对大部分的OLTP系统而言,这种说法是有道理的。
一个系统的数据刚刚进入系统中,其活性是非常强的。在很短的时间内,往往要经过系统最复杂的交易逻辑、最严格的运行算法,最后进入报表模块形成交易汇总。这部分对于系统而言,性能是至关重要的。所有的设计都是围绕着这个环节。
当数据经过这个阶段,进入沉积期之后,操作动作就会快速减少。很多时候,系统有没有将这个数据删除,完全不影响系统工作了。这个时期,这部分系统的性能要求是不高的。
我们的数据性能中,很大一个因素在于IO。IO直接是从成本决定的。如果我们有快的IO,还有慢的IO,就可以利用分区技术将不同活性的数据分散在不同的存储设备上,从而获得最优均衡。
SQL> select partition_name, tablespace_name from dba_tab_partitions where
table_owner='SYS' and table_name='T_PART';
PARTITION_NAME TABLESPACE_NAME
------------------------------ ------------------------------
P1 SYSTEM
P2 SYSTEM
P3 SYSTEM
Executed in 0.062 seconds
SQL> alter table t_part move partition p3 tablespace users update global indexes;
Table altered
Executed in 0.28 seconds
SQL> select partition_name, tablespace_name from dba_tab_partitions where
table_owner='SYS' and table_name='T_PART';
PARTITION_NAME TABLESPACE_NAME
------------------------------ ------------------------------
P1 SYSTEM
P2 SYSTEM
P3 USERS
Executed in 0.078 seconds
9、数据备份
Partition能够优化备份工作。这点的源头在于不同数据活性的事实。如果一个分区数据没有活性,不会发生变化了。一种好的做法是将其单独放在一个表空间里面,设置表空间为只读read only。
SQL> alter tablespace users read only;
Tablespace altered
Executed in 0.156 seconds
一旦表空间被设置为read only,从备份角度是存在优化空间的。Oracle进行备份识别的时候,只会备份这个表空间一次。在第二次进行备份的时候,这个表空间就不会进行备份了。
10、结论
Partition是Oracle的一项重要技术,也是我们常见的优化手段。使用分区,首先要明确出使用的目的。在实践中,分区可能会成为双刃剑。分区策略的选择,性能和管理,是需要设计人员明确的重要取舍。如果更关注性能,而且需求层面有很强烈的数据访问规律性,那么可以使用分区。如果更关注管理,数据活跃性是否明显是我们考量的方面。