在 DB2 中采用时态表,第 1 部分: 系统周期表的基础迁移场景

IBM® DB2® V10 中的时态特性为基于时间的数据管理提供了丰富的功能。DB2 中的时态表可以记录其数据更改的完成历史,这样您就可以及时返回并查询数据的任一过去状态。时态表也支持您跟踪和管理数据的业务有效性,指出信息在现实世界中何时是有效的。“在 DB2 中采用时态表” 系列文章介绍如何将现有表和时态解决方案迁移到 DB2 中的时态表。第 1 部分介绍在 DB2 中采用系统周期时态表的基础场景。

Matthias Nicola, 高级技术人员, IBM Silicon Valley Lab

作者照片:Matthias NicolaMatthias Nicola 是位于加州圣何塞的 IBM 硅谷实验室的一名高级技术人员。他主要研究 DB2 性能与基准测试、XML、时态数据管理、数据库内分析及其他新兴技术。Matthias 与客户和业务合作伙伴紧密协作,协助他们设计、优化和实现 DB2 解决方案。此前,Matthias 曾在 Informix Software 公司负责数据仓库性能方面的工作。他从德国的亚琛科技大学获得了计算机科学博士学位。



2013 年 4 月 16 日

简单介绍使用 DB2 进行时态表管理

DB2 支持基于时间的数据管理,允许插入、更新、删除和查询过去、现在以及未来的数据,同时保存 “您所了解的” 和 “何时了解” 的完整历史记录。

DB2 中的时态表

DB2 支持 3 类时态表:

  • 系统周期时态表:DB2 以透明的方式不断地保留已更新或已删除行的历史记录。使用 SQL 语言标准的新结构,用户可以及时返回并查询过去任一选择点的数据库。这是以内部分配的系统时间戳为基础,DB2 使用该时间戳来管理系统时间(也称为事务时间)。
  • 应用程序周期时态表:应用程序提供日期或时间戳以描述其数据的业务有效性。新的 SQL 结构支持应用程序插入、查询、更新和删除过去、现在以及未来的数据。DB2 自动应用约束条件和行分割 (row-split) 来正确地维护应用程序提供的业务时间(也称为有效时间)。
  • 双时态表:管理系统时间业务时间。双时态表结合了系统周期和应用程序周期时态表的所有功能。这种结合使得应用程序能够管理其数据的业务有效性,同时 DB2 可保存所有更新和删除的完整历史记录。

本系列文章假设您熟悉 DB2 中的时态表。文章 “时间问题:DB2 中的时态数据管理” 提供了有关这些主题的介绍。“DB2 最佳实践:使用 DB2 进行时态数据管理” 提供了其他使用指南。例如,时态表范围分区,历史数据特权、历史意识模式设计以及最佳实践模块中的其他建议。

系统周期时态表:基础

当使用系统时间周期创建一个表时,就需要命令 DB2 自动捕获该表的更改并在历史记录表(一个单独的表,与您的基表(也称为当前表)具有相同结构)中保存 “旧” 行。

从头开始定义一个新系统周期时态表涉及以下步骤:

  1. 为当前数据创建基表

    清单 1. 基表的定义
    CREATE TABLE employees (
        empid        BIGINT NOT NULL PRIMARY KEY,
        name         VARCHAR(20),
        deptid       INTEGER,
        salary       DECIMAL(7,2),
        system_begin TIMESTAMP(12) NOT NULL GENERATED ALWAYS AS ROW BEGIN, 
        system_end   TIMESTAMP(12) NOT NULL GENERATED ALWAYS AS ROW END,
        trans_start  TIMESTAMP(12) GENERATED ALWAYS AS TRANSACTION START ID,
        PERIOD SYSTEM_TIME (system_begin, system_end) );
  2. 创建与基表结构相同的历史记录表

    清单 2. 历史表的定义
    CREATE TABLE employees_hist (
        empid        BIGINT NOT NULL,
        name         VARCHAR(20),
        deptid       INTEGER,
        salary       DECIMAL(7,2),
        system_begin TIMESTAMP(12) NOT NULL, 
        system_end   TIMESTAMP(12) NOT NULL,
        trans_start  TIMESTAMP(12) );

    另外,可使用 CREATE TABLE 语句中的 LIKE 子句创建历史记录表,这可确保历史记录表的列与基表的列相同:

    CREATE TABLE employees_hist LIKE employees;

  3. 启用版本控制并指出使用哪个历史记录表。

    清单 3. 支持版本控制
    ALTER TABLE employees 
        ADD VERSIONING USE HISTORY TABLE employees_hist;

    一旦链接建立,将自动创建历史行并支持时间旅行 (time-travel) 查询。

