第13章 分区
分区(partitioning)最早在Oracle 8.0中引入,这个过程是将一个表或索引物理地分解为多个更小、更可管理的部分。就访问数据库的应用而言,逻辑上讲只有一个表或一个索引,但在物理上这个表或索引可能由数十个物理分区组成。每个分区都是一个独立的对象,可以独自处理,也可以作为一个更大对象的一部分进行处理。
注意 分区特性是Oracle数据库企业版的一个选件,不过要另行收费。标准版中没有这个特性。
在这一章中,我们将分析为什么要考虑使用分区。原因是多方面的,可能是分区能提高数据的可用性,或者是可以减少管理员(DBA)的负担,另外在某些情况下,还可能提高性能。一旦很好地了解了使用分区的原因,接下来将介绍如何对表及其相应的索引进行分区。这个讨论的目的并不是教你有关管理分区的细节,而是提供一个实用的指导,教你如何利用分区来实现应用。
我们还会讨论一个重要的事实:表和索引的分区不一定是数据库的一个“fast=true”设置。从我的经验看,许多开发人员和DBA都认为:只要对对象进行分区,就自然而然地会得到性能提升这样一个副作用。但是,分区只是一个工具,对索引或表进行分区时可能发生3种情况:使用这些分区表的应用可能运行得更慢;可能运行得更快;有可能没有任何变化。我的意见是,如果你只是一味地使用分区,而不理解它是如何工作的,也不清楚你的应用如何利用分区,那么分区极可能只会对性能产生负面影响。
最后,我们将分析当今世界使用分区的一种非常常见的用法:在OLTP和其他运营系统中支持大规模的在线审计跟踪。我们将讨论如何结合分区和段空间压缩来高效地在线存储很大的审计跟踪数据,并且能用最少的工作将这个审计跟踪数据中的旧记录进行归档。
1.1 分区概述
分区有利于管理非常大的表和索引,它使用了一种“分而治之”的逻辑。分区引入了一种分区键(partition key)的概念,分区键用于根据某个区间值(或范围值)、特定值列表或散列函数值执行数据的聚集。如果让我按某种顺序列出分区的好处,这些好处如下:
(1) 提高数据的可用性:这个特点对任何类型的系统都适用,而不论系统本质上是OLTP还是仓库系统。
(2) 由于从数据库中去除了大段,相应地减轻了管理的负担。在一个100GB的表上执行管理操作时(如重组来删除移植的行,或者在“净化”旧信息后回收表左边的“空白”空间),与在各个10GB的表分区上执行10次同样的操作相比,前者负担要大得多。另外,通过使用分区,可以让净化例程根本不留下空白空间,这就完全消除了重组的必要!
(3) 改善某些查询的性能:主要在大型仓库环境中有这个好处,通过使用分区,可以消除很大的数据区间,从而不必考虑它们,相应地根本不用访问这些数据。但这在事务性系统中并不适用,因为这种系统本身就只是访问少量的数据。
(4) 可以把修改分布到多个单独的分区上,从而减少大容量OLTP系统上的竞争:如果一个段遭遇激烈的竞争,可以把它分为多个段,这就可以得到一个副作用:能成比例地减少竞争。
下面分别讨论使用分区可能带来的这些好处。
1.1.1 提高可用性
可用性的提高源自于每个分区的独立性。对象中一个分区的可用性(或不可用)并不意味着对象本身是不可用的。优化器知道有这种分区机制,会相应地从查询计划中去除未引用的分区。在一个大对象中如果一个分区不可用,你的查询可以消除这个分区而不予考虑,这样Oracle就能成功地处理这个查询。
为了展示这种可用性的提高,我们将建立一个散列分区表,其中有两个分区,分别在单独的表空间中。这里将创建一个EMP表,它在EMPNO列上指定了一个分区键(EMPNO就是我们的分区键)。在这种情况下,这个结构意味着:对于插入到这个表中的每一行,会对EMPNO列的值计算散列,来确定这一行将置于哪个分区(及相应的表空间)中:
ops$tkyte@ORA 10G > CREATE TABLE emp 2 ( empno int, 3 ename varchar2(20) 4 ) 5 PARTITION BY HASH (empno) 6 ( partition part_1 tablespace p1, 7 partition part_2 tablespace p2 8 ) 9 / Table created. |
接下来,我们向表中插入一些数据,然后使用带分区的扩展表名检查各个分区的内容:
ops$tkyte@ORA 10G > insert into emp select empno, ename from scott.emp 2 / 14 rows created.
ops$tkyte@ORA 10G > select * from emp partition(part_1); EMPNO ENAME ---------- -------------------- 7369 SMITH 7499 ALLEN 7654 MARTIN 7698 BLAKE 7782 CLARK 7839 KING 7876 ADAMS 7934 MILLER 8 rows selected.
ops$tkyte@ORA 10G > select * from emp partition(part_2); EMPNO ENAME ---------- -------------------- 7521 WARD 7566 JONES 7788 SCOTT 7844 TURNER 7900 JAMES 7902 FORD 6 rows selected. |
应该能注意到,数据的“摆放”有些随机。在此这是专门设计的。通过使用散列分区,我们让Oracle随机地(很可能均匀地)将数据分布到多个分区上。我们无法控制数据要分布到哪个分区上;Oracle会根据生成的散列键值来确定。后面讨论区间分区和列表分区时,我们将了解到如何控制哪个分区接收哪些数据。
下面将其中一个表空间离线(例如,模拟一种磁盘出故障的情况),使这个分区中的数据不可用:
ops$tkyte@ORA 10G > alter tablespace p1 offline; Tablespace altered. |
接下来,运行一个查询,这个查询将命中每一个分区,可以看到这个查询失败了:
ops$tkyte@ORA 10G > select * from emp; select * from emp * ERROR at line 1: ORA-00376: file 12 cannot be read at this time ORA-01110: data file 12: '/home/ora 10g /oradata/ora 10g /ORA 10G /datafile/p1.dbf' |
不过,如果查询不访问离线的表空间,这个查询就能正常工作;Oracle会消除离线分区而不予考虑。在这个特定的例子中,我使用了一个绑定变量,这只是为了展示Oracle肯定能消除离线分区:即使Oracle在查询优化时不知道会访问哪个分区,也能在运行是不考虑离线分区:
ops$tkyte@ORA 10G > variable n number ops$tkyte@ORA 10G > exec :n := 7844; PL/SQL procedure successfully completed.
ops$tkyte@ORA 10G > select * from emp where empno = :n; EMPNO ENAME ---------- -------------------- 7844 TURNER |
总之,只要优化器能从查询计划消除分区,它就会这么做。基于这一点,如果应用在查询中使用了分区键,就能提高这些应用的可用性。
分区还可以通过减少停机时间来提高可用性。例如,如果有一个100GB的表,它划分为50个2GB的分区,这样就能更快地从错误中恢复。如果某个2GB的分区遭到破坏,现在恢复的时间就只是恢复一个2GB分区所需的时间,而不是恢复一个100GB表的时间。所以从两个方面提高了可用性:
q 优化器能够消除分区,这意味着许多用户可能甚至从未注意到某些数据是不可用的。
q 出现错误时的停机时间会减少,因为恢复所需的工作量大幅减少。
1.1.2 减少管理负担
之所以能减少管理负担,这是因为与在一个大对象上执行操作相比,在小对象上执行同样的操作从本质上讲更为容易、速度更快,而且占用的资源也更少。
例如,假设数据库中有一个10GB的索引。如果需要重建这个索引,而该索引未分区,你就必须将整个10GB的索引作为一个工作单元来重建。尽管可以在线地重建索引,但是要完全重建完整的10GB索引,还是需要占用大量的资源。至少需要在某处有10GB的空闲存储空间来存放两个索引的副本,还需要一个临时事务日志表来记录重建索引期间对基表所做的修改。另一方面,如果将索引本身划分为10个1GB的分区,就可以一个接一个地单独重建各个索引分区。现在只需要原先所需空闲空间的10%。另外,各个索引的重建也更快(可能是原来的10倍),需要向新索引合并的事务修改也更少(到此为止,在线索引重建期间发生的事务修改会更少)。
另外请考虑以下情况:10GB索引的重建即将完成之前,如果出现系统或软件故障会发生什么。我们所做的全部努力都会付诸东流。如果把问题分解,将索引划分为1GB的分区,你最多只会丢掉重建工作的10%。
或者,你可能只需要重建全部聚集索引的10%,例如,只是“最新”的数据(活动数据)需要重组,而所有“较旧”的数据(相当静态)不受影响。
最后,请考虑这样一种情况:你发现表中50%的行都是“移植”行(关于串链/移植行的有关详细内容请参见第10章),可能想进行修正。建立一个分区表将有利于这个操作。为了“修正”移植行,你往往必须重建对象,在这种情况下,就是要重建一个表。如果有一个100GB的表,就需要在一个非常大的“块”(chunk)上连续地使用ALTER TABLE MOVE来执行这个操作。另一方面,如果你有25个分区,每个分区的大小为4GB,就可以一个接一个地重建各个分区。或者,如果你在空余时间做这个工作,而且有充足的资源,甚至可以在单独的会话中并行地执行ALTER TABLE MOVE语句,这就很可能会减少整个操作所需的时间。对于一个未分区对象所能做的工作,分区对象中的单个分区几乎都能做到。你甚至可能发现,移植行都集中在一个很小的分区子集中,因此,可以只重建一两个分区,而不是重建整个表。
这里有一个小例子,展示了如何对一个有多个移植行的表进行重建。BIG_TABLE1和BIG_TABLE2都是从BIG_TABLE的一个10,000,000行的实例创建的(BIG_TABLE创建脚本见“环境配置”一节)。BIG_TABLE1是一个常规的未分区表,而BIG_TABLE2是一个散列分区表,有8个分区(下一节将介绍散列分区;现在只要知道它能把数据相当均匀地分布在8个分区上就足够了):
ops$tkyte@ORA10GR1> create table big_table1 2 ( ID, OWNER, OBJECT_NAME, SUBOBJECT_NAME, 3 OBJECT_ID, DATA_OBJECT_ID, 4 OBJECT_TYPE, CREATED, LAST_DDL_TIME, 5 TIMESTAMP, STATUS, TEMPORARY, 6 GENERATED, SECONDARY ) 7 ablespace big1 8 as 9 select ID, OWNER, OBJECT_NAME, SUBOBJECT_NAME, 10 OBJECT_ID, DATA_OBJECT_ID, 11 OBJECT_TYPE, CREATED, LAST_DDL_TIME, 12 TIMESTAMP, STATUS, TEMPORARY, 13 GENERATED, SECONDARY 14 from big_table.big_table; Table created.
ops$tkyte@ORA10GR1> create table big_table2 2 ( ID, OWNER, OBJECT_NAME, SUBOBJECT_NAME, 3 OBJECT_ID, DATA_OBJECT_ID, 4 OBJECT_TYPE, CREATED, LAST_DDL_TIME, 5 TIMESTAMP, STATUS, TEMPORARY, 6 GENERATED, SECONDARY ) 7 partition by hash(id) 8 (partition part_1 tablespace big2, 9 partition part_2 tablespace big2, 10 partition part_3 tablespace big2, 11 partition part_4 tablespace big2, 12 partition part_5 tablespace big2, 13 partition part_6 tablespace big2, 14 partition part_7 tablespace big2, 15 partition part_8 tablespace big2 16 ) 17 as 18 select ID, OWNER, OBJECT_NAME, SUBOBJECT_NAME, 19 OBJECT_ID, DATA_OBJECT_ID, 20 OBJECT_TYPE, CREATED, LAST_DDL_TIME, 21 TIMESTAMP, STATUS, TEMPORARY, 22 GENERATED, SECONDARY 23 from big_table.big_table; Table created. |
现在,每个表都在自己的表空间中,所以我们可以很容易地查询数据字典,来查看每个表空间中已分配的空间和空闲空间:
ops$tkyte@ORA10GR1> select b.tablespace_name, 2 mbytes_alloc, 3 mbytes_free 4 from ( select round(sum(bytes)/1024/1024) mbytes_free, 5 tablespace_name 6 from dba_free_space 7 group by tablespace_name ) a, 8 ( select round(sum(bytes)/1024/1024) mbytes_alloc, 9 tablespace_name 10 from dba_data_files 11 group by tablespace_name ) b 12 where a.tablespace_name (+) = b.tablespace_name 13 and b.tablespace_name in ('BIG1','BIG2') 14 / TABLESPACE MBYTES_ALLOC MBYTES_FREE ---------- ------------ ----------- BIG1 1496 344 BIG2 1496 344 |
BIG1和BIG2的大小都大约是1.5GB,每个表空间都有344MB的空闲空间。我们想创建第一个表BIG_TABLE1:
ops$tkyte@ORA10GR1> alter table big_table1 move; alter table big_table1 move * ERROR at line 1: ORA-01652: unable to extend temp segment by 1024 in tablespace BIG1 |
但失败了,BIG 1表空间中要有足够的空闲空间来放下BIG_TABLE1的完整副本,同时它的原副本仍然保留,简单地说,我们需要一个很短的时间内有大约两倍的存储空间(可能多一点,也可能少移动,这取决于重建后表的大小)。现在试图对BIG_TABLE2执行同样的操作:
ops$tkyte@ORA10GR1> alter table big_table2 move; alter table big_table2 move * ERROR at line 1: ORA-14511: cannot perform operation on a partitioned object |
这说明,Oracle在告诉我们:无法对这个“表”执行MOVE操作;我们必须在表的各个分区上执行这个操作。可以逐个地移动(相应地重建和重组)各个分区:
ops$tkyte@ORA10GR1> alter table big_table2 move partition part_1; Table altered. ops$tkyte@ORA10GR1> alter table big_table2 move partition part_2; Table altered. ops$tkyte@ORA10GR1> alter table big_table2 move partition part_3; Table altered. ops$tkyte@ORA10GR1> alter table big_table2 move partition part_4; Table altered. ops$tkyte@ORA10GR1> alter table big_table2 move partition part_5; Table altered. ops$tkyte@ORA10GR1> alter table big_table2 move partition part_6; Table altered. ops$tkyte@ORA10GR1> alter table big_table2 move partition part_7; Table altered. ops$tkyte@ORA10GR1> alter table big_table2 move partition part_8; Table altered. |
对于每个移动,只需要有足够的空闲空间来存放原来数据的1/8的副本!因此,假设有先前同样多的空闲空间,这些命令就能成功。我们需要的临时资源将显著减少。不仅如此,如果在移动到PART_4后但在PART_5完成“移动”之前系统失败了(例如,掉电),我们并不会丢失以前所做的所有工作,这与执行一个MOVE语句的情况不同。前4个分区仍是“移动”后的状态,等系统恢复时,我们可以从分区PART_5继续处理。
有人看到这里可能会说:“哇,8条语句,要输入这么多语句!”不错,如果有数百个分区(或者更多),这确实有些不切实际。幸运的是,可以很容易地编写一个脚本来解决这个问题,前面的语句则变成以下脚本:
ops$tkyte@ORA10GR1> begin 2 for x in ( select partition_name 3 from user_tab_partitions 4 where table_name = 'BIG_TABLE2' ) 5 loop 6 execute immediate 7 'alter table big_table2 move partition ' || 8 x.partition_name; 9 end loop; 10 end; 11 / PL/SQL procedure successfully completed. |
你需要的所有信息都能在Oracle数据字典中找到,而且大多数实现了分区的站点都有一系列存储过程,可用于简化大量分区的管理。另外,许多GUI工具(如Enterprise Manager)也有一种内置的功能,可以执行这种操作而无需你键入各条命令。
关于分区和管理,还有一个因素需要考虑,这就是在维护数据仓库和归档中使用数据“滑动窗口”。在许多情况下,需要保证数据在最后N个时间单位内一直在线。例如,假设需要保证最后12个月或最后5年的数据在线。如果没有分区,这通常是一个大规模的INSERT,其后是一个大规模的DELETE。为此有相对多的DML,并且会生成大量的redo和undo。如果进行了分区,则只需做下面的工作:
(1) 用新的月(或年,或者是其他)数据加载一个单独的表。
(2) 对这个表充分建立索引(这一步甚至可以在另一个实例中完成,然后传送到这个数据库中)。
(3) 将这个新加载(并建立了索引)的表附加到分区表的最后,这里使用一个快速DDL命令:ALTER TABLE EXCHANGE PARTITION。
(4) 从分区表另一端将最旧的分区去掉。
这样一来,现在就可以很容易地支持包含时间敏感信息的非常大的对象。就数据很容易地从分区表中去除,如果不再需要它,可以简单地将其删除;或者也可以归档到某个地方。新数据可以加载到一个单独的表中,这样在加载、建索引等工作完成之前就不会影响分区表。在这一章的后面,我们还会看到关于滑动窗口的一个完整的例子。
简单地说,利用分区,原先让人畏惧的操作(有时甚至是不可行的操作)会变得像在小数据库中一样容易。
1.1.3 改善语句性能
分区最后一个总的(潜在)好处体现在改进语句(SELECT、INSERT、UPDATE、DELETE、MERGE)的性能方面。我们来看两类语句,一种是修改信息的语句,另一种是只读取信息的语句,并讨论在这种情况下可以从分区得到哪些好处。
1. 并行DML
修改数据库中数据的语句有可能会执行并行DML(parallel DML,PDML)。采用PDML时,Oracle使用多个线程或进程来执行INSERT、UPDATE或DELETE, 而不是执行一个串行进程。在一个有充足I/O带宽的多CPU主机上,对于大规模的DML操作,速度的提升可能相当显著。在Oracle9i以前的版本中,PDML要求必须分区。如果你的表没有分区,在先前的版本中就不能并行地执行这些操作。如果表确实已经分区,Oracle会根据对象所有的物理分区数为对象指定一个最大并行度。从很大程度上讲,在Oracle9i及以后版本中这个限制已经放松,只有两个突出的例外;如果希望在一个表上执行PDML,而且这个表的一个LOB列上有一个位图索引,要并行执行操作就必须对这个表分区;另外并行度就限制为分区数。不过,总的说来,使用PDML并不一定要求进行分区。
注意 我们会在第14章更详细地讨论并行操作。
2. 查询性能
在只读查询(SELECT语句)的性能方面,分区对两类特殊操作起作用:
q 分区消除(partition elimination):处理查询时不考虑某些数据分区。我们已经看到了一个分区消除的例子。
q 并行操作(parallel operation):并行全表扫描和并行索引区间扫描就是这种操作的例子。
不过,由此得到的好处很多程度上取决于你使用何种类型的系统。
l OLTP系统
在OLTP系统中,不应该把分区当作一种大幅改善查询性能的方法。实际上,在一个传统的OLTP系统中,你必须很小心地应用分区,提防着不要对运行时性能产生负面作用。在传统的OLTP系统中,大多数查询很可能几乎立即返回,而且大多数数据库获取可能都通过一个很小的索引区间扫描来完成。因此,以上所列分区性能方面可能 的主要优点在OLTP系统中表现不出来。分区消除只在大对象全面扫描时才有用,因为通过分区消除,你可以避免对对象的很大部分做全面扫描。不过,在一个OLTP环境中,本来就不是对大对象全面扫描(如果真是如此,则说明肯定存在严重的设计缺陷)。即使对索引进行了分区,就算是真的能在速度上有所提高,通过扫描较小索引所得到的性能提升也是微乎其微的。如果某些查询使用了一个索引,而且它们根本无法消除任何分区,你可能会发现,完成分区之后查询实际上运行得反而更慢了,因为你现在要扫描5、10或20个更小的索引,而不是一个较大的索引。稍后讨论各种可用的分区索引时还会更详细地讨论这个内容。
尽管如此,有分区的OLTP系统确实也有可能得到效率提示。例如,可以用分区来减少竞争,从而提高并发度。可以利用分区将一个表的修改分布到多个物理分区上。并不是只有一个表段和一个索引段,而是可以有10个表分区和20个索引分区。这就像有20个表而不是1个表,相应地,修改期间就能减少对这个共享资源的竞争。
至于并行操作(将在下一章更详细地讨论),你可能不希望在一个OLTP系统中执行并行查询。你会慎用并行操作,而是交由DBA来完成重建、创建索引、收集统计信息等工作。事实上在一个OLTP系统中,查询已经有以下特点:即索引访问相当快,因此,分区不会让索引访问的速度有太大的提高(甚至根本没有任何提高)。这并不是说要绝对避免在OLTP系统中使用分区;而只是说不要指望通过分区来提供大幅的性能提升。尽管有效情况下分区能够改善查询的性能,但是这些情况在大多数OLTP应用中并不成立。不过在OLTP系统中,你还是可以得到另外两个可能的好处:减轻管理负担以及有更高的可用性。
l 数据仓库系统
在一个数据仓库/决策支持系统中,分区不仅是一个很强大的管理工具,还可以加快处理的速度。例如,你可能有一个大表,需要在其中执行一个即席查询。你总是按销售定额(sales quarter)执行即席查询,因为每个销售定额包含数十万条记录,而你有数百万条在线记录。因此,你想查询整个数据集中相当小的一部分,但是基于销售定额来索引不太可行。这个索引会指向数十万条记录,以这种方式执行索引区间扫描会很糟糕(有关的更多详细内容请参见第11章)。处理许多查询时都要求执行一个全表扫描,但是最后却发现,一方面必须扫描数百万条记录,但另一方面其中大多数记录并不适用于我们的查询。如果使用一种明智的分区机制,就可以按销售定额来聚集数据,这样在查询某个给定销售定额的数据时,就可以只对这个销售定额的数据进行全面扫描。这在所有可能的解决方案中是最佳的选择。
另外,在一个数据仓库/决策支持环境中,会频繁地使用并行查询。因此,诸如并行索引区间扫描或并行快速全面索引扫描等操作不仅很有意义,而且对我们很有好处。我们希望充分地使用所有可用的资源,并行查询就提供了这样的一种途径。因此,在数据仓库环境中,分区就意味着很有可能会加快处理速度。
1.2 表分区机制
目前Oracle中有4种对表分区的方法:
q 区间分区:可以指定应当存储在一起的数据区间。例如,时间戳在Jan-2005内的所有记录都存储在分区1中,时间戳在Feb-2005内的所有记录都存储在分区2中,依此类推。这可能是Oracle中最常用的分区机制。
q 散列分区:我们在这一章一个例子中就已经看到了散列分区。这是指在一个列(或多个列)上应用一个散列函数,行会按这个散列值放在某个分区中。
q 列表分区:指定一个离散值集,来确定应当存储在一起的数据。例如,可以指定STATUS列值在(’A’,’M’,’Z’)中的行放在分区1中,STATUS值在(‘D’,’P’,’Q’)中的行放在分区2中,依此类推。
q 组合分区:这是区间分区和散列分区的一种组合,或者是区间分区与列表分区的组合。通过组合分区,你可以先对某些数据应用区间分区,再在区间中根据散列或列表来选择最后的分区。
在下面几节中,我们将介绍各类分区的好处,以及它们之间有什么差别。我们还会说明什么时候应当对不同类型的应用采用何种机制。这一节并不打算对分区语法和所有可用选项提供一个全面的介绍。相反,我们要靠例子说话,这些例子很简单,也很直观,专门设计为帮助你了解分区,使你对分区如何工作以及如何设计不同类型的分区有一个简要认识。
注意 要想全面地了解分区语法的所有细节,建议你阅读Oracle SQL Reference Guide或Oracle Administrator’s Guide。另外,Oracle Data Warehousing Guide也是一个不错的信息源,其中对分区选项做了很好的说明,对于每个计划实现分区的人来说,这是必读的文档。
1.2.1 区间分区
我们要介绍的第一种类型是区间分区表(range partitioned table)。下面的CREATE TABLE语句创建了一个使用RANGE_KEY_COLUMN列的区间分区表。RANGE_KEY_COLUMN值严格小于01-JAN-2005的所有数据要放在分区PART_1中,RANGE_KEY_COLUMN值严格小于01-JAN-2006的所有数据则放在分区PART_2中。不满足这两个条件的所有数据(例如,RANGE_KEY_COLUMN值为01-JAN-2007的行)将不能插入,因为它们无法映射到任何分区:
ops$tkyte@ORA10GR1> CREATE TABLE range_example 2 ( range_key_column date , 3 data varchar2(20) 4 ) 5 PARTITION BY RANGE (range_key_column) 6 ( PARTITION part_1 VALUES LESS THAN 7 (to_date(' 01/01/2005 ','dd/mm/yyyy')), 8 PARTITION part_2 VALUES LESS THAN 9 (to_date(' 01/01/2006 ','dd/mm/yyyy')) 10 ) 11 / Table created. |
注意 我们在CREATE TABLE语句中使用了日期格式DD/MM/YYYY,使之做到“国际化”。如果使用格式DD-MON-YYYY,倘若在你的系统上一月份的缩写不是Jan,这个CREATE TABLE语句就会失败,而得到一个ORA-01843:not a valid month错误。NLS_LANGUAGE设置会对此产生影响。不过,我在正文和插入语句中使用了3字符的月份缩写,以避免月和日产生歧义,否则有时很难区分哪个分量是日,哪个分量是月。
图13-1显示了Oracle会检查RANGE_KEY_COLUMN的值,并根据这个值,将数据插入到两个分区之一。
图13-1 区间分区插入示例
为了展示分区区间是严格小于某个值而不是小于或等于某个值,这里插入的行是特别选择的。我们首先插入值15-DEC-2004,它肯定要放在分区PART_1中。我们还插入了日期/时间为01-JAN-2005之前一秒(31-dec-2004 23:59:59)的行,这一行也会放到分区PART_1中,因为它小于01-JAN-2005。不过,插入的下一行日期/时间不是严格小于分区区间边界。最后一行显然应该放在分区PART_2中,因为它小于PART_2的分区区间边界。
可以从各个分区分别执行SELECT语句,来确认确实如此:
ops$tkyte@ORA 10G > select to_char(range_key_column,'dd-mon-yyyy hh24:mi:ss') 2 from range_example partition (part_1); TO_CHAR(RANGE_KEY_CO -------------------- 15-dec-2004 00:00:00 31-dec-2004 23:59:59
ops$tkyte@ORA 10G > select to_char(range_key_column,'dd-mon-yyyy hh24:mi:ss') 2 from range_example partition (part_2); TO_CHAR(RANGE_KEY_CO -------------------- 01-jan-2005 00:00:00 15-dec-2005 00:00:00 |
你可能想知道,如果插入的日期超出上界会怎么样呢?答案是Oracle会产生一个错误:
ops$tkyte@ORA10GR1> insert into range_example 2 ( range_key_column, data ) 3 values 4 ( to_date( '15/12/2007 00:00:00', 5 'dd/mm/yyyy hh24:mi:ss' ), 6 'application data...' ); insert into range_example * ERROR at line 1: ORA-14400: inserted partition key does not map to any partition |
假设你想像刚才一样,将2005年和2006年的日期分别聚集到各自的分区,但是另外你还希望将所有其他日期都归入第三个分区。利用区间分区,这可以使用MAXVALUE子句做到这一点,如下所示: