数据库开发管理规范-PARTITION TABLE(分区)

孤者浪人 提交于 2020-04-27 22:14:37
随着时间的推移及公司业务不断发展,各系统业务数据越来越庞大,数据库中的数据量不断膨胀,导致业务系统的查询效率低下,运行缓慢,且庞大的数据量也不易管理维护。基于这种情况,一个有效的解决方案是使用分区表和分区索引。但由于以往分区表的应用较少,对于大部分IT人员来说,它们还是比较陌生的。为了指导开发人员合理的创建和使用分区表及分区索引,特建立此规范。


1.2 适用范围#

本规范适用于开发人员、数据库管理人员。

1.3 术语和缩略语#

此处请填入文档中的专业术语及解释。
序号 术语/缩略语 全称和说明
1 分区表 (PARTITION TABLE) 分区表将数据分成被称为分区甚至子分区的更小的、可以单独管理的块,而这些块共享相同的逻辑属性
2 全局索引 建立在整个表上而非单独分区上的索引
3 本地索引 建立在分区或者子分区上的索引

1.4、简介#

分区表允许将数据分成被称为分区甚至子分区的更小、更好管理的块。索引也可以这么分区。每个分区可以被单独管理,可以不依赖于其他分区而单独发挥作用,因此对提高系统的可用性和性能有重要的意义。


2、设计要求规范#

2.1 分区依据#

2.1.1 分区的目的是为了性能优化或数据管理(数据归档、大批量数据删除等);在解决这其中一个需求的前提下,且表大于2GB,才考虑做分区。

2.1.2 建议1:分区是优化全表扫描的手段,当查询返回的结果集超过表的总数据量的10%,Oracle会倾向于走全表扫描。为降低I/O的消耗,可考虑做分区。
对于已经使用索引并且结果集较小的情况,做分区不一定能提升性能。分区表并不是用于,将一个大表走索引变成小表走索引。如果返回的结果集有一定的数据量,能将查询的数据量集中在一个分区中进行扫描,效率会有一定的提高。

2.2 分区设计规范#

2.2.1 分区设计中需要特别关注,最终的数据要均匀分布到每个分区,偏大或偏小的比例不超过15%。
这样可以提高效率,尤其是在并行查询时。

2.2.2 如果一个字段的UPDATE在一周内超过该表的总UPDATE行数的10%(参考Oracle的定义:当数据的10%被修改后,其Statistics被认为是失真的),则该字段不允许作为分区列。
如果分区列的值被修改,频繁引起ROW MOVEMENT,会影响分区的性能,因为UPDATE操作会引起对原分区作DELETE操作,另一个分区作INSERT操作,从而影响性能。
获取UPDATE行数的方法参考附录D。

2.2.3 分区越多,ORACLE维护成本越高,管理越复杂。因此非哈希分区不要超过32个分区,哈希分区不要超过64个分区。
Hash分区主要用于并发查询和解决热点问题。考虑到主机CPU的并发处理能力,64个Hash分区已经足够。

2.2.4 建议:在实际的应用中,可以根据需要用不同的粒度进行分区。例如,同一张表中,当年数据可按月份进行分区,当年之前的数据可按年进行分区。

2.2.5 建议:列表分区通常用于应用中可以通过某一个字段来平均分布工作负载。例如RAC中,分节点跑不同的应用,同一张表上的数据也对应到不同的分区上,减少不同节点上的数据传输开销。还可以对应应用的分区,达到逻辑上分离数据库的作用,例如根据某一个字段的状态进行数据的管理、归档等。注意,一些状态字段如果变化非常频繁,会导致大量的row movement,影响性能,因此对于状态字段变化非常多的情况不适用。

2.2.6 建议:哈希分区的优点:每个分区的数量平均,分区并行时效率高,根据hash值插入数据,可以将数据插入到不同的块,在并行度高时有利于提高效率。哈希分区还可以用于解决热块的问题。当无法采用范围分区或列表分区时,可以考虑采用哈希分区。

2.2.7 建议:复合分区的选择,从性能、数据管理(数据归档)、资源分布(应用的分区)、数据分布等多个方面进行选择。如果侧重于今后对按时间进行数据归档,则可以采取range-list分区方式;如果侧重于资源分布,则可以采用list-range分区方式。如果两方面都不太侧重,则可以再关注下表的数据分布,根据数据的分布情况来决定采用何种复合分区方式。

2.2.8 生产系统中违规超过规范限定的分区个数时,可参照下列思路考虑改造方案:
1)对于非Hash分区,要考虑达到32个分区的使用现状,是活跃数据已经达到最大限制,还是存在非活跃数据归档后没有及时合并或删除空的分区。活跃数据已经达到32个分区的情况,请架构师一起参与评估,确认合理后可以提交例外申请;其他情况,在性能影响评估后,考虑下发版本merge分区或者drop掉空的分区。
2)对于Hash分区,由于增加或减少分区数量,都可能涉及到数据的重新分布,需要请架构师一起分析分区数量大于64的原因,确认分区合理可以申请例外,对于不合理的分区,可以在性能影响评估后,使用语句ALTER TABLE schema.tablename COALESCE PARTITION来减少分区数量。