查询一个系统周期时态表

您可以在系统周期时态表中运行常规 SQL 查询,运行方法与在其他表中一样。例如,下列查询返回员工 1000 的当前数据:

SELECT empid, name, deptid, salary, system_begin, system_end
FROM employees
WHERE empid = 1000 ;

此外,可使用新的 FOR SYSTEM_TIME 子句查询系统周期时态表,从而检索数据的过去状态。例如,下一个查询返回员工 500 截至 2011 年 2 月 1 日(午夜)在数据库中的记录:

SELECT empid, name, deptid, salary, system_begin, system_end
FROM employees FOR SYSTEM_TIME AS OF '2011-02-01'
WHERE empid = 500 ;

该查询返回的行可以是当前行,也可以是历史行。DB2 以透明方式检查当前表和历史记录表,并返回正确结果。FOR SYSTEM_TIME AS OF 子句中的值可以是一个日期、时间戳、表达式、参数标记或一个主机变量。

注意:在 DB2 for z/OS® 中,文本值 2011-02-01 必须写成 TIMESTAMP '2011-02-01',这样该值才能正确地转换成目标数据类型。

要检索员工 4711 的所有数据(当前行和历史行),使用该 SQL 语句:

SELECT empid, name, deptid, salary, system_begin, system_end
FROM employees FOR SYSTEM_TIME FROM '0001-01-01' TO '9999-12-30'
WHERE empid = 4711 ;

有时候,您可能希望检索一行的前一个版本,即当前版本之前的最后一个版本。下列查询返回员工 1212 的该行的前一个版本:

SELECT prev.* 
FROM employees curr,
     employees FOR SYSTEM_TIME BETWEEN '0001-01-01' AND CURRENT_TIMESTAMP prev
WHERE curr.empid = 1212
    AND prev.empid = 1212
    AND curr.system_begin = prev.system_end;

或者,通过直接查询历史记录表也可以得到同样的结果:

SELECT * 
FROM employees_hist
WHERE empid = 1212
    AND system_end = (SELECT MAX(system_end)
                     FROM employees_hist
                     WHERE empid = 1212) ;

典型迁移场景

在 DB2 中采用系统周期时态表比较容易。将一个现有解决方案迁移到系统周期时态表的具体步骤取决于现有表和数据的特征。比如:

  • 是否记录了历史行,例如,使用触发器
  • 是使用单一表来保存当前数据和历史数据,还是使用两个表
  • 是否为一个行的每个版本都存储了一个或两个时间戳值
  • 为现有时态解决方案选择的周期模型(inclusive-exclusive 与 inclusive-inclusive)

根据这些属性,本系列第 1 部分和第 2 部分探讨了 5 种现有解决方案,介绍了这些解决方案到 DB2 系统周期表的迁移。

表 1. 本系列第 1 部分和第 2 部分中讨论的迁移场景概述
现有解决方案 记录历史?现有历史记录 在一个单独表中?时间戳列号 用于版本控制?您的周期 模型?文章
场景 0N/A第 1 部分
场景 1两个Inclusive-exclusive第 1 部分
场景 2两个Inclusive-exclusive第 2 部分
场景 3一个Inclusive-exclusive第 2 部分
场景 4两个Inclusive-inclusive第 2 部分

场景 0 是最简单的,首先讨论。

对于场景 1-4,这里有迁移到 DB2 系统周期时态表的通用步骤:

  1. 确保迁移期间没有表访问(读或写)。
  2. 禁用任何自定义触发器或在更新/删除操作中负责创建历史记录行的应用程序代码。
  3. 匹配表模式:根据需要创建一个历史记录表和一些附加时间戳列。确保当前表和历史记录表具有相同的列以及相同的名称、顺序、为空性以及数据类型。技巧:使用 CREATE TABLE 语句和一个 LIKE 子句,如前所示。
  4. 将现有时间戳列的数据类型更改为 TIMESTAMP(12)
  5. 如果现有解决方案为当前数据和历史数据使用一个表,那么将现有历史记录行移到历史记录表中。
  6. 调整应用程序:INSERT/UPDATE/DELETE 语句通常需要较少或不需要更改。根据现有周期和时间戳列数量,可能需要对现有查询进行微小改动。推荐进行其他一些更改以便实现易用性和更好的性能。

