再上一篇:12.8 ROWID/UROWID类型
上一篇:12.9小结
主页
下一篇:13.2表分区机制
再下一篇:13.3索引分区
文章列表

13.1分区概述

Oracle 9i 10g编程艺术:深入数据库体系结构

分区有利于管理非常大的表和索引,它使用了一种“分而治之”的逻辑。分区引入了一种分区键
(partition key)的概念,分区键用于根据某个区间值(或范围值)、特定值列表或散列函数值执行数据 的聚集。如果让我按某种顺序列出分区的好处,这些好处如下:
(1) 提高数据的可用性:这个特点对任何类型的系统都适用,而不论系统本质上是OLTP 还是仓库系统。
(2) 由于从数据库中去除了大段,相应地减轻了管理的负担。在一个100GB的表上执 行管理操作时(如重组来删除移植的行,或者在“净化”旧信息后回收表左边的“空白” 空间),与在各个 10GB的表分区上执行10次同样的操作相比,前者负担要大得多。另外 , 通过使用分区,可以让净化例程根本不留下空白空间,这就完全消除了重组的必要!
(3) 改善某些查询的性能:主要在大型仓库环境中有这个好处,通过使用分区,可以 消除很大的数据区间,从而不必考虑它们,相应地根本不用访问这些数据。但这在事务 性系统中并不适用,因为这种系统本身就只是访问少量的数据。
(4) 可以把修改分布到多个单独的分区上,从而减少大容量OLTP系统上的竞争:如 果一个段遭遇激烈的竞争,可以把它分为多个段,这就可以得到一个副作用:能成比例 地减少竞争。
下面分别讨论使用分区可能带来的这些好处。

13.1.1提高可用性

可用性的提高源自于每个分区的独立性。对象中一个分区的可用性(或不可用)并不意味着对象本身 是不可用的。优化器知道有这种分区机制,会相应地从查询计划中去除未引用的分区。在一个大对象中如 果一个分区不可用,你的查询可以消除这个分区而不予考虑,这样Oracle就能成功地处理这个查询。
为了展示这种可用性的提高,我们将建立一个散列分区表,其中有两个分区,分别在单独的表空间中 。 这里将创建一个EMP表,它在EMPNO 列上指定了一个分区键(EMPNO就是我们的分区键)。在这种情况下, 这个结构意味着:对于插入到这个表中的每一行,会对EMPNO 列的值计算散列,来确定这一行将置于哪个 分区(及相应的表空间)中:
ops$tkyte@ORA10G> 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@ORA10G> insert into emp select empno, ename from scott.emp
2 /
14 rows created.

ops$tkyte@ORA10G> 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@ORA10G> 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@ORA10G> alter tablespace p1 offline;
Tablespace altered.

接下来,运行一个查询,这个查询将命中每一个分区,可以看到这个查询失败了:
ops$tkyte@ORA10G> 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/ora10g/oradata/ora10g/ORA10G/datafile/p1.dbf'

不过,如果查询不访问离线的表空间,这个查询就能正常工作;Oracle会消除离线分区而不予考虑。
在这个特定的例子中,我使用了一个绑定变量,这只是为了展示Oracle肯定能消除离线分区:即使Oracle
在查询优化时不知道会访问哪个分区,也能在运行是不考虑离线分区:
ops$tkyte@ORA10G> variable n number
ops$tkyte@ORA10G> exec :n := 7844;
PL/SQL procedure successfully completed.
ops$tkyte@ORA10G> select * from emp where empno = :n; EMPNO ENAME
---------- --------------------
7844 TURNER

总之,只要优化器能从查询计划消除分区,它就会这么做。基于这一点,如果应用在查询中使用了分
区键,就能提高这些应用的可用性。
分区还可以通过减少停机时间来提高可用性。例如,如果有一个100GB的表,它划分为50个2GB的 分区,这样就能更快地从错误中恢复。如果某个2GB的分区遭到破坏,现在恢复的时间就只是恢复一个2GB 分区所需的时间,而不是恢复一个100GB表的时间。所以从两个方面提高了可用性:
优化器能够消除分区,这意味着许多用户可能甚至从未注意到某些数据是不可用的。 出现错误时的停机时间会减少,因为恢复所需的工作量大幅减少。

