摘要
把继承层次映射到关系型数据库表有很多种做法。以下展示的模式是单种的映射方式,在实际中,你可以混合使用不同的映射方式。(2002-08-20 12:28:52)
By axing
继承映射模式
把继承层次映射到关系型数据库表有很多种做法。以下展示的模式是单种的映射方式,在实际中,你可以混合使用不同的映射方式。
以下的讨论没有涉及多重继承。在领域层中很少会遇到有多重继承的情况。大多数多重继承的使用都可以使用协议继承来实现,一个类从虚基类中继承多种协议。协议类很少有需要持久性的,因此我们只讨论简单的继承。
运行实例
我们选用了一个称为合伙人(partner)的系统中的一部分。一个群体(Party)由任何形式的人(person)组成,可以是自然人(natural person)或法人(institution)。客户和员工都是群体。对于员工,我们将之区分为专职员工(SalariedEmployee)和兼职员工(FreelanceEmployee)。见下图:
图中使用的属性比较少,现实中的系统中会有更多的属性,但是我们这里只是列出了可以表示我们所讨论的模式不同之处的属性。因此它们之中都没有涉及到读咱的属性或关系。并且我们假设以上层次中每一个类都不是虚基类,所有的五个类都可以生成实例。
模式: One Inheritance Tree One Table
摘要
该模式展示了一种方法,将一个完整的继承层次映射到单个的数据库表中。
问题
怎样把一个完整的继承层次映射到单个的数据库表中?
约束
除了文章一开始列出的通用约束,还包括了如下的约束:
- 多态读取、存储空间和写/更新性能的对比:在一个继承层次中,你需要支持这样的查询:在给定的条件下,查找所有匹配的Party对象。结果集还必须是多态的。在该例中,包括Employee、FreelanceEmployee或SalariedEmployee。这种方案虽然能够支持多态查询,但是既浪费空间,也影响了写/更新性能。
- 数据库的锁模式:有些数据库只是实现了页面锁,这样的话,你就需要注意数据库的流量,不要让一个表中的锁超出了限制的数量而影响性能。如果把过多的类映射到单个表中,很明显,你必须注意流量的问题。
- 继承层次的深度:某些方案适合于处理扁平性的继承层次,但难于处理纵深的继承层次。
- 维护负担:把单个的对象的数据跨越多个表来存放,这样的做法可以加快多态读取的速度,但缺点是增加了维护量,一旦对象需要新增或删除属性,模式的发展也需要考虑物理数据模型中数据的重复问题,这极易导致维护的噩梦。其它的维护实例还包括在一个继承层次中插入类和删除类。
- 用户自定义的查询:如果你希望给用户赋予自定义查询的权利,那么你需要从用户的角度考虑该映射是否能为用户所理解。
解决方案
把继承层次中的所有对象的属性全集对应到数据库的单个表中。每条记录中那些无用的字段使用Null值。
结构
解决方案示例
上例中的表设计如下图:
结论
- 写/更新性能:One Inheritance Tree One Table允许在单词的数据库操作中对任何基类的任何一个子类读取和写入。
- 多态读性能:象全部的基类的子类都可以在一张表中找到一样,多态读也很简单。唯一的难点就是为一条选中的记录构造正确的对象类型。处理这种情况的模式有很多,例如Abstract Interface[Col96]。
- 所占空间:就像你在上面的映射中所描述的,存储对象的属性会浪费一些空间,浪费空间的多少取决于继承层次的深度。层次越深,不同的属性越多,属性的全集就越大,也就越浪费空间。
- 通过多个表来均衡数据库负载:将过多的类映射到单个表中会导致低性能。可以在如下的数据库行为中发现这些问题的来源:
- 如果数据库使用页面级锁,一张表上的过量的传输量会大大降低数据库的访问速度。巧妙的安排簇可以抵消部分的开销。如果单个表上的传输量超过限制,性能将继续恶化,甚至引起死锁。
- 在单个表上太多的锁还会导致锁扩散。锁的数量是关系型数据库系统中锁扩散问题的罪魁祸首。
- 某些类需要次级索引来提高搜索速度。如果你在一个单独的数据库表中实现了很多类,相应的你也需要在表中加入索引。一张表上过多的索引会使得更新的时候需要同时更新所有的索引,降低了速度。
- 维护成本:当映射比较直接、比较简单的时候,只要继承的层次不会很多,模式的改进相对也会比较直接和简单。
- 特殊查询:由于映射和直观,因此一些特殊的查询也会相对较容易。
实现
- 考虑将所有的对象都映射到单个的表:你可以把所有的对象类型都放在单个表中--这将导致表上的大传输量。对于小型的应用系统类说,这不失为切实可行且灵活的方法。
- 空间的浪费:你需要检查关系数据库是否允许空值压缩。如果允许的话,那么这种映射方法无疑将更具吸引力,因为空值也不会浪费空间。类型标识:表中需要插入类型信息。可以在Synthetic Object Identities中包含类型信息。
相关模式
参看Representing Inheritance in a Relational Database [Bro+96]
模式:One Class One Table
摘要
该模式讨论了如何把一个继承层次中各个类映射到不同的数据库表中。
问题
如何把继承层次中的类映射到数据库表中?
约束
参见One Inheritance Tree One Table模式。
解决方案
将每个类的属性放到不同的表中。在每个表中插入一个Synthetic OID,将子类的行和父类所在表的行关联起来。
结构
解决方案示例
将前例中的类映射到数据库后产生五张表--每个类一张。单个的SalariedEmployee将存储在五张表其中的三张中:
结论
- 写/更新性能:该模式提供了非常灵活的映射机制,但缺乏一个较好的性能。考虑在上例中读取FreelanceEmployee的一个实例。它将会包括三次的数据库读操作:一次读FreelanceEmployee表, 一次读Employee表,一次读Party表。写同样需要三次操作,并更新一个或多个索引。在读写相关的任务上,这种映射方式相当费资源,这个代价还随着继承层次的增多而增大。
- 多态读性能:在我们的例子中,FreelanceEmployee的实例分别对应于不同表的Employee实例和Party实例。因此,多态读只需要读取一张表。这是该模式除去节约空间的优点之外吸引人的另一大优点。
- 所占空间:这种映射几乎有最佳的空间节约性。唯一的容易就是额外的synthetic OID,来连接不同的层级层次。
- 维护成本:由于这种映射比较直接,也易于了解,模式的改进也相应较为直接、容易。
- 特殊查询:由于映射需要访问多个表来获取一个对象实例的数据,特殊的查询比较难以组织,尤其是对于无经验的用户。
- 根表上的重负载:该模式将会导致根对象类型表上的重负载。在该例中,持有FreelanceEmployee表的写入锁的事务将同时持有对Party表和Employee表的写入锁。参看One Inheritance Tree One Table的结论部分对数据库表瓶颈的负面影响的讨论。
实现
- 虚类:注意虚类也需要映射到单独的表中。
- 类型标识:从前一个模式的例子中,我们假设Synthetic Object Identity已经包含了类型信息。为了从多态读取查询返回的结果中构建正确的类型,需要了解某些类型信息。
相关模式
参看Representing Inheritance in a Relational Database [Bro+96]。
模式:One Inheritance Path One Table
摘要
该模式显式了一种将继承路径中的所有属性放到单个的数据库表中的方法。
问题
如何把一个继承层次中的所有类映射到数据库的表中?
约束
和One Inheritance Tree One Table模式的约束相同。
解决方案
将每个类的属性映射到不同的表中。并在表中加入该类的父类的所有属性。
结构
解决方案示例
上例中映射的结果将会产生五张表--每个类一张。SalariedEmployee用其中的一张表就可以表示,SalariedEmployee的映射方法如下:
结论
- 写/更新性能:映射需要单次的数据库操作来完成对一个对象的读写。
- 多态读性能:本例中,对所有的Party对象的查询意味着要访问五张表。和One Class One Table模式移机One Inheritance Tree One Table模式比起来是不小的开销。
- 所占空间:这种映射提供了最佳的空间占用。没有任何的冗余属性,甚至连额外的synthetic OID都没有。
- 维护成本:产物一个新的子类意味着需要更新所有的多态查询。增加或删除父类的属性将会导致对所有子类的表结构的修改。
- 特殊查询:由于映射需要访问不只一张表来完成多态搜索,特殊的多态查询对无经验的用户来说难以编写。对叶结点类的查询将会非常的复杂。
实现
- 虚类:注意虚类不要映射到表。
- 类型标识:不需要在表中插入类型信息,因为可以通过表的名称来区分对象类型。因为Synthetic Object Identities包含了类型信息,可以考虑减少它的长度来节省部分的空间。
相关模式
参见Representing Inheritance in a Relational Database [Bro+96]。
模式:Objects in BLOBs
摘要
该模式展示了一种使用BLOB,将对象映射到单个数据库表的方法。该模式覆盖了继承、聚合和关联。
问题
怎样把对象映射到关系型数据库?
约束
参见One Inheritance Tree One Table模式。
解决方案
表包括两个字段:一个是synthetic OID,另一个是变长的BLOB,后者囊括了一个对象内的所有数据。使用流把对象的数据存放到BLOB中。
结构
解决方案示例
任何一种的设计方案都和上图相似。
结论
- 写/更新性能:Objects in BLOBs可以在单次的数据库操作中读取、写入基类的任何子类。注意,在很多RDBMS中,BLOBs都不是最快的访问数据的类型。
- 多态读:搜索类来获得属性是非常困难的。由于你没有办法访问BLOB的内部结构,因此就需要在数据库中注册函数,以便于访问属性。这种函数的实现可以参考[Loh+91]。定义和维护这些函数也需要付出客观的代价。
- 特殊查询:由于搜索类中的属性难以实现,因此特殊查询也同样难于表达。还必须要额外的定义函数。
- 所占空间:如果你的数据库可以实现变长的BLOBs,所占空间就是最小的。
- 维护成本:数据库模式的演进将会和面向对象数据库一样容易。
实现
- 相似实现的起源:Objects in BLOBs已用于原型的研究,该原型正尽可能的像OODBMS靠近,只不过它是使用一个关系型数据库作为存储管理。因此该模式的实现机会等于是在一个已存在的存储管理器的基础上实现OODBMS。
- 表间的数据库负载均衡:把过多的类映射到一个单独的表会造成低性能。具体的讨论可以参见One Inheritance Tree One Table模式的相关章节。
- 把OODB的特点和关系型数据库相结合:把这个模式同其它类的对象/关系映射相结合是切实可行的。在这个例子中,BLOB存储了复杂的对象的结构,类似于一个项目计划图一样。可以设置其它的字段来存储信息,以供那些访问已结构化的数据的特殊查询使用。(见图3,这种用BLOBs来存储对象的方式已用于SMRC原型研究[Rei+94, Rei+96]。 SMRC中的BLOBs不仅存储单个类的属性,还包含了一个对象网,这个对象网采用流方式存储在BLOBs中。流方式存储在图3中描述)。此外,除了我们上面介绍的纯粹的使用这种方法外,它还允许关系型数据和OODBMS共存。
图3:对象数据和关系型数据的共存。
相关模式
当只使用该模式的时候,这个模式类似于One Inheritance Tree One Table映射,参看Representing Inheritance in a Relational Database [Bro+96]。