场景 0:启用非时态表的版本控制

该场景介绍了对于还未具有任何现有历史记录与之关联的常规表如何开始记录历史以及启用时间旅行查询。我们来看看 清单 4 中的示例表 employees_s0。

清单 4. 现有表定义
CREATE TABLE employees_s0 (
    empid        BIGINT NOT NULL PRIMARY KEY,
    name         VARCHAR(20),
    deptid       INTEGER,
    salary       DECIMAL(7,2) );

使用 清单 5 中的 3 个语句可以轻松地将该表变成一个系统周期时态表。ALTER TABLE 语句添加 3 个强制时间戳列并声明 SYSTEM_TIME 周期。随后的语句分别创建相应历史表并启用版本控制。完成这些步骤之后,就没必要重组该表。

新时间戳列定义为 IMPLICITLY HIDDEN,这是可选择的,能确保它们不会出现在 SELECT * 查询结果集中。因此,现有应用程序将看到与迁移之前完全相同的查询结果。不需要更改现有查询或插入、更新和删除语句。

清单 5. 将表 employees_s0 转换成一个 STT
ALTER TABLE employees_s0
 ADD COLUMN sys_start TIMESTAMP(12) NOT NULL GENERATED AS ROW BEGIN IMPLICITLY HIDDEN
 ADD COLUMN sys_end   TIMESTAMP(12) NOT NULL GENERATED AS ROW END IMPLICITLY HIDDEN
 ADD COLUMN trans_id  TIMESTAMP(12) GENERATED AS TRANSACTION START ID IMPLICITLY HIDDEN
 ADD PERIOD SYSTEM_TIME (sys_start, sys_end);
                
CREATE TABLE employees_s0_hist LIKE employees_s0;
                
ALTER TABLE employees_s0 ADD VERSIONING USE HISTORY TABLE employees_s0_hist;

当向现有表添加一个时间戳列时(如 清单 5 所示),在 sys_end 列中的所有行获得的值为 9999-12-30,这表明所有行是当前行。然而,DB2 不知道这些行最初是何时插入的,以及它们的 sys_start 值应该是什么。因此,所有现有行最初获得的 sys_start 值为 0001-01-01,即为 0001 年 1 月 1 日。

如果您喜欢使用当前时间作为所有现有行的系统启动时间,则有两个选择可以实现这一点:

  • 首先用所需的默认值添加 sys_start 列,然后发出第二个 ALTER TABLE 语句将列设置为 GENERATED AS ROW BEGIN,如 清单 6 所示。
  • 可以使用清单 5 中的步骤,但在启用版本控制之前 必须导出并重新加载所有行。如 清单 7 所示。
清单 6. 使用 sys_start 自定义值将表 employees_s0 转换成一个 STT
ALTER TABLE employees_s0
 ADD COLUMN sys_start TIMESTAMP(12) NOT NULL DEFAULT CURRENT_TIMESTAMP IMPLICITLY HIDDEN
 ADD COLUMN sys_end   TIMESTAMP(12) NOT NULL GENERATED AS ROW END IMPLICITLY HIDDEN
 ADD COLUMN trans_id  TIMESTAMP(12) GENERATED AS TRANSACTION START ID IMPLICITLY HIDDEN;
                
ALTER TABLE employees_s0
 ALTER COLUMN sys_start DROP DEFAULT SET GENERATED AS ROW BEGIN
 ADD PERIOD SYSTEM_TIME (sys_start, sys_end);
                
CREATE TABLE employees_s0_hist LIKE employees_s0;
                
ALTER TABLE employees_s0 ADD VERSIONING USE HISTORY TABLE employees_s0_hist;
清单 7. 使用 PERIODIGNORE 修饰符加载数据
EXPORT TO emp.del OF DEL MODIFIED BY IMPLICITLYHIDDENINCLUDE 
  SELECT * FROM employees_s0;
                
LOAD FROM emp.del OF DEL MODIFIED BY PERIODIGNORE IMPLICITLYHIDDENINCLUDE 
  REPLACE INTO employees_s0;

ALTER TABLE employees_s0 ADD VERSIONING USE HISTORY TABLE employees_s0_hist;