2.3 各种分区方式相关规范#

2.3.1 分区列必须为NOT NULL。
分区列不能为NULL,必须为NOT NULL。如果有NULL 值插入的话会进入MAXVALUE或default对应的分区,影响未来对MAXVALUE或default分区的split,以及在null改为not null值时产生row movement。

2.3.2 除采用11g的interval方式分区以外,范围分区方法都需要指定maxvalue分区。
因为如果数据在分区字段上插入或者更新的值超出所有分区范围,除非指定了maxvalue分区,否则该操作将会报错。例如:

CREATE TABLE epcis_sales (  acctount_no  NUMBER(5),  person       VARCHAR2(30),  sales_amount NUMBER(8),  department_code    varchar2(10))  PARTITION BY RANGE (department_code) (  PARTITION epcis_sales_201_pt VALUES LESS THAN (201) tablespace t1_data,  PARTITION epcis_sales_202_pt VALUES LESS THAN (202) tablespace t2_data,  ...  PARTITION epcis_sales_208_pt VALUES LESS THAN (208) tablespace t8_data, PARTITION epcis_sales_max_pt VALUES LESS THAN (maxvalue) tablespace             t_data);
如上面的分区表,如果插入一条记录:
Insert into epcis_sales (acctount_no,person,sales_amount,department_code) values  (10001,’test’,200,‘209’);
那么这条记录将被存放在epcis_sales_max_pt这个分区中。如果表上没有这个maxvalue分区,插入该数据时,数据库将会报错。为了避免这种情况,采用范围分区方法时,必须指定maxvalue的分区。

2.3.3 使用范围分区和列表分区时必须使用 ENABLE ROW MOVEMENT。
当表中一行数据的分区字段的值被更新时,如果允许该数据移动到新的分区上,则在创建分区表时,需使用ENABLE ROW MOVEMENT,默认是不允许移动的,如果此时更新分区列,导致分区字段的值超出该分区的范围时,将会报错。因此,为了避免出现这种情况,必须允许记录移动;例如:

CREATE TABLE epcis_sales (  acctount_no  NUMBER(5),  person       VARCHAR2(30),  sales_amount NUMBER(8),  department_code    varchar2(10))  PARTITION BY RANGE (department_code) (  PARTITION epcis_sales_201_pt VALUES LESS THAN (201) tablespace t1_data,  PARTITION epcis_sales_202_pt VALUES LESS THAN (202) tablespace t2_data,  ...  PARTITION epcis_sales_208_pt VALUES LESS THAN (208) tablespace t8_data,  PARTITION epcis_sales_max_pt VALUES LESS THAN (maxvalue) tablespace t_data) ENABLE ROW MOVEMENT;


2.3.4 使用列表分区时,分区中必须包含default分区。
例如,有以下分区表:

CREATE TABLE sales_by_region ( Dept_no number, Dept_name varchar2(20), quarterly_sales number(10,2), state varchar2(2)) PARTITION BY LIST(state) ( PARTITION sales_by_region_east_pt VALUES('SH','NJ') tablespace t1_data, PARTITION sales_by_region_south_pt VALUES('GZ','SZ','HN') tablespace t2_data, PARTITION sales_by_region_west_pt VALUES('XN','NN') tablespace t3_data, PARTITION sales_by_region_north_pt VALUES('SY','LZ') tablespace t4_data) ENABLE ROW MOVEMENT;
如果向上表中插入一列state=’AA’的数据,数据库将会报错,因为该分区表并没有指定state的值为’AA’时的分区。为了避免出现报错的情况,在列表分区时,必须包含default分区。
示例:
CREATE TABLE sales_by_region ( Dept_no number, Dept_name varchar2(20), quarterly_sales number(10,2), state varchar2(2)) PARTITION BY LIST(state) ( PARTITION sales_by_region_east_pt VALUES('SH','NJ') tablespace t1_data, PARTITION sales_by_region_south_pt VALUES('GZ','SZ','HN') tablespace t2_data, PARTITION sales_by_region_west_pt VALUES('XN','NN') tablespace t3_data, PARTITION sales_by_region_north_pt VALUES('SY','LZ') tablespace t4_data, PARTITION sales_by_region_other_pt VALUES(DEFAULT)  tablespace t5_data) ENABLE ROW MOVEMENT;
这样,当插入state=’AA’的数据时,该记录会映射到sales_by_region_other_pt分区。

2.3.5 列表分区中分区列值必须区分大小写。
列表分区中的分区列值是大小写敏感的,上面的例子中,如果插入下列值:

