对象数据库:不再仅仅适用于 CAD/CAM
应用程序正变得越来越复杂,并且越来越依赖于大量的持久数据。大多数应用程序依赖于关系数据库来管理这些大量的数据。然而,对象数据库已成为各种应用程序的另一个有吸引力的选择。正如 Esther Dyson 所说,“使用表格存储对象就像开车回家,然后将其拆卸放入车库。它可以在早上再次组装起来,但人们最终会问这是否是停车的最有效方式。” [ORF96]
对象数据库起源于 CAD/CAM 领域。对象数据库支持 CAD/CAM 应用程序所需的程序员定义的数据类型和复杂关系。为了管理额外的复杂性,面向对象编程语言正成为开发当今主流应用程序的标准。使用对象数据库是这种语言选择的自然延伸。对象数据库提供更好的性能、更快的开发和更健壮的程序。本文将探讨这些说法,并研究一个公共领域的对象数据库,即 Texas Persistent Store。
关系数据库使用一种单独的编程语言,称为“结构化查询语言”(SQL)。有时,会使用一种类似但非标准的查询语言来定义表的布局以及与数据库的交互。关系数据库的一个缺点是它们只能存储有限的数据类型;为了存储更复杂类型的对象,它们必须以某种方式映射到 SQL 支持的原始类型中。相比之下,对象数据库使用面向对象编程语言来定义数据和操作数据库中的对象。这消除了尝试将复杂对象和关系映射到关系世界的有限数据类型和表中的“阻抗失配”。减少容易出错的翻译代码使程序员能够专注于对象行为的语义,而不是存储和检索对象的语法。没有嵌入式 SQL,运行时存储错误就被消除了。
虽然关系数据库必须使用 SQL 在运行时重新创建这些关系,但对象数据库直接在数据库中捕获对象间的关系。这通过减少编写的代码行数和运行时执行的代码行数,使开发变得更容易。这样做的一个积极的副作用是,您不必为了适应连接表或向类中添加外键标识符而做出任何设计上的妥协。
对象数据库的工作原理是从一个命名的对象开始,导航到类层次结构中的其他对象。这些命名的对象可以是单个对象或对象容器。导航到包含的对象允许对象数据库立即加载对象,而无需查询。这减少了程序员编写和测试的代码量,从而使程序更健壮,开发周期更短。
如果更快的开发和更健壮的程序还不足以说服您,那么让我们尝试提高性能。许多供应商的目标是使对持久对象的访问速度与对瞬态对象的访问速度一样快。这是一个不可能实现的目标,因为加载存储的对象需要访问磁盘,甚至可能需要访问网络。一旦对象加载到内存中,复杂的客户端缓存和内存管理技术就可以提供非常低的开销。某些实现,例如 Texas Persistent Store 和 ObjectStore,一旦对象被交换到内存中,就没有任何开销。大多数关系系统不会在客户端系统上缓存结果,因此在下次访问时会产生不必要的网络传输和额外的查询。
遗憾的是,目前很少有基准测试可以比较关系数据库和对象数据库,以支持这些性能声明。有两个常见的对象数据库基准测试:工程数据库基准测试(也称为 001、Sun 基准测试或 Cattell 基准测试),由 Sun Microsystems 开发,以及 007 基准测试,由威斯康星大学开发。001 基准测试旨在证明对象数据库在工程应用中优于关系数据库。结果表明,测量的对象数据库比基准关系数据库快 30 倍或更多 [CAT92]。007 尝试提供更广泛的测量组合,包括多用户访问。007 基准测试的实现由威斯康星大学审核,应可从参与的数据库供应商处获得 [LOO95]。
一些高级对象数据库功能包括集群和可配置的对象获取策略。集群允许程序员指示一组对象将一起使用。当请求集群中的任何一个对象时,集群中的所有对象都会加载到客户端缓存中。这减少了加载客户端缓存所需的磁盘和网络传输次数。一些供应商允许配置对象获取策略,从而可以自定义服务器发送的额外数据量。这些性能提升通常以增加代码行数和额外的性能分析为代价。
对象数据库有两种模型。一种模型要求您从供应商提供的持久基类继承,类似于对象数据库管理组 (ODMG) 标准 [CAT96]。持久基类提供了对对象进行数据库请求的接口。另一种模型是指针回写技术,它允许您使用指向持久对象的指针,就好像它们从未离开过内存一样。我认为指针回写技术在编程模型和灵活性方面更胜一筹,我将在后面更详细地介绍这项技术。
指针回写是将磁盘格式指针更改或映射为内存格式指针。指针回写对客户端程序是透明的。当程序使用指向未加载对象的指针时,会发生段错误。供应商库捕获该错误并从数据库中获取对象。然后,它将指针设置为新加载的对象,并将控制权返回给客户端程序。客户端程序完全不知道发生了数据库访问。
标准 C++ 内存管理技术的使用允许相同的应用程序代码在瞬态对象和持久对象上工作。对象是使用 C++ placement new 运算符构造的。在持久内存中分配会隐式地将对象存储在数据库中。从数据库中删除对象就像调用 C++ delete 运算符一样简单。
Texas Persistent Store 是一个公共领域的 C++ 指针回写对象数据库。Texas 由德克萨斯大学奥斯汀分校创建和维护。当前的 0.4 beta 版本支持 Linux 1.2.9、Solaris 2.4、SunOS 4.1.3 和 DEC Ultrix 4.2 平台,所有平台都使用 GNU g++ 2.5.8 或 g++ 2.6.3 编译器。它还支持使用 IBM CSet 编译器和 Sun 3.0.1 C++ 编译器的 OS/2 2.1。白皮书和源代码可通过匿名 ftp 从 cs.utexas.edu 的 /pub/garbage 目录或 OOPS 研究小组的主页 www.cs.utexas.edu/users/oops 获取。
我的设置包括在配备 16Mb 内存的 486/100 上运行的 Slackware 1.2.8。Texas 在我的 Linux 机器上安装和运行,几乎没有麻烦。由于 g++ 2.6.3 中的编译器模板错误,您必须修补编译器或修改 makefile 以使用 -fexternal_templates 编译器开关。文档描述了该错误和修复方法,使库的安装相当轻松。Texas 附带了一些测试程序和示例,以确保系统运行正常。
要开始使用 Texas 库进行编码,您只需了解四个简单的功能:初始化宏、打开和关闭持久存储、查找和创建命名根,以及将对象分配到持久内存中。在这里,我将简要讨论这些功能,然后深入研究一些代码。
Texas 库的初始化通过调用 TEXAS_MAIN_INIT() 宏来完成。此宏设置信号处理程序,读取架构信息和虚函数表。TEXAS_MAIN_UNINIT() 宏删除信号处理程序并将系统重置为之前的状态。
使用 open_pstore() 函数打开数据库。如果文件不存在,则创建并打开数据库。打开数据库会启动一个事务。您可以在程序的生命周期内通过调用 commit_transaction() 或 abort_transaction() 来操作事务。commit_transaction() 会将当前所有持久对象保存到磁盘并启动新事务,而 abort_transaction() 会丢弃所有脏页并启动新事务。要关闭数据库,请使用 close_pstore() 函数。这将隐式调用 commit_transaction() 并关闭文件数据库。如果您不想提交当前工作,可以调用 close_pstore_without_commit() 函数。
命名根是您从数据库中检索持久对象的入口点。它们提供了一种机制,程序可以通过该机制直接导航到对象或搜索对象的容器。您可以使用 add_root() 函数创建命名根。命名根通过 get_root() 调用检索,数据库通过 is_root() 函数查询命名根是否存在。
Texas 内存分配宏 pnew() 和 pnew_array() 隐藏了 C++ placement 运算符 new。分配宏还隐藏了 TexasWrapper 模板类的实例化。TexasWrapper 类处理架构信息的创建和在数据库中的注册。架构信息保存类属性在数据库中的布局。
让我们看一个示例,了解在 Texas 中使事物持久化是多么容易。按照传统,我们编写熟悉的“hello world”程序,但带有一个持久化的转折:我们记录程序已执行了多少次。清单 1 显示了此任务的代码。
首先,我们初始化 Texas 库,并将 main 函数中的 argc 和 argv 参数传递给它。然后,程序在当前工作目录中打开一个名为“hello.pstore”的持久存储。
查询持久存储,以使用 is_root() 函数查看名为 "COUNT" 的命名根是否存在。如果命名根不存在,则分配一个新的整数。新整数初始化为零,并使用 add_root() 函数命名为 "COUNT"。否则,我们从数据库中检索整数。计数器递增,结果打印到标准输出。所有脏对象都被提交,数据库被关闭。库被取消初始化,程序退出。
随着程序的每次连续运行,名为 "COUNT" 的整数将被检索、递增并重写到数据库中。您会注意到这一切都非常无缝:没有显式调用查询、插入、加载或保存。
接下来,我们将简要探讨 Texas 如何在页面错误时回写指针以及如何处理内存管理。这绝不是对这些主题的完整讨论。有兴趣了解有关 Texas 系统的更多信息的读者应下载白皮书和源代码。
Texas 使用传统的虚拟内存访问机制来确保对任何持久页面的首次访问都会被 Texas 库拦截。此页面从数据库加载并扫描持久指针。对该页面上的所有持久指针进行回写到内存地址。所有新页面都被保留并受到访问保护。当程序遍历未加载页面的对象层次结构时,此错误和保留过程会重复进行。虚拟内存页面比实际引用页面提前一步保留。这意味着程序永远看不到回写指针,只能看到受访问保护的指向未加载对象的指针。Linux 实现使用 mprotect() 系统调用来设置页面上的访问保护。有关此主题的深入讨论,请参阅在第五届国际持久对象系统研讨会上发表的 Texas 白皮书 [SIN92]。
Texas 允许您访问多个数据库,每个数据库都有自己的持久堆。标准的瞬态堆和栈也可用于非持久内存分配。Texas 不将其地址空间划分为区域,从而允许来自不同堆的页面在内存中交错。每个页面必须仅属于一个堆,因此 Texas 为每个堆维护单独的空闲列表。当空闲列表为空或没有足够大的可用空闲内存块时,将创建新页面。新页面被划分为大小均匀的内存块,这些内存块足够大以容纳要分配的对象。所有其他块都链接到空闲列表上。页面的这种统一分块使得页面上对象标头的识别变得微不足道。只需检查页面的第一个标头即可确定该页面上所有内存块的大小。其他对象标头的对齐方式很容易推断出来。对象的标头存储对象的架构信息,以便可以识别对象并正确回写。
虽然 Hello Persistent World 程序不是很令人兴奋,但它展示了使对象(在本例中为整数)持久化所需的最小工作量。下一个示例演示了对象数据库捕获对象之间关系的能力。这个人为设计的示例展示了几个多对多关系。它还暴露了 Texas 库当前的一些缺陷。该示例是一个用于跟踪堆满我办公室的许多不同研究论文和书籍的系统。有关使用非统一 Booch 表示法的类图,请参见 图 1。这两个示例的设计文件和源代码可在我的主页 www.qds.com/people/gmeinke 上找到。
类图显示了类 PublishedWork,它是所有已出版材料的抽象基类。它提供了用于查询对象的标题、价格、页数和作者列表的简单方法。Author 和他们的 PublishedWork 之间的关系是多对多关系的示例。Publisher 和他们出版的 Books 之间的关系是一对多关系。由于外键和多对多关系所需的中间连接表,在关系数据库中表达这些复杂关系很麻烦。相比之下,Texas 使用 C++ 容器处理这些复杂关系,并将它们直接存储在对象数据库中。对于外键数据成员,对象设计无需做出任何妥协。
Texas 库当前的限制包括缺乏多用户支持以及无法查询容器以查找对象的某些实例。查询限制源于 Texas 库未提供容器这一事实。大多数商业对象数据库供应商提供一组支持查询的优化容器类。如果您需要的是一个非常快速、单用户、持久的对象存储,那么这些限制是次要的。另一个限制是无法透明地处理持久对象和瞬态对象。您无法发现对象分配在哪个堆上;这会在指向其他包含对象的对象的对象中引起问题。虽然这对于较小的程序来说是一个较小的限制,但它确实会影响更大、更复杂、多数据库程序的开发。
Texas 的未来看起来光明。它是一个健壮高效的单用户、可移植库。我和一位同事计划将 Texas 移植到 Windows NT。这将完善对最流行的平台 Solaris、Linux 和 NT 的支持。我们还计划为堆的透明处理提供一些小的增强功能。STL 和持久分配器可能会在一定程度上缓解容器和查询支持的不足,但多用户支持仍然遥遥无期。
对象数据库不是软件开发的灵丹妙药,但它们确实为已经使用面向对象编程语言的人们提供了更健壮和自然的编程环境。它们提供比关系数据库更好的性能和更多的性能调优选项。对于中小型单用户项目,Texas 数据库是一个有吸引力的选择;对于大型多用户项目,您可能需要查看 Object Design, Inc. 的 ObjectStore。ObjectStore 支持大量平台和编译器——遗憾的是,不支持 Linux。ObjectStore 是一款非常快速且灵活的对象数据库产品。有关 ObjectStore 的更多信息,请访问其主页 www.odi.com 或订阅 ObjectStore 开发邮件列表。(要订阅,请发送电子邮件至 ostore-request@qds.com,无需主题,并在邮件正文中写 subscribe。)
特别感谢 Quantitative Data Systems 的 Rob Murray 和 Superconducting Core Technologies 的 Craig Heckman 提供的精彩评论和帮助。
[ORF96] Robert Orfali、Dan Harkey 和 Jeri Edwards,《Essential Distributed Objects Survival Guide》,John Wiley & Sons, Inc.,第 164 页,1996 年。
[CAT92] R.G.G. Cattell 和 J. Skeen,《Object Operations Benchmark》,ACM Transactions on Database Systems,17(1):1-31,1992 年。
[LOO95] Mary E. S. Loomis,《Object Databases: The Essentials》,Addison-Wesley Publishing Company,第 197-200 页,1995 年。
[CAT96] R.G.G. Cattell,《The Object Database Standard: ODMG - 93》,Release 1.2,Morgan Kaufmann Publishers, Inc.,1996 年。
[SIN92] Vivek Singhal、Sheetal V. Kakkad 和 Paul R. Wilson,《Texas: An Efficient, Portable Persistent Store》,第五届国际持久对象系统研讨会,1992 年。对象数据库:不再仅仅适用于 CAD/CAM。
Greg Meinke (gmeinke@qds.com) 在 Quantitative Data Systems, Inc. 工作,从事使用 C++、CORBA 和 ObjectStore 数据库的分布式业务系统。