LOAD 命令中的修饰符 PERIODIGNORE 指示 DB2 忽略导出数据中的时间戳,从而在加载过程中生成新时间戳。在其他情况下,您可能会发现使用修饰符 是很有帮助的,该修饰符会将现有时间戳加载到系统时间列,而不是在加载操作期间生成新时间戳。


场景 1:为当前数据和历史数据迁移两个表

在该场景中,我们探讨一个现有时态解决方案,该解决方案与 DB2 中的系统周期时态表具有类似的属性,具体包括:

  • 两个单独的表,一个存储当前数据,一个存储历史记录数据。
  • 周期模型属于 inclusive-exclusive。
  • 两个时间戳列用于该周期。

现有表定义

我们假设现有时态解决方案使用下表捕获有关员工和变更历史的信息。

清单 8. 现有表定义
CREATE TABLE employees_s1 (
    empid        BIGINT NOT NULL PRIMARY KEY,
    name         VARCHAR(20),
    deptid       INTEGER,
    salary       DECIMAL(7,2),
    system_begin TIMESTAMP(6) NOT NULL DEFAULT CURRENT TIMESTAMP, 
    system_end   TIMESTAMP(6) NOT NULL DEFAULT TIMESTAMP '3000-01-01 00:00:00.000000' );
                
CREATE TABLE employees_s1_hist (
    empid        BIGINT NOT NULL,
    name         VARCHAR(20),
    deptid       INTEGER,
    salary       DECIMAL(7,2),
    system_begin TIMESTAMP(6) NOT NULL, 
    system_end   TIMESTAMP(6) NOT NULL  );

表 employees_s1 保存员工的当前信息。该表的每一行都有 system_end 值 3000-01-01 00:00:00.000000,以表明该信息是当前直至发生改变的值。

我们假设在表 employees_s1 上定义了适当的 AFTER DELETEAFTER UPDATE 触发器,以便将更新和删除行的之前映像插入到表 employees_s1_hist 中。

表 2表 3 显示了这两个数据库表的样例内容。

表 2. employees_s1 中的数据
empid名字deptid薪金system_beginsystem_end
1000John15000.002010-05-11 12:00:00.0000003000-01-01 00:00:00.000000
1212James24500.002011-05-11 09:30:00.1000003000-01-01 00:00:00.000000
4711Maddy15250.002011-07-30 09:25:47.1234563000-01-01 00:00:00.000000
表 3. employees_s1_hist 中的数据
empid名字deptid薪金system_beginsystem_end
500Peter14000.002010-05-11 12:00:00.0000002011-06-30 09:15:45.123456
1212James14000.002010-05-11 12:00:00.0000002011-05-11 09:30:00.100000
4711Maddy14000.002010-05-11 12:00:00.0000002011-07-30 09:25:47.123456

删除创建历史记录行的触发器

由于 DB2 自动生成已更新和已删除记录的历史行,所以您应该删除现有解决方案中生成历史行的所有触发器。如果需要在迁移过程中更新行,那么在迁移过程一开始就删除触发器可避免生成不必要的(可能)错误的历史行。

迁移表定义和数据

我们在该场景(清单 8)中假设的现有表定义不同于下列属性中的系统周期时态表(清单 1):

  1. 当前行的现有 system_end 值是 3000-01-01,但在系统周期时态表中应该是 9999-12-30。
  2. system_begin 和 system_end 列的数据类型是 TIMESTAMP(6),但系统周期时态表的数据类型必须是 TIMESTAMP(12)
  3. 系统周期时态表在基表和历史表中都必须有一个事务 ID 列
  4. system_begin 和 system_end 列的生成定义有所不同:分别是 DEFAULT CURRENT TIMESTAMP/TIMESTAMP'…'GENERATED ALWAYS AS ROW BEGIN/END
  5. 系统周期时态表需要一个 PERIOD SYSTEM_TIME 规范。
  6. 系统周期时态表必须启用版本控制。

清单 9 中的语句解决这些差异,并将表 employees_s1 转换成一个系统周期时态表。

清单 9. 将表 employees_s1 转换成一个 STT
-- 1. Change the system_end values to the same value that DB2 generates
UPDATE employees_s1 SET system_end = '9999-12-30';
                
-- 2.+3. Change data types to TIMESTAMP(12) and add the transID column
ALTER TABLE employees_s1 
    ALTER COLUMN system_begin SET DATA TYPE TIMESTAMP(12)
    ALTER COLUMN system_end   SET DATA TYPE TIMESTAMP(12) 
    ADD   COLUMN trans_start TIMESTAMP(12) GENERATED ALWAYS 
                 AS TRANSACTION START ID IMPLICITLY HIDDEN;
                