insert into sales_by_ragion values(100,’test’,200,’sh’);
该记录不会映射到sales_by_region_east_pt分区,而是映射到sales_by_region_other_pt分区,因为大小写敏感的缘故,数据库认为sh和SH是不同的值。

2.3.6 使用哈希分区时,分区数量必须要是2的N次方幂。
例如2,4,8,16,32,64。

2.4分区表索引设计规范#

2.4.1 分区表索引设计规范
1. 如果分区列是索引的子集(无论是prefix还是non-prefix),则建立local index。否则,请参照准则2。
2. 如果索引是唯一的,则建立global index。否则,请参照准则3。
3. 建议:如果基于性能考虑,且应用是OLTP类型,用户需要更快的响应时间,则建立global index;若应用是DSS类型,且用户更注重吞吐量,则建立local index。如果基于维护性考虑,则建立local index.

Local 索引较 global 索引具有更高的可维护性;local index能对不同的分区使用并行访问,可以提高数据库单位时间内的总体吞吐量;local prefixed index使得优化器可以直接根据分区做pruning限制至特定的分区,可以适当引入OLTP系统;global index更适于 OLTP类型应用;对于等于或小范围查询较多的表,global index有比local index更快的响应时间。

2.5分区表使用规范#

2.5.1 若表做了范围分区,范围查询要限制在一个分区内,不能跨分区;分区的宽度要按查询的范围做合理的设置;跨分区的情况,可以通过重新定义分区宽度,或改写SQL用并行来提高性能。

2.5.2 若表做了列表和哈希分区,分区字段不允许做范围查询。

2.5.3 建议:在分区表与分区表关联的查询中,如果关联条件正好是分区字段的话,在执行计划中会产生partition join的方式,更加提高效率。

附录#

附录A 分区技术资料#