13.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) 从分区表另一端将最旧的分区去掉。
这 样一来,现在就可以很容易地支持包含时间敏感信息的非常大的对象。就数据很容易地从分区表 中去除,如果不再需要它,可以简单地将其删除;或者也可以归档到 某个地方。新数据可以加载到一个单 独的表中,这样在加载、建索引等工作完成之前就不会影响分区表。在这一章的后面,我们还会看到关于 滑动窗口的一个完整的 例子。
简单地说,利用分区,原先让人畏惧的操作(有时甚至是不可行的操作)会变得像在小数据库中一样 容易。

13.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语句)的性能方面,分区对两类特殊操作起作用:
分区消除(partition elimination):处理查询时不考虑某些数据分区。我们已经看到了 一个分区消除的例子。
并行操作(parallel operation):并行全表扫描和并行索引区间扫描就是这种操作的例子 。 不过,由此得到的好处很多程度上取决于你使用何种类型的系统。
OLTP系统
在OLTP系统中,不应该把分区当作一种大幅改善查询性能的方法。实际上,在一个传统的OLTP系统 中,你必须很小心地应用分区,提防着不要对运行时性能产生负面作用。在传统的OLTP系统中,大多数查 询很可能几乎立即返回,而且大多数数据库获取可能都通过一个很小的索引区间扫描来完成。因此,以上 所列分区性能方面可能 的主要优点在OLTP系统中表现不出来。分区消除只在大对象全面扫描时才有用, 因为通过分区消除,你可以避免对对象的很大部分做全面扫描。不过,在一个OLTP环 境中,本来就不是 对大对象全面扫描(如果真是如此,则说明肯定存在严重的设计缺陷)。即使对索引进行了分区,就算是真 的能在速度上有所提高,通过扫描较小 索引所得到的性能提升也是微乎其微的。如果某些查询使用了一个 索引,而且它们根本无法消除任何分区,你可能会发现,完成分区之后查询实际上运行得反而更慢 了,因 为你现在要扫描5、10或20个更小的索引,而不是一个较大的索引。稍后讨论各种可用的分区索引时还会 更详细地讨论这个内容。
尽管如此,有分区的OLTP系统确实也有可能得到效率提示。例如,可以用分区来减少竞争,从而提 高并发度。可以利用分区将一个表的修改分布到多个物理分区上。并不是只有一个表段和一个索引段,而 是可以有10个表分区和20个索引分区。这就像有20个表而不是1个表,相应地,修改期间就能减少对这 个共享资源的竞争。
至于并行操作(将在下一章更详细地讨论),你可能不希望在一个 OLTP系统中执行并行查询。你会慎 用并行操作,而是交由DBA来完成重建、创建索引、收集统计信息等工作。事实上在一个 OLTP系统中,查 询已经有以下特点:即索引访问相当快,因此,分区不会让索引访问的速度有太大的提高(甚至根本没有 任何提高)。这并不是说要绝对避免在OLTP系统中使用分区;而只是说不要指望通过分区来提供大幅的性 能提升。尽管有效情况下分区能够改善查询的性能,但是这些情况在大多数OLTP应用中并不成立。不过在 OLTP系统中,你还是可以得到另外两个可能的好处:减轻管理负担以及有更高的可用性。
数据仓库系统 在一个数据仓库/决策支持系统中,分区不仅是一个很强大的管理工具,还可以加快处理的速度。例
如,你可能有一个大表,需要在其中执行一个即席查询。你总是按销售定额(sales quarter)执行即席查
询,因为每个销售定额包含数十万条记录,而你有数百万条在线记录。因此,你想查询整个数据集中相当 小的一部分,但是基于销售定额来索引不太可行。这个索引会指向数十万条记录,以这种方式执行索引区 间扫描会很糟糕(有关的更多详细内容请参见第11章)。 处理许多查询时都要求执行一个全表扫描,但是 最后却发现,一方面必须扫描数百万条记录,但另一方面其中大多数记录并不适用于我们的查询。如果使 用一种明智 的分区机制,就可以按销售定额来聚集数据,这样在查询某个给定销售定额的数据时,就可以 只对这个销售定额的数据进行全面扫描。这在所有可能的解决方案中是 最佳的选择。
另外,在一个数据仓库/决策支持环境中,会频繁地使用并行查询。因此,诸如并行索引区间扫描或 并行快速全面索引扫描等操作不仅很有意义,而且对我们很有好处。我们希望充分地使用所有可用的资源, 并行查询就提供了这样的一种途径。因此,在数据仓库环境中,分区就意味着很有可能会加快处理速度。