ALTER TABLE employees_s1_hist 
    ALTER COLUMN system_begin SET DATA TYPE TIMESTAMP(12)
    ALTER COLUMN system_end   SET DATA TYPE TIMESTAMP(12)
    ADD COLUMN trans_start TIMESTAMP(12) IMPLICITLY HIDDEN;
                
-- 4.+5. Set the auto generation of the columns system_begin and system_end, and
--       declare these columns as a system time period
ALTER TABLE employees_s1 
    ALTER COLUMN system_begin DROP DEFAULT SET GENERATED ALWAYS AS ROW BEGIN 
    ALTER COLUMN system_end   DROP DEFAULT SET GENERATED ALWAYS AS ROW END
    ADD PERIOD SYSTEM_TIME (system_begin, system_end);
                
-- 6. Reorg the tables and enable versioning
REORG TABLE employees_s1; 
REORG TABLE employees_s1_hist; 
                
ALTER TABLE employees_s1
    ADD VERSIONING USE HISTORY TABLE employees_s1_hist;

我们来详细讨论每个步骤:

  1. 强烈建议将现有 system_end 值从 3000-01-01 更新为与 DB2 生成值 (9999-12-30) 相同的值,但是对于迁移过程而言并不是完全必要的。迁移之后要通过一个常用 system_end 值识别当前行,该值必须是 9999-12-30。

    在单个语句中更新多个行可能需要大量日志空间。为了避免出现 “日志满” 的情况,请确保您的日志足够大,或使用中间提交更新一系列较小批处理中的行。

    注意,DB2 不能使用值 9999-12-31 作为当前行的 system_end 值。原因是,如果应用程序将其转换至不同时区,值 9999-12-31 可能更改为 10000 年中的一个值。这是不可取的,因为具有 5 位数字的年份时间不能插入 DB2 或再次在 DB2 中加载。

  2. 如果将 system_begin 和 system_end 列的精度从 TIMESTAMP(6) 增加到 TIMESTAMP(12),那么这些列中的现有值将自动转换并填补 6 个 0。例如,值 2010-05-11 12:00:00.000000 变为 2010-05-11 12:00:00.000000000000。
  3. 因为新列 trans_start 定义为可以为空,所以在该列中所有现有行值都为 NULL。对于新行,DB2 会根据需要为该列自动生成一个值。trans_start 列定义为 IMPLICITLY HIDDEN,所以不会在 "SELECT *" 查询的结果集中显示出来。但是,如果在 SELECTWHERE 子句中显式使用其列名,仍然可以进行检索和比较。
  4. 一旦将 system_begin 和 system_end 列更改为 GENERATED ALWAYS AS ROW BEGIN/END,用户就不能为 INSERTUPDATE 中的这些列提供值了。相反,DB2 总是为在其中进行插入和更新操作的事务生成一个时间戳值。
  5. 如果涉及的列没有所需的属性,那么添加 PERIOD SYSTEM_TIME 声明将失败。
  6. 必需显式激活版本控制,这样表就变成了一个 STT,会自动记录历史,且支持时态查询。如果历史表的该列不能与基表匹配,版本控制启用将失败。任何 INSERTUPDATEDELETE 语句执行之前,两个表都需要 REORG。这时,您可能也想通过发出 RUNSTATS 命令分别更新基表和历史表上的统计数据。

应用程序变更

从表中读取或写入表中的现有应用程序可能需要也可能不需要轻微改变,这取决于它们访问表的具体方法。例如,Java™ 应用程序不会在数据类型从 TIMESTAMP(6) 更改为 TIMESTAMP(12) 时受到影响。

INSERTUPDATEDELETE 语句

在该迁移场景中,即便不是所有,大多数 INSERTUPDATEDELETE 语句不修改也能继续工作。原因就是在现有解决方案(清单 8)中,system_begin 和 system_end 列的值是由默认值自动提供的,因此应用程序无需为这些列显式提供值。迁移到一个系统周期时态表之后,DB2 也能继续为这些列自动生成值。另外,在现有解决方案中,历史行由数据库触发器创建,这已由 DB2 的历史行自动生成功能所取代。另外,不需要在应用程序端进行更改。

