Kevin's World

God helps ones who help themselves

Monthly Archives: 10月 2010

经典的数据滑动窗口

许多数据仓库都实现了一种滑动窗口(sliding window)方法来管理数 据,也就是说,删除表中最旧的分区,并为新加载的数据增加一个新分区。在过去(Oracle8i及以前的版本),数据仓库系统都避免使用全局索引,对此有 一个很好的原因:全局索引缺乏可用性。大多数分区操作(如删除一个旧分区)都会使全局索引无效,除非重建全局索引,否则无法使用,这会严重地影响可用性, 以前往往都是如此。     然而从Oracle9i开始,对于分区维护又增加了另一个选项:可以在分区操作期间使用UPDATE GLOBAL INDEXES子句来维护全局索引。这意味着,在你删除一个分区、分解一个分区以及在分区上执行任何必要的操作时,Oracle会对全局索引执行必要的修 改,保证它是最新的。   当使用此选项时,由于我们要在全局索引结构上执行INSERT和DELETE操作的相应逻辑操作。删除一个分区时,必须删除可能指向该分区的所有全局索引 条目。执行表与分区的交换时,必须删除指向原数据的所有全局索引条目,再插入指向刚滑入的数据的新条目。所以ALTER命令执行的工作量会大幅增加。

在以下例子中我们将处理每年的数据,并加载2009和2010财政年度的数据。这个表按TIMESTAMP列分区,并创建了两个索引,一个是ID列上的局部分区索引,另一个是TIMESTAMP列上的全局索引(这里为分区):
CREATE TABLE partitioned
( timestamp date,
id int
)
PARTITION BY RANGE (timestamp)
(
PARTITION fy_2008 VALUES LESS THAN
(to_date(’01-jan-2009′,’dd-mon-yyyy’)) ,
PARTITION fy_2009 VALUES LESS THAN
(to_date(’01-jan-2010′,’dd-mon-yyyy’))
);
insert into partitioned partition(fy_2008)
select to_date(’31-dec-2008′,’dd-mon-yyyy’)-mod(rownum,360), object_id
from all_objects;
insert into partitioned partition(fy_2009)
select to_date(’31-dec-2009′,’dd-mon-yyyy’)-mod(rownum,360), object_id
from all_objects;
create index partitioned_idx_local
on partitioned(id)
LOCAL;
create index partitioned_idx_global
on partitioned(timestamp)
GLOBAL;
这样就建立我们需要的demo表。假设现在正处于年末,我们想做下面的工作:
(1) 删除最旧的财政年度数据。我们不想永远地丢掉这个数据,而只是希望它老化,并将其归档。
(2) 增加最新的财政年度数据。加载、转换、建索引等工作需要一定的时间。我们想做这个工作,但是希望尽可能不影响当前数据的可用性。
第一步是为2008财政年度建立一个看上去就像分区表的空表。我们将使用这个表与分区表中的FY_2008分区交换,将这个分区转变成一个表,相应地是分区表中的分区为空。这样做的效果就是分区表中最旧的数据(实际上)会在交换之后被删除:
create table fy_2008 ( timestamp date, id int );
create index fy_2008_idx on fy_2008(id);
然后对要加载的新财政年度数据做同样的工作。我们将创建并加载一个表,其结构就像是现在的分区表(但是它本身并不是分区表):
create table fy_2010 ( timestamp date, id int );
insert into fy_2010
select to_date(’31-dec-2010′,’dd-mon-yyyy’)-mod(rownum,360), object_id
from all_objects;
create index fy_2010_idx on fy_2010(id) nologging;
我们将当前的满分区变成一个空分区,并创建了一个包含FY_2008数据的“慢”表。而且,我们完成了使用FY_2010数据的所有必要工作,这包括验证数据、进行转换以及准备这些数据所需完成的所有复杂任务。现在可以使用一个交换分区来更新数据:
alter table partitioned
exchange partition fy_2008
with table fy_2008
including indexes
without validation
UPDATE GLOBAL INDEXES;
alter table partitioned
drop partition fy_2008
UPDATE GLOBAL INDEXES;

要把旧数据“老化”,所要做的仅此而已。我们将分区变成一个满表,而将空表变成一个分区。如果不加UPDATE GLOBAL INDEXES选项,这是一个简单的数据字典更新,瞬时就会完成,而不会发生大量的I/O。接下来,我们想“滑入”(即增加)新数据:
alter table partitioned
add partition fy_2010
values less than (to_date(’01-jan-2011′,’dd-mon-yyyy’));
alter table partitioned
exchange partition fy_2010
with table fy_2010
including indexes
without validation
UPDATE GLOBAL INDEXES;
同样,这个工作也会立即完成;这是通过简单的数据字典更新实现的。增加空分区几乎不需要多少时间来处理。然后,将新创建的空分区与满表交换(满表与空分区交换),如果不加UPDATE GLOBAL INDEXES选项,这个操作会很快完成。
如果以上操作不加UPDATE GLOBAL INDEXES选项,则当完成新分区的exchange过程后需要重建全局索引;在重建索引时有可能需要相当长的时间,并且会占用大量资源。如果查询依赖于这些索引,在此期间它们的运行时查询性能就会受到负面影响,可能根本不会运行,也可能运行时得不到索引提供的好处,所有数据都必须扫描。

而 增加UPDATE GLOBAL INDEXES选项是一种允许用资源耗费的增加来换取可用性的选项。如果需要提供连续的可用性,这就是一个必要的选择。倘若使用UPDATE GLOBAL INDEXES,就不能绕过undo或redo生成。在使用这个特性之前,需要检查确定组件大小所用的规则,从而确保这种方法在你的系统上确实能正常工 作。