简介
IBM 提供了 DB2 Spatial Extender,用于在 DB2 Universal Database TM 关系数据库中存储、管理和分析空间数据。空间数据是用结构化的类型存储在数据库中的,这样才能为其复杂的属性建立正确的模型,也才能通过一种更简单、更可靠的方式来利用那些专用的多维索引方法。
DB2 Information Management 产品家族的另一个关键组件是 DB2 Replication 套件。这个套件可以对从多个数据库中选出的数据集进行维护。对数据进行的修改可以从一个数据库的某张表拷贝到另一个数据库中的另一张表。DB2 复制是 DB2 Universal Database v8 的集成特性。它的复制能力使之适合于处理 DB2 的内置数据类型,如 INTEGER、DOUBLE 或 VARCHAR 等。目前,复制中并没有对已有空间数据的内置支持,也不能在用户需要的时候直接将空间数据复制到另外的数据库中。
本文描述了一种方法,可以用 DB2 中已有的复制机制对空间数据进行复制,具体方法是先将空间数据转换成基于 LOB 的外部表现形式,然后复制这些 LOB。在目标数据库上,我们施行相反的转换过程,再次构造出 DB2 Spatial Extender 能够理解的几何图形。这种方法要求在设置复制的时候进行一些小的修改。我将在本文中逐步解释必要的设置过程,这样您就能很轻松地理解,并在您自己的环境中应用这种方法了。
理解空间数据与复制
空间数据的作用是,在对象-关系型数据库系统中用标量值的形式表示复杂的空间特性。所谓空间特性,是指点、线、多边形,以及上面这些特性的集合。有了 DB2 Spatial Extender,您就具备了必要的数据类型与功能,可以在 DB2 数据库中对空间数据实施管理。只要您的数据库启用了空间功能,您就可以回答诸如“所有具有一定收入的顾客离我的商店有多远?”或“我是不是在某段特定的时间范围和某块相同区域内规划了多个建筑群?我能否将其合并?”之类的问题。 DB2 Spatial Extender 定义了一组数据类型和功能,可用于管理空间数据库(几何图形),也可以帮助您充分利用存在于您的数据中的空间信息。
空间数据类型是通过类型层次来组织的,可为您提供更高的抽象级别以及更好的类型安全性。不准确地说,SQL 中的类型层次等同于面向对象编程语言中的类层次。 图1向您展示的是空间类型的层次关系。用蓝色背景表示的类型代表不可实例化的类型。这些类型就好比 Java 或 C++ 程序语言中的抽象类。其他所有的类型都是可实例化的。
图 1. DB2 Spatial Extender 的类型层次关系
DB2 Spatial Extender 中包含了 100 多个不同的函数,可用于对空间数据进行操作。比如说,您可以测试一个几何图形是否包含在另一个之中,或者,您可以计算两个几何图形之间的距离。
很多种不同的用户情景中都用到了数据复制。举个例子来说,复制可以实现多个不同的用户组在公用的数据上独立工作,被复制的那部分数据仍然可以保持同步。复制也为高可用的实现提供了一种方法。
图2概要描述了 DB2 复制的基本工作原理。用户的应用程序发出针对某一源表(source table)的查询(SELECT)及数据更改语句(INSERT、UPDATE、DELETE)。这张表中的数据会复制到另一个数据库上的另一张表中,我们把后面这张表称为目标表(target table)。在源表中对数据所作的修改会记录在 DB2 日志中。异步的 Capture 进程会读取日志记录,当它检测到对源表进行了修改时,就从日志记录中将变化的数据取出,放到一张中转表中,这张表叫做变化数据表(change-data table,即 CD 表)。另一个进程 Apply 周期性地扫描 CD 表,如果其中出现新的信息,这个进程就将其读出,然后发布到目标表上。Apply 进程可以处理多个目标表。因此,DB2 复制的架构遵循经典的发布/订阅机制。
图 2. DB2 复制概览
在下一节中,我将要描述一种方法,它可以用 DB2 及其复制工具提供的一些特性对空间数据库进行复制。转换函数和转换组的概念在空间数据复制中的重要性更高,在结构化类型的转换函数一节中我们将对其进行更详细地讨论。在捕获空间数据中的变化一节中,我们要介绍如何通过设置,捕获源(捕获)数据库上的空间(或其他类型)数据中发生的变化。本节将展示 CD 表和视图的定义方法,并解释 Capture 进程的工作原理。在准备目标数据库一节中,我会告诉您,应该用什么样的方式准备目标数据库,Apply 进程才能直接将空间数据分别存储到对应的表中。为进一步简化源数据库和目标数据库的设置过程,我们实现了一个命令行工具,您可以从本文的“下载”部分找到这个工具。有关这一工具的详细信息将在设置工具“db2se repl”一节中讨论。本文以若干条对 DB2、空间信息以及复制特性的注解结束。
总览图
复制组件 —— 包括 Capture、Apply、Monitor 以及 Replication Center 的管理接口 —— 在一般情况下的设置并不能支持结构化的类型,特别是空间数据类型。Capture 程序不能解释 DB2 日志记录中所保存的空间数据,因此,我们必须采取一组措施克服这一限制。DB2 及其复制工具中已经内置了一些功能,可用来实现这些措施。让我们从高层开始介绍。
当我们复制空间数据的时候,要先将其转换成 LOB 数据,更确切地说,是转换成二进制大对象(BLOB)。采用这种方式之后,复制工具就可以对一种已经支持的数据类型进行处理了,而且也并不知道这些 BLOB 数据实际上是空间数据。我们可以利用视图来实现这样的转换。我们可以在复制中定义视图,这样 Apply 程序在向目标进行完全更新的过程中就可以从一张来自源表的视图中取出数据,对于差量数据的更新则从 CD 表的视图中取出数据。视图实现了对源数据的“逻辑”扩展与/或解释,因此,Apply 程序就可以在获取数据的过程中对数据进行转换或扩展。在这样一种空间数据复制的实现方法中,我们针对源表定义一张视图,令其以 BLOB 的形式将空间数据包含在内。图3展示了这种方法的架构。
图 3. 用 BLOB 复制空间数据
Apply 进程对来自源视图的数据进行查询。在这里,它看到的不是 Spatial 字段,而是 BLOB 字段,这个字段中存储的实际上正是由源表转换来的空间数据。另外几个字段 —— 关键字字段“k”和数据字段“dl” —— 的复制方法与常规的 DB2 复制相同,不需要进行特殊考虑。数据是通过 Apply 进程插入到目标视图中的,如图中的虚线所示,这个过程最终跨越了系统的边界。目标视图的定义与源视图相匹配,这意味着存储空间数据的数据类型依然是 BLOB。 除了目标视图之外,我们还定义了三个 INSTEAD OF 触发器,用于在 INSERT、UPDATE 或 DELETE 操作的过程中实现逆向转换。逆向转换的作用是根据 Apply 进程复制过来的二进制数据构造出空间数据。
完全更新(即将源表中的全部数据复制到目标表上)的过程中使用的也是同一套逻辑。基本的原则与差量更新(即只复制上一次 Apply 周期以来发生变化的数据)相同。对于差量更新来说,我们必须考虑变化数据表及变化视图(CD 表和 CD 视图)。CD 表是通过 Capture 进程来维护的,这个进程负责扫描 DB2 日志,提取出数据变化信息。然后将这些数据变化信息收集到 CD 表中。CD 视图的作用与其他的视图类似,是 CD 表中数据的一种不同的表现形式。这些视图是从实际需要进行复制的视图中派生出来的。 图4展示了源数据库,包括 CD 表和 CD 视图的定义。
图 4. 源表、源视图与 CD 表、CD 视图
现在我们面临一种两难的境地。首先,Capture 进程不能理解空间数据类型,因此,它无法收集 CD 表中变化的数据。第二个问题的来由是上面谈到的那个将空间数据在源视图中转换为 LOB 数据的过程。但是只要您考虑到 DB2 和 DB2 复制的下列功能,上述问题将迎刃而解:
在这种实现方式中,由于要通过视图中的二进制数据类型访问空间数据,因此对于 CD 视图也需要构建相同的逻辑。然而,Capture 进程并不知晓空间字段要按照 LOB 字段来处理。这就要求我们伪造一个更新指示器字段。我们向源表中增加另一个字段,并定义两个触发器,仿照 Capture 进程维护 LOB 字段的更新指示器的方式来维护这个字段。然后用 DB2 Replication 注册这个附加的字段,这样,对该字段所作的任何修改都能够被捕获并保存在 CD 表中。CD 表及 CD 视图便都可以包含这个更新指示器。
现在就要用到前面描述的功能了。如图 4 所示,更新指示器字段标记为“@”。我们通过编写触发器,实现当空间字段被修改时将更新指示器的值设置为“U”。更新指示器同时也是 CD 表的一部分。这样,CD 表就可以保存空间字段是否被修改的信息了。CD 视图定义为将 CD 表中除更新指示器外的全部字段原封不动地映射过来,而将更新指示器的名字修改为经过转换的空间字段。结果是,Apply 进程在源视图中找到一个 BLOB 字段,又在 CD 视图中找到一个与该 BLOB 字段的名字相匹配的更新指示器字段。非空间性的数据是从 CD 表中取出的。当进行 INSERT 操作或 UPDATE 操作时,空间数据是从源视图中的 BLOB 字段里得到的,同时,CD 表中的更新指示器设置为“U”,表示空间数据曾经被修改过。 图5说明了上面所讨论的全部逻辑流程。
图 5. 空间数据复制概览
下面几节将更详细地解释把空间数据转换成 BLOB 的必要性,以及如何设置源表和目标表。随后我们还会给出一个例子,引导您在 Replication Center 中执行各种必要的操作,并帮助您完整地理解这种空间数据复制方法。
结构化类型的转换函数
现在,让我们来看一看空间数据是如何在数据库服务器和客户机之间传送的。空间数据的表现形式为结构化的数据类型。正如在其他任何一种编程语言一样,结构化数据无法实现原封不动的简单传送。我们需要对其进行线性化处理,或将其转换成某种形式的字节流。然后,再把这种字节流从客户机传送到服务器上,反之亦然。
DB2(以及 SQL 标准)使用所谓的“转换函数”(transform function)来执行线性化操作,将 DB2 内置的结构化数据转换成外部表现形式,也可以转换回来。这种外部表现形式是一种单一的基于内置数据类型的标量值。内置数据类型可以是 VARCHAR、INTEGER、BLOB 或 CLOB,或者是 DB2 中包含的任何其他数据类型。转换结果到底要求为何种数据类型,这取决于特定的转换过程。在 DB2 Spatial Extender 中,我们用 BLOB 代表空间数据的外部二进制表现形式,而用 CLOB 代表空间数据的外部文本表现形式。
为了在 DB2 客户机和服务器之间实现通信过程,我们需要两个不同的转换函数。一个函数用来将空间数据从 SQL 形式转换成外部数据格式,另一个函数则将外部数据格式转换成 SQL 形式。为了简化这两个转换函数的使用过程,您可以采用“转换组”(transform group)。转换组中包含一个 FROM SQL 函数和一个 TO SQL 函数,前者负责将结构化数据转换成外部格式,后者负责根据用外部格式表示的数据构造出 SQL 数据库中的结构化数值。
转换组
下面让我们通过一个例子,看看转换函数和转换组的概念,以及它们的应用情况。DB2 Spatial Extender 提供了四个转换组。每一个转换组都包含一个将几何图形转换成外部表现形式的函数和一个将外部表现形式转换成几何图形的函数。这四个转换组支持下面几种数据格式:
从外部表现形式(WKT、WKB、GML、Shape)转换成相应的几何图形并存储在 SQL 数据库中的过程是由 ST_Geometry、ST_Point、ST_LineString、ST_Polygon、ST_GeomCollection、 ST_MultiPoint、ST_MultiLineString 以及 ST_MultiPolygon 等构造函数实现的。每一个构造函数都经过重载,既可以接受 BLOB,也可以接受 CLOB 作为输入参数。根据输入数据的不同,这些构造函数可以决定您提供的到底是何种格式,然后对数据进行恰当的编码。
转换组的使用方法用 清单1中的 SQL 代码就很容易解释明白。
清单 1. 使用转换组
|
从上面的清单中可以看出,SELECT 语句取出表 T 中的所有字段。字段 G 声明的类型是 ST_Point,这是一种结构类型。很显然,当我们取出这些字段中存储的结构化数据时,需要对其进行转换。这个查询中并没有显式调用任何转换函数,但是 DB2 能够识别出这种结构化的数据,而且能够根据 CURRENT DEFAULT TRANSFORM GROUP 这一特殊注册项的设置来决定使用哪个转换组和转换函数。在第一个查询中,DB2 发现转换组指定为 ST_WellKnownText。该组中的 FROM SQL 转换函数是 ST_AsText()。这样,DB2 就调用 ST_AsText() 函数,将字段 G 中的所有结构化值转换成 CLOB。DESCRIBE 语句的输出能够证明这一点。当数据被取出的时候,这些 CLOB 值就和 ID 字段的值联系在了一起。
作为本节的结论,我们之所以要在空间数据中应用转换的概念,是因为我们想使从一个数据库向另一个数据库传输结构化数据的过程更加高效。然而,复制工具并不直接支持转换组和转换函数。因此,我们使用了一种更加手工化的方法。在视图的帮助下,我们将结构化数据转换为外部的表现形式,逆转换也是由视图和 INSTEAD OF 触发器一起实现的。下面两节将就这一点进行详细讨论。
捕获空间数据的变化
DB2 Replication 支持 LOB 的复制 1 ,空间数据也可以用转换组中的转换函数转换为 LOB。这些特性结合在一起,就能够直接支持空间数据的复制。在本节中,我们将解释如何对源表进行设置,以实现将空间数据转换为 LOB,并讨论 Capture 进程的功能。设置目标表的过程和 Apply 进程的功能将在下一节讨论。
转换组的概念通常用于在 DB2 数据库服务器和 DB2 客户机之间传送数据,在两个数据库之间传送数据也适用相同的概念。现在,源数据库变成了数据库客户机,它将数据提供给目标数据库(即数据库服务器)并存储在其中。从这个角度来看,这正是经典的客户机/服务器环境。
选择一种转换方式
前面已经讲过,我们在复制空间数据的时候可以选择不同的外部数据格式。为了确定正确的转换函数,我们需要制定下面的标准:
为了同时支持这两项标准,我们既可以实现自己的转换组,也可以使用 DB2 Spatial Extender 已经提供的转换组。我选择使用已有的转换组 ST_WellKnownBinary,因为预定义转换组的功能经过了很好的测试,比起自己实现来可以降低成本。
选择文本形式还是二进制形式同样十分简单。文本形式通常比二进制形式更长,因为对于每一个浮点数而言,文本形式需要更多的字符才能表示。二进制数据的又一优点在于其更高的正确性。空间数据中的点是用符合 IEEE 754 标准的浮点数来存储的。将二进制数据用文本数据表示则牵扯到转换,会引入数据舍入的问题。因此,我们可能会降低已经存在于复制源中的数据的准确性,而且在目标数据库中重新构造二进制数据的过程又会进一步降低准确性。直接传输二进制数据就可以避免第二个问题。
根据前面的观察,转换组 ST_WellKnownBinary 和 ST_Shape 成为我们可能的选择。进一步观察 ESRI 的 shape 格式可以发现,shape 格式无法识别几何图形是只由一个部分组成的具有多个部分的几何形状,比如 multipoint(10 10),还是单纯的只有一部分的几何形状,如 point(10 10)。因此,如果任何类型的几何图形存储在这张待复制的表中,那么特定的类型信息就可能在复制过程中丢失掉。然而,如果我们知道空间数据具有某种更加专用的类型,那么这个参数是不适用的。只有在目标表上使用适当的构造函数才能保证重新构造出正确的专用类型。
已知二进制格式对于大型系统而言还有一个优势。这种格式既支持小型编码也支持大型编码。而 shap 格式将所有的信息都存储在小型编码中(每一条记录的头部除外),因此,就需要将每一个浮点数从大型编码转换到小型编码。
在另一方面,已知二进制格式还有一个缺点,即它比 shape 格式稍微冗余一些。它用于表示几何图形所需的字节数要多出几个。因此,我们并不强制使用 ST_WellKnownBinary,而放弃 ST_Shape,因为这两个转换组都能够有效地实现相同的目标。
不过,DB2 的复制并不直接支持 DB2 的转换组机制。 它不允许用户在从源表取出数据或向目标表插入数据时定义某个组。 复制工具只能将空间字段识别为一种不支持的类型。结果是我们必须在复制过程开始处理空间数据之前手工将其转换。
这一限制的确带来一些不便,但并不能阻止我们复制空间数据。我们将基于源表定义一个视图,让从空间数据到外部格式的转换在这个视图中进行。以这种方式创建的视图现在可用作订阅组中的复制源。
在复制 LOB 数据的时候,LOB 不会存储在 CD 表或 CD 视图中。而是只有更新指示器存储于其中。更新指示器可以供 Apply 进程使用,用于确定是否对 LOB 字段执行了更新操作。如果该字段未被更新,那么就没有必要将 LOB 的值从源表拷贝到目标表中。这种方法可以避免不必要的数据移动。
请您注意,插入操作总是会引起 LOB 数据的拷贝,因为 LOB 数据是新记录的一部分,目标表中并不存在。出于我们将要在视图中实现的手工格式转换过程,CD 表中并不存在与源表对应的更新指示器。前面提到,复制工具本身并不会把空间字段看成是一个可以复制的字段。结果导致我们必须自己提供并维护这样一个指示器字段。
视图的定义以及附加的更新指示器字段导致我们的方法分为四步,现在我们将概述其要点,并举出示例。假设我们打算复制 清单2中定义的表中的所有数据,包括空间数据。
清单 2. 定义要复制的源表
|
定义更新指示器
第一步是增加一个字段充当更新指示器,以及两个维护该字段的触发器。一个触发器用于在每次对记录进行更新时重置指示器。第二个触发器负责在当且仅当对几何图形字段进行更新时给指示器赋值。触发器是根据其定义的顺序触发的,也就是说,CREATE_TIME 较早的触发器会比在其之后定义的触发器先激活。由于我们希望先重置指示器,再在几何图形字段更新时对其赋值,因此我们必须当心触发器的执行顺序。必须先创建重置指示器的触发器,然后再创建更新指示器的触发器。 清单3中的 SQL 语句显示了必要的修改操作,以及按正确的顺序定义触发器的情况。
清单 3. 为空间字段增加更新指示器
|
在源表上创建视图
为了让所有的数据都具有适合于复制的正确格式,我们需要在源表上定义一张视图。这张视图的定义将涉及源表中所有需要复制的字段。源表中有两个空间字段。如前所述,空间字段需要转换成已知二进制格式。ST_AsBinary 函数可用于实现这一转换。更新指示器是另一个未在视图定义中出现的字段。我们并不想复制更新指示器;这个字段只在 Apply 进程决定来自空间字段的数据是否需要复制的时候才是有用的。因此,我们用 清单4中所示的 SQL 语句来创建基于复制源表的视图。
清单 4. 创建将空间数据转换成 BLOB 的视图
|
请注意,您在视图定义中应该总是用完全限定性的表名和对应的字段名,这样复制工具才能成功解析其定义,并析取出后面所需的相应信息。
创建视图和向源表中增加更新指示器的顺序无关紧要。这两个步骤都可以完全独立地进行。然而,后续步骤则要求必须完成前面这一步。
将源表注册为复制源
现在所有的准备工作都已完成,我们可以开始设置实际的复制环境了。当从视图进行复制时,DB2 复制机制要求将视图所基于的表也注册为复制源表。否则,我们就无法利用 Capture 进程来扫描日志文件,找出所有被修改且需要复制的记录。这意味着我们只能在目标数据库中对被复制的表进行完全刷新。而这在很多情况下显然不可行。
下面,我们假设您已经创建了捕获控制表(capture control table),也为数据库启用了复制功能。启用复制要求数据库配置参数 LOGRETAIN 设为 YES。请注意,您必须在修改了 LOGRETAIN 参数之后备份数据库。
注册源表的方法很简单。我们需要捕获除空间字段之外,且包括更新指示器在内的所有字段上的变化。 图6显示在 DB2 Replication Center 中注册源表的情况。您从这张图中可以看到,TRACK 字段的任何变化都不会被捕获,因为它是具有结构化类型的字段。
图 6. 将表 STREETS 注册为复制源
注册过程创建了一张新的变化数据表(即 CD 表),Capture 进程用这张表记录从 DB2 日志中挖掘出来的 STREETS 表上发生的任何变化。让我们快速验证一下这一点。 清单5显示了几条 SQL 语句,先对 STREETS 表中的数据进行了修改,然后查询 CDSTREETS 表,看是否已经捕获并组装好正确的数据。请您注意,可能要等待一段时间之后 Capture 进程才能从日志中取得数据填入 CD 表中。