如果应用程序包含写入历史表的 INSERTUPDATE 语句,或在当前表中为 system_begin 和 system_end 列提供值,这些语句则需要更改。原因是 DB2 可以为您自动执行这些写入操作。

查询

任何应用程序,即在应用程序代码或 SQL WHERE 子句(比如 WHERE system_end = '3000-01-01 00:00:00.000000')中显式测试之前的 system_end 值的应用程序,相反都应当通过更改来测试 DB2 生成的 system_end 值 9999-12-30。

很多常用时态查询工作将保持不变,但是可以大大简化。例如,假设您要检索员工 500 截至 2011 年 2 月 1 日午夜所记录的信息。清单 10 显示这个查询看起来与迁移之前,以及可以在迁移后使用的同一查询的简化版类似。

清单 10. 检索员工 500 截至 2011 年 2 月 1 日的信息
-- Before the migration:
SELECT empid, name, deptid, salary, system_begin, system_end
    FROM employees_s1
    WHERE system_begin <= '2011-02-01'
        AND system_end > '2011-02-01'
        AND empid = 500
UNION ALL
SELECT empid, name, deptid, salary, system_begin, system_end
    FROM employees_s1_hist
    WHERE system_begin <= '2011-02-01'
        AND system_end > '2011-02-01'
        AND empid = 500;

-- Simplified query after the migration: SELECT empid, name, deptid, salary, system_begin, system_end FROM employees_s1 FOR SYSTEM_TIME AS OF '2011-02-01' WHERE empid = 500;

尽管原始查询仍然适用于迁移表(如果拥有历史表的读权限),但是简化查询更为简洁,更容易理解,也更不容易出错。

类似地,假设您想查看员工 4711 的所有当前行和历史行。清单 11 阐述了迁移前后如何编码这类查询。

清单 11. 检索员工 4711 的所有当前行和历史行
-- Before the migration:
SELECT empid, name, deptid, salary, system_begin, system_end
    FROM employees_s1
    WHERE empid = 4711
UNION ALL
SELECT empid, name, deptid, salary, system_begin, system_end
    FROM employees_s1_hist
    WHERE empid = 4711;

-- Simplified query after the migration: SELECT empid, name, deptid, salary, system_begin, system_end FROM employees_s1 FOR SYSTEM_TIME FROM '0001-01-01' TO '9999-12-30' WHERE empid = 4711;

变体和其他考虑事项

在本小节中,我们将讨论迁移场景 1 的几个变体。

ROW_END 列中的 NULL 值表示 “直至最后一刻”

在该迁移场景中,我们假设现有数据使用 3000-01-01 作为当前行的 system_end 值,以表示 “直至最后一刻”。相反,如果您的应用程序使用 NULL 值该怎么办?在这种情况下,您必须将当前行的所有 system_end 值替换为 9999-12-30。此外,之前对 system_end 列的 NULL 值进行测试的任何查询都应该进行更改。

例如,谓词 system_end IS NULL 应更改为 system_end = '9999-12-30'。类似地,现有解决方案中的搜索条件(比如 system_end > '2012-02-01' OR system_end IS NULL)可简化成 system_end > '2012-02-01'

ROW BEGIN 列中的 Null 值

在系统周期时态表中,system_begin 列不能为 NULL。如果现有的 system_begin 列包含 NULL 值,则可能表明该行存在一些未知的时间点,所以必须用一个非空的值(比如 0001-01-01 或 1900-01-01)替换这些 NULL 值。

ROW BEGIN 值不能小于 ROW END 值

在系统周期时态表的每一行中,system_begin 值必须小于 system_end 值。如果现有数据不满足这一条件,查询则可能返回意想不到的结果。如果您不能确定,则在迁移之前考虑在现有数据表 employees_s1 和 employees_s1_hist 中定义约束条件 CHECK(system_begin < system_end)。如果任何行违反了这一约束条件,您将必须进行更正或删除。

一旦所有行都满足该约束条件且迁移已完成,就可以删除该约束条件。当为一个系统周期时态表启用版本控制时,DB2 自动确保 system_begin 小于 system_end 值,因此约束条件只会是不必要的开销。

历史表包含的列多于或少于基表

在现有的时态解决方案中,历史表包含的列数可能与基表不相同。然而,在将这两个表转换成一个系统周期时态表之前,两个表中的列数及其名称、位置、数据类型以及非空性必须相同。因此,您必须添加或删除列,这样两个表才能有相同的模式。