分区技术资料
表分区方法      ORACLE提供有如下几种分区方法:      a:范围分区(range partitioning);      b:哈希分区(hash partitioning);      c:列表分区(list partitioning);      d:范围-哈希组合分区(composite range-hash partitioning);      e:范围-列表组合分区(composite range-list partitioning);      ORACLE版本支持的分区策略如下: ORACLE版本        支持的分区策略 Oracle 8        范围分区 Oracle 8i       哈希分区、范围-哈希复合分区 Oracle 9i       范围分区、哈希分区、范围-哈希组合分区/列表分区 Oracle 9i R2    范围分区、哈希分区、范围-哈希组合分区、列表分区、范围-列表组合分区  范围分区方法 使用原则及基本语法 当数据可以被均匀的划分成逻辑范围时,如年度中的月份,就可以用这种类型的分区。数据在整个范围中能被均等地划分时性能最好。 如果范围分区会由于不均等的划分而导致分区在大小上明显不同时,就需要考虑其他分区方法。 创建范围分区时,必须指定: 分区方法:范围,分区列 ,标识分区边界的分区描述; 示例: CREATE TABLE sales (  acctount_no  NUMBER(5),      person       VARCHAR2(30),  sales_amount NUMBER(8),  sell_date    CHAR(8))      PARTITION BY RANGE (sell_date) (  PARTITION pt_sales_2007_q1 VALUES LESS THAN (20070401),  PARTITION pt_sales_2007_q2 VALUES LESS THAN (20070701),  ...      PARTITION pt_sales_2007_q4 VALUES LESS THAN (20080101),  PARTITION pt_sales_2008     VALUES LESS THAN (maxvalue)); 范围分区表的每个分区都被存储在单独的段中,互不影响,且每个分区都可指定单独的 数据表空间。  哈希分区方法 如果数据不那么容易进行范围分区,但为了性能和管理的原因又想分区时,就使用哈希分区方法。 哈希分区提供了一种在指定数量的分区中均等地划分数据的方法。创建和使用哈希分区会提供了一种很灵活的放置数据的方法, 进而来影响可用性和性能。 为了创建哈希分区,需要指定: 分区方法:哈希,分区列,分区数量或单独的分区描述 示例: CREATE TABLE test (  id NUMBER,  name VARCHAR2(60))  PARTITION BY HASH (id)   PARTITION pt_ test_1,  PARTITION pt_ test_2,  PARTITION pt_ test_3,  PARTITION pt_ test_3); 该示例创建了一个哈希分区表,分区列是id,创建4个分区并指定每个分区的名称; 使用哈希分区,用户不能控制哪些数据放在哪些分区里,由数据库自动控制的。 列表分区方法   使用原则及基本语法 当需要明确控制如何将数据行映射到分区时,就使用列表分区方法。列表分区可以非常自然地将无序的和不相关的数据集进行分组和组织到一起。 与范围分区和哈希分区所不同,列表分区不支持多列分区,分区键就只能由表的一个单独的列组成。 当创建列表分区时,必须指定: 分区方法:列表,分区列,分区描述――每个描述指定一串文字值(值的列表), 示例: CREATE TABLE sales_by_region ( deptno number, deptname varchar2(20), quarterly_sales number(10,2), state varchar2(2)) PARTITION BY LIST(state) ( PARTITION pt_sales_by_region_east VALUES('SH','NJ'), PARTITION pt_sales_by_region_south VALUES('GZ','SZ','HN'), PARTITION pt_sales_by_region_west VALUES('XN','NN'), PARTITION pt_sales_by_region_north VALUES('SY','LZ')) ENABLE ROW MOVEMENT;       
  包含default分区 如果向上表中插入一列state=’AA’的数据,数据库将会报错,因为该分区表并没有指定state的值为’AA’时的分区。 为了避免出现报错的情况,在列表分区时,必须包含default分区。 示例: CREATE TABLE sales_by_region ( deptno number, deptname varchar2(20), quarterly_sales number(10,2), state varchar2(2)) PARTITION BY LIST(state) ( PARTITION pt_sales_by_region_east VALUES('SH','NJ'), PARTITION pt_sales_by_region_south VALUES('GZ','SZ','HN'), PARTITION pt_sales_by_region_west VALUES('XN','NN'), PARTITION pt_sales_by_region_north VALUES('SY','LZ'), PARTITION pt_sales_by_region_other VALUES(DEFAULT) ) ENABLE ROW MOVEMENT;         这样,当插入state=’AA’的数据时,该记录会映射到pt_sales_by_region_other分区。 大小写敏感          list分区中的分区列表值是大小写敏感的,上面的例子中,如果插入下列值:          insert into sales_by_ragion values(100,’test’,200,’sh’);          该记录不会映射到pt_sales_by_region_east分区,而是映射到pt_sales_by_region_other分区, 因为大小写敏感的缘故,数据库认为shSH是不同的值。 范围-哈希组合分区方法 该分区方法是在分区中使用范围分区方法分区,而在子分区中则使用哈希分区方法。它改善了范围分区及其数据放置的可管理性, 并提供了哈希分区的并行机制的优点。 当创建组合时,要指定: 分区方法:范围 分区列 标识分区边界的分区描述 子分区方法:哈希 子分区列 每个分区的子分区数量,或子分区的描述 示例: CREATE TABLE test ( Equip_no   NUMBER, Equip_name VARCHAR(32), price     NUMBER) PARTITION BY RANGE (equip_no) SUBPARTITION BY HASH(equip_name)  ( PARTITION pt_test_1000 VALUES LESS THAN (1000)  (           SUBPARTITION pt_test_1000_h1,           SUBPARTITION pt_test_1000_h2,           SUBPARTITION pt_test_1000_h3,           SUBPARTITION pt_test_1000_h4), PARTITION p2 VALUES LESS THAN (2000) (           SUBPARTITION pt_test_2000_h1,           SUBPARTITION pt_test_2000_h2,           SUBPARTITION pt_test_2000_h3,           SUBPARTITION pt_test_2000_h4), PARTITION p3 VALUES LESS THAN (MAXVALUE) (           SUBPARTITION pt_test_max_h1,           SUBPARTITION pt_test_max_h2,           SUBPARTITION pt_test_max_h3,           SUBPARTITION pt_test_max_h4) ) ENABLE ROW MOVEMENT; 在本列中,创建了3个范围分区,每个范围分区包含4个子分区。 范围-列表组合分区 如范围-哈希组合分区方法一样,范围-列表组合分区也是有两层分区:第一层分区基于一个范围值,第二层分区基于一系列离散值。 这种组合分区兼有范围分区和列表分区的优点。 当创建组合时,要指定: 分区方法:范围 分区列 标识分区边界的分区描述 子分区方法:列表 子分区列 子分区描述,为每个子分区指定一系列离散值; 示例: CREATE TABLE regional_sales ( depment_no number, item_no varchar2(20), sale_date date, sale_amount number, state varchar2(2)) PARTITION BY RANGE (sale_date) SUBPARTITION BY LIST (state) (PARTITION regional_sales_1999 VALUES LESS THAN (TO_DATE('1999-04-01','YYYY-MM-DD'))( SUBPARTITION pt_regional_sales_q1_east VALUES ('OR', 'WA'), SUBPARTITION pt_regional_sales_q1_south VALUES ('AZ', 'UT', 'NM'), SUBPARTITION pt_regional_sales_q1_west VALUES ('NY', 'VM', 'NJ'), SUBPARTITION pt_regional_sales_q1_north VALUES ('FL', 'GA'), SUBPARTITION pt_regional_sales_q1_other VALUES(DEFAULT) ), PARTITION q2_1999 VALUES LESS THAN ( TO_DATE('1999-07-01','YYYY-MM-DD'))( SUBPARTITION pt_regional_sales_q2_east VALUES ('OR', 'WA'), SUBPARTITION pt_regional_sales_q2_south VALUES ('AZ', 'UT', 'NM'), SUBPARTITION pt_regional_sales_q2_west VALUES ('NY', 'VM', 'NJ'), SUBPARTITION pt_regional_sales_q2_north VALUES ('FL', 'GA') SUBPARTITION pt_regional_sales_q2_other VALUES(DEFAULT) ), PARTITION q3_1999 VALUES LESS THAN (maxvalue)( SUBPARTITION pt_regional_sales_q3_east VALUES ('OR', 'WA'), SUBPARTITION pt_regional_sales_q3_south VALUES ('AZ', 'UT', 'NM'), SUBPARTITION pt_regional_sales_q3_west VALUES ('NY', 'VM', 'NJ'), SUBPARTITION pt_regional_sales_q3_north VALUES ('FL', 'GA'), SUBPARTITION pt_regional_sales_q3_other VALUES(DEFAULT))) ENABLE ROW MOVEMENT; 分区索引         索引可以像表一样能够被分区,分区或者非分区索引都能够使用在分区或者非分区表上。分区索引有两种类型:         a:全局索引;         b:本地索引;  全局索引(global index            全局索引又可分为全局分区索引(global partitioned index)和全局非分区索引(global non-partitioned index),示例:        创建全局分区索引:        CREATE INDEX ix_sales_month ON sales(sales_month) GLOBAL PARTITION BY RANGE(sales_month)( PARTITION ix_sales_month_2 VALUES LESS THAN (2), PARTITION ix_sales_month_3 VALUES LESS THAN (3), PARTITION ix_sales_month_4 VALUES LESS THAN (4), PARTITION ix_sales_month_5 VALUES LESS THAN (5), PARTITION ix_sales_month_6 VALUES LESS THAN (6), PARTITION ix_sales_month_7 VALUES LESS THAN (7), PARTITION ix_sales_month_8 VALUES LESS THAN (8), PARTITION ix_sales_month_9 VALUES LESS THAN (9), PARTITION ix_sales_month_10 VALUES LESS THAN (10), PARTITION ix_sales_month_11 VALUES LESS THAN (11), PARTITION ix_sales_month_12 VALUES LESS THAN (12), PARTITION ix_sales_month_13 VALUES LESS THAN (MAXVALUE)) ;     创建全局非分区索引(即普通索引): CREATE INDEX ix_sales_month ON sales(sales_month) GLOBAL;    
       需要注意的是,全局分区索引必须是前缀索引(Prefixed index),即索引的分区键第一个字段与表的分区列是相同的, ORACLE不支持非前缀的全局分区索引,而全局非分区索引没有这个限制。 本地索引(local index) 本地索引必须是建立在分区表上。本地索引也分为前缀索引和非前缀索引, 索引分区键第一个字段与表的分区列相同的为前缀索引,否则为非前缀索引。 示例: 如有下列分区表:      CREATE TABLE department_define (      department_code NUMBER,      department_name VARCHAR2(10),      remark          VARCHAR2(14))      PARTITION BY RANGE (department_code) (      PARTITION pt_department_define_30 VALUES LESS THAN (30),      PARTITION pt_department_define_max VALUES LESS THAN (MAXVALUE)) ENABLE ROW MOVEMENT; 在该表上创建本地前缀索引: CREATE  INDEX  ix_department_define_code ON department_define(department_code) LOCAL TABLESPACE PCISBASE_IDX ; 创建本地非前缀索引:     CREATE  INDEX  ix_department_define_name  ON department_define (department_name) LOCAL TABLESPACE PCISBASE_IDX ; 自动更新全局索引         对分区表的许多表维护操作会使全局索引不可用(标记成UNUSABLE)。那么,就必须重建整个全局索引。 Oracle允许在用于维护操作的ALTER TABLE语句中,指定UPDATE GLOBAL INDEXES。指定这个子句也就告诉Oracle,当它执行维护操作的DDL语句时, 更新全局索引。 在对分区表的某个分区作维护操作同时自动更新全局索引,会导致DDL要执行更长的时间;与先不更新索引而执行DDL,再重建索引所花的时间相比较, 谁具有优势也很难断言。一个适用的规则是,如果维护操作的分区大小小于表大小的5%,则自动更新索引更快点。 示例: Alter table sales drop partition department_define_30 update global indexes; 如下操作支持UPDATE GLOBAL INDEXES子句: ADD PARTITION|SUBPARTITION(仅限于哈希分区) DROP PARTITION EXCHANGE PARTITION|SUBPARTITION MERGE PARTITION MOVE PARTITION|SUBPARTITION SPLIT PARTITION TRUNCATE PARTITION|SUBPARTITION COALESCE PARTITION|SUBPARTITION

附录B 11G分区的新特性#

1. Interval Partitioning
11g中生成新分区的这项工作可以交由Oracle自动完成,指定需要Oracle自动创建分区的间隔时间,下面这个例子是1个月,然后至少创建一个基本分区。
例子中20110101之前的所有数据都在epcis_sales_2010_pt分区中,以后每个月的数据都会存放在Oracle自动创建的一个新分区中。
CREATE TABLE epcis_sales 
(
 acctount_no  NUMBER(5),
 person       VARCHAR2(30),
 sales_amount NUMBER(8),
 department_code    varchar2(10),
 sale_time date)
PARTITION BY RANGE (sale_time) 
INTERVAL(NUMTOYMINTERVAL(1, 'month')) 
(PARTITION epcis_sales_2010_pt VALUES LESS THAN (TO_DATE('20110101', 'yyyymmdd'))); 


2. System Partitioning
在这个新的类型中,我们不需要指定任何分区键,数据会进入哪个分区完全由应用程序决定,
实际上也就是由SQL来决定,即在Insert语句中可以指定插入哪个分区了。
由于System Partitioning的特殊性,这种类型的分区将不支持Partition Split操作,也不支持create table as select操作。
举例:创建了下面这张分区表,没有指定任何分区键
CREATE TABLE sales_by_region (
Dept_no number, 
Dept_name varchar2(20), 
quarterly_sales number(10,2), 
state varchar2(2))
PARTITION BY SYSTEM 
( 
PARTITION sales_by_region_east_pt TABLESPACE tbs_1, 
PARTITION sales_by_region_south_pt TABLESPACE tbs_2, 
PARTITION sales_by_region_west_pt TABLESPACE tbs_3, 
PARTITION sales_by_region_north_pt TABLESPACE tbs_4
);

现在由SQL语句来指定插入哪个分区:
--数据插入sales_by_region_east_pt分区
INSERT INTO sales_by_region PARTITION(sales_by_region_east_pt) VALUES (100,’test’,100,’SH’);
--数据插入第2个分区,也就是sales_by_region_south_pt分区
INSERT INTO sales_by_region PARTITION(sales_by_region_south_pt) VALUES (200,’test2’,200,’SZ’);
--为了实现绑定变量,用pno变量来代替实际分区号,以避免过度解析
INSERT INTO systab PARTITION(:pno) VALUES (300,’test3’,300,’LZ’);


3. Virtual Column-Based Partitioning
Virtual Column是11g中的一个新功能,这种列中的数据并不实际存储于磁盘上(可以看成是一个类似Function的列),
只有当读取的时候才实时计算。
虚拟列虽然没有实际的存储空间,但是却可以跟其他普通列一样,创建索引,作为分区键,甚至可以收集统计信息。
可以通过这样的语句来创建虚拟列:
CREATE TABLE tb_v 
(col_1 number(6) not null, 
col_2 number not null, 
col_v as (col_1 *( 1+col_2)));


4、Reference Partitioning
order_itmes表自动创建了和父表相对应的分区。不能指定引用分区的上下限,但可以给分区命名,如果没有显示的给各个分区命名,
将自动继承父表的分区名称。
一旦父表的分区发生变化,子表分区也会自动适应,而单独修改子表分区则不被允许。
CREATE TABLE orders
( order_id NUMBER(12),
  order_date DATE,
  order_mode VARCHAR2(8),
  customer_id NUMBER(6),
  CONSTRAINT pk_orders PRIMARY KEY(order_id)
)
PARTITION BY RANGE(order_date)
  (PARTITION orders_2011_Q1_pt VALUES LESS THAN (TO_DATE('2011-4-1','yyyy-mm-dd')),
   PARTITION orders_2011_Q2_pt VALUES LESS THAN (TO_DATE('2011-7-1','yyyy-mm-dd')),
   PARTITION orders_2011_Q3_pt VALUES LESS THAN (TO_DATE('2011-10-1','yyyy-mm-dd')),
   PARTITION orders_2011_Q4_pt VALUES LESS THAN (TO_DATE('2012-1-1','yyyy-mm-dd')))
;

CREATE TABLE order_items
( order_id NUMBER(12) ,
  line_item_id NUMBER(3),
  product_id NUMBER(6),
  unit_price NUMBER(8,2),
  quantity NUMBER(8),
  CONSTRAINT pk_order_items PRIMARY KEY(order_id,line_item_id),
  CONSTRAINT fk_order_items FOREIGN KEY(order_id) REFERENCES orders(order_id)
)
PARTITION BY REFERENCE(fk_order_items);

select PARTITION_NAME from dba_tab_partitions
where table_name='ORDERS';

PARTITION_NAME
------------------------------
ORDERS_2011_Q4_PT
ORDERS_2011_Q3_PT
ORDERS_2011_Q2_PT
ORDERS_2011_Q1_PT            

select PARTITION_NAME from dba_tab_partitions
where table_name='ORDER_ITEMS';

PARTITION_NAME
------------------------------
ORDERS_2011_Q4_PT
ORDERS_2011_Q3_PT
ORDERS_2011_Q2_PT
ORDERS_2011_Q1_PT

alter table orders drop partition orders_2011_Q1_pt;
select PARTITION_NAME from dba_tab_partitions where table_name='ORDERS';

PARTITION_NAME
------------------------------
ORDERS_2011_Q4_PT
ORDERS_2011_Q3_PT
ORDERS_2011_Q2_PT

select PARTITION_NAME from dba_tab_partitions where table_name='ORDER_ITEMS';

PARTITION_NAME
------------------------------
ORDERS_2011_Q4_PT
ORDERS_2011_Q3_PT
ORDERS_2011_Q2_PT

alter table order_items drop partition orders_2011_Q2_pt;
报错:
ORA-14255: 未按范围, 列表, 组合范围或组合列表方法对表进行分区


5. More Composite Partitioning
在11g前,复合分区只支持Range-List和Range-Hash;
在11g中复合分区的类型大大增加,现在Range,List,Interval都可以作为Top level分区,而Second level则可以是Range,List,Hash,
也就是在11g中可以有3*3=9种复合分区。


附录C 索引分区的语法举例#

对分区表上的索引再建分区,语法参考如下


1、建分区表
create table orders(
order_no      number,
part_no       varchar2(40),
ord_date      date
)
partition by range (ord_date)
(partition orders_201101_pt values less than (TO_DATE('2011-02-01','YYYY-MM-DD')),
partition orders_201102_pt values less than (TO_DATE('2011-03-01','YYYY-MM-DD')),
partition orders_201103_pt values less than (TO_DATE('2011-04-01','YYYY-MM-DD')),
partition orders_201104_pt values less than (TO_DATE('2011-05-01','YYYY-MM-DD'))
)ENABLE ROW MOVEMENT;

2、建global分区索引,global索引分区的键值不用和表的分区键值相同
create index orders_ord_date_global_idx
on orders(ord_date)
global partition by range (ord_date)
(partition orders_global_idx_201101_pt values less than (TO_DATE('2011-02-01','YYYY-MM-DD')),
partition orders_global_idx_201104_pt values less than (TO_DATE('2011-05-01','YYYY-MM-DD')),
partition orders_global_idx_201107_pt values less than (TO_DATE('2011-08-01','YYYY-MM-DD')),
partition orders_global_idx_max_pt values less than (MAXVALUE)
);

3、Oracle不支持非前缀的全局分区索引,如果需要建立非前缀分区索引,索引必须建成本地索引。
下面两条建索引的语句都会报错:"ORA-14038: GLOBAL partitioned index must be prefixed"
/*
create index orders_part_no_global_idx
on orders(part_no)
  global partition by range (order_no)
   (partition orders_global_idx_555555_pt values less than (555555),
    partition orders_global_idx_max_pt values less than (MAXVALUE)
   );         

create index orders_part_no_global_idx
on orders(part_no)
global partition by range (ord_date)
(partition orders_global_idx_201101_pt values less than (TO_DATE('2011-02-01','YYYY-MM-DD')),
partition orders_global_idx_201102_pt values less than (TO_DATE('2011-03-01','YYYY-MM-DD')),
partition orders_global_idx_201103_pt values less than (TO_DATE('2011-04-01','YYYY-MM-DD')),
partition orders_global_idx_max_pt values less than (MAXVALUE)
);

*/

建立带前缀的全局分区索引,正确的sql如下:
create index orders_part_no_global_idx
on orders(ord_date,part_no)
global partition by range (ord_date)
(partition orders_global_idx_201201_pt values less than (TO_DATE('2012-02-01','YYYY-MM-DD')),
partition orders_global_idx_201202_pt values less than (TO_DATE('2012-03-01','YYYY-MM-DD')),
partition orders_global_idx_201203_pt values less than (TO_DATE('2012-04-01','YYYY-MM-DD')),
partition orders_global_idx_max_pt values less than (MAXVALUE)
);


4、建local分区索引,local索引分区的键值必须和表的分区键值相同
4.1、注释中的语句加上时,会报错:"ORA-14010:this physical attribute may not be specified for an index partition"
drop index orders_ord_date_global_idx;
create index orders_ord_date_local_idx
on orders(ord_date)
local  
(partition orders_local_idx_201101_pt /*values less than (TO_DATE('2011-02-01','YYYY-MM-DD'))*/,  
partition orders_local_idx_201102_pt /*values less than (TO_DATE('2011-03-01','YYYY-MM-DD'))*/,
partition orders_local_idx_201103_pt /*values less than (TO_DATE('2011-04-01','YYYY-MM-DD'))*/,
partition orders_local_idx_max_pt /*values less than (MAXVALUE)*/
);

4.2、注释中的语句执行时,会报错:
ORA-14024:number of partitions of LOCAL index must equal that of the underlying table
/*
create index orders_party_no_local_idx
     on orders(part_no)
      local 
   (partition orders_local2_idx_201101_pt,
    partition orders_local2_idx_max_pt
   );   */

4.3、正确的sql如下,虽然索引的键值是part_no(无用),但索引分区的键值仍然和表的分区键值相同,即ord_date:
create index orders_party_no_local_idx
     on orders(part_no)
      local 
  (partition orders_local2_idx_201101_pt ,
partition orders_local2_idx_201102_pt ,
partition orders_local2_idx_201103_pt,
partition orders_local2_idx_max_pt
);

附录D 获取UPDATE行数的方法#

说明:执行用户需要有查询v$sql的权限和在自己名下建表的权限。
步骤(以下步骤都在同一执行用户下完成):
1、	执行initial_get_col_update_tab.sql,创建表coll_col_update_info,该表用来存放捕获的表字段更新信息;
2、	创建一个排程任务(Job,Cron都可以),该任务定期执行get_col_update.sql,用来定期收集被更新的表和字段的信息。
3、	收集到一定时间以后(例如一周),从表coll_col_update_info里面以字段名为条件过滤统计rows_processed量并和
所有的rows_processed统计量对比即可得到某一特定字段更新的占比。

initial_get_col_update_tab.sql
create table coll_col_update_info as 
select sysdate as snap_time,
       parsing_schema_name,
       COMMAND_TYPE,
       sql_id,
       updated_table,
       updated_sql_section,
       ceil(rows_processed / active_duration) as update_per_second,
       ceil(executions / active_duration) as exec_per_second,
       executions,
       rows_processed,
       first_load_time,
       last_active_time
  from (select parsing_schema_name,
               sql_id,
               COMMAND_TYPE,
               substr(upper(sql_text),
                      instr(upper(sql_text), 'UPDATE') + 7,
                      instr(upper(sql_text), ' SET ') -
                      (instr(upper(sql_text), 'UPDATE') + 7)) as updated_table,
               upper(substr(sql_text,
                            instr(upper(sql_text), 'SET'),
                            case
                              when instr(upper(sql_text), 'WHERE') = 0 then
                               length(sql_text)
                              else
                               instr(upper(sql_text), 'WHERE')
                            END - instr(upper(sql_text), 'SET'))) as updated_sql_section,
               executions,
               rows_processed,
               to_date(first_load_time, 'YYYY-MM-DD HH24:MI:SS') as first_load_time,
               last_active_time,
               case
                 when (last_active_time -
                      to_date(first_load_time, 'YYYY-MM-DD HH24:MI:SS')) * 24 * 3600 <= 0 then
                  1
                 else
                  (last_active_time -
                  to_date(first_load_time, 'YYYY-MM-DD HH24:MI:SS')) * 24 * 3600
               end as active_duration
          from v$sql
         where command_type = 6 /*command_type equal 6 means update sql*/
           and executions > 0) col_update_total
 where 1=2
 order by executions desc;


get_col_update.sql
insert into coll_col_update_info 
select parsing_schema_name,
       COMMAND_TYPE,
       sql_id,
       updated_table,
       updated_sql_section,
       rows_processed,
       executions,
       rows_processed,
       first_load_time,
       last_active_time
  from (select parsing_schema_name,
               sql_id,
               COMMAND_TYPE,
               substr(upper(sql_text),
                      instr(upper(sql_text), 'UPDATE') + 7,
                      instr(upper(sql_text), ' SET ') -
                      (instr(upper(sql_text), 'UPDATE') + 7)) as updated_table,
               upper(substr(sql_text,
                            instr(upper(sql_text), 'SET'),
                            case
                              when instr(upper(sql_text), 'WHERE') = 0 then
                               length(sql_text)
                              else
                               instr(upper(sql_text), 'WHERE')
                            END - instr(upper(sql_text), 'SET'))) as updated_sql_section,
               executions,
               rows_processed,
               to_date(first_load_time, 'YYYY-MM-DD HH24:MI:SS') as first_load_time,
               last_active_time,
               case
                 when (last_active_time -
                      to_date(first_load_time, 'YYYY-MM-DD HH24:MI:SS')) * 24 * 3600 <= 0 then
                  1
                 else
                  (last_active_time -
                  to_date(first_load_time, 'YYYY-MM-DD HH24:MI:SS')) * 24 * 3600
               end as active_duration
          from v$sql
         where command_type = 6 /*command_type equal 6 means update sql*/
           and executions > 0) col_update_total
 where rows_processed / active_duration > 1
   and updated_sql_section is not null
   and ((active_duration >= 3600 and rows_processed / active_duration > 0.1) or
       ((active_duration < 3600 and rows_processed / active_duration > 1) and
       rows_processed > 1000))
   and updated_table like '<YOUR_TABLE_NAME>%' 
 order by executions desc;
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!