如果想要只记录基表中列的子集的历史记录,则考虑将基表垂直拆分成两个表,正如 “DB2 最佳实践:使用 DB2 进行时态数据管理” 一文所述。

对于每个行变更需要记录用户 ID 或应用程序 ID

系统周期时态表不能自动记录哪个用户 ID 或应用程序 ID 导致一个特定的行变更。为了记录这个信息,需要具有显式列和逻辑来提供正确的值。例如,您可以使用一个 BEFORE INSERT 触发器(如 清单 12 所示)在历史表中记录用户 ID:

清单 12. 在历史表中记录用户 ID 的触发器
CREATE TRIGGER pop_user_id_col
    NO CASCADE BEFORE INSERT ON employees_s1_hist
    REFERENCING NEW AS NROW
    FOR EACH ROW MODE DB2SQL
    BEGIN ATOMIC 
      SET user_id_col = CURRENT CLIENT_USERID; 
    END#

历史表也包含当前行

如果现有解决方案在基表和历史表中冗余地存储了当前行,则必须从历史表中删除当前行。否则,时态查询可能返回错误的结果。

历史表包含周期重叠的行

对于一个给定键值,比如,员工示例中的 empid 值,历史表决不能包含两个或多个具有相同键和重叠周期的行。对于同一键值,重叠周期意味着,在某个时间点上当前表中同一主键有 2 个当前行,这将是不一致的。因此,时态表查询可能返回意想不到的结果。

如果您不能确定,就应该验证现有历史数据不包含重叠部分。如果有的话,清单 13 中的查询将返回所有重叠部分。

清单 13. 检测时态重叠的查询
SELECT empID, 
       previous_end    AS overlap_start,
       system_begin    AS overlap_end
FROM
    (SELECT empID, system_begin, system_end,
             MIN(system_end) OVER (PARTITION BY empID ORDER BY system_begin 
                                   ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) 
             AS previous_end 
     FROM employees_s1_hist)
WHERE system_begin < previous_end;

历史记录行的生成:每个事务一个 vs. 每个语句一个

使用自己开发的基于触发器的解决方案和使用一个 DB2 系统周期时态表进行版本控制的区别在于,基于触发器的解决方案记录每个语句的历史行,而系统周期时态表记录每个事务的历史行。

如果同一行在单个事务中多次更新,那么触发器就会为每个更新生成一个历史行。结果会导致历史表包含中间版本的行,中间版本从未在数据库中提交过。因此,历史表的任何查询在基表中都能看到未提交数据。从事务角度来看,这意味着任何历史查询执行 “脏读 (dirty reads)”。如果隔离级别是未提交读取 (UR),那么对于大多数应用程序来说显然是不可取的。

该问题通过迁移到 DB2 中的系统周期时态表而得到了解决。对于每个已修改的行,系统周期时态表为每个事务至多创建一个历史行,这会记录该行在当前事务之前的状态。在 DB2 中,这种行为节约了存储空间并确保历史数据在事务上是正确的。


结束语

在 DB2 中,时态功能为时间敏感的数据管理、合规性和审计需求提供高级支持。本系列文章的第 1 部分介绍了采用 DB2 中的系统周期时态表的 2 个常见迁移场景。事实证明,迁移到时态表真的很容易。

场景 0 是最基础的案例,在该场景中,只使用 3 个简单的 DDL 语句,就可将常规(非版本控制)表转换成一个系统周期时态表。场景 1 假设一个应用程序具有两个现有表,这两个表使用时间戳和触发器来记录当前和历史数据。同样,将它们迁移到时态表只需几个 DDL 语句。其他迁移场景将在本系列文章的后续部分进行讨论。

参考资料

学习

获得产品和技术

  • 下载 DB2 试用版或免费的 DB2 Express-C,亲自试用全新的时态数据管理特性。
  • 使用 IBM 试用版软件 构建您的下一个开发项目,可直接从 developerWorks 下载。
  • 现在,您可以免费使用 DB2。下载 DB2 Express-C,这是一个为社区提供的 DB2 Express Edition 免费版本,该版本提供了与 DB2 Express Edition 相同的核心数据功能,并为构建和部署应用程序提供了坚实的基础。

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Information Management
ArticleID=870580
ArticleTitle=在 DB2 中采用时态表,第 1 部分: 系统周期表的基础迁移场景
publish-date=04162013