RSS、Atom、mashup、高级搜索要求和其他发展正使得原生 XML 数据库成为搜索应用程序和服务的一个重要组成部分。XML 数据库类型的优势在于擅长高效地在大量半结构化(semi-structured)的数据中进行搜索。在本文中,您将发现一些用于最大化使用 XQuery 和 XML 数据库的应用程序的性能的一般原则。
XQuery 和原生 XML 数据库
在某些情况下,在原生 XML 数据库系统中使用 XQuery(一种用于查询 XML 数据集合的函数型语言)可能非常有用。与标准关系数据库相比,原生 XML 数据库在服务于主要是只读的复杂查询时能够提供更快的响应时间和开发时间。XQuery 是目前最简单、最强大的数据转换系统,它完美地内置在查询语言中。借助 XQuery,可以实现更快的开发时间,因为无需设计一个单独的全文本索引系统,或者为用户组装大量数据。
以减慢插入和更新速度为代价,原生 XML 数据库能够提供无与伦比的开箱即用响应时间,因为它们保持数据基本上非规格化(denormalized),提供默认索引,并能极好地利用可用 RAM。但是,在处理超大型数据集时,您还可以通过遵循以下一般原则进一步改善原生 XML 数据库的查询响应时间。
1、避免规格化
2、采用唯一的元素名称
3、预先计算值
4、通过查询转换数据
5、剖析 XQuery 代码
6、保留优化列表
这些原则是通用的,适用于当今可用的许多原生 XML 数据库,包括 IBM DB2 Express-C、Mark Logic Server、eXist、甚至 Oracle Berkeley DB XML(参见 参考资料 中的链接)。接下来,我们将详细探讨这些优化原则。
避免规格化
设计原生 XML 数据库模式时,最重要的事情是避免使用设计关系数据库时采用的方法来规格化数据。
原生 XML 数据库的数据规格化过程涉及到设计多个 XML 文档类型,这些文档类型相互链接的方式与关系模型表相互链接的方式类似。但是,在多数情况下,需要尽可能少(如果有的话)地规格化原生 XML 数据库的数据。将原本应该驻留在几十个关系模型表中的数据存储到一个 XML 文档类型中的做法是十分常见的。
现今的大多数 XQuery 实现执行连结(join)操作的效率很低,即使是一个只涉及几千条记录的简单查询都需要耗费大量的、令人难以接受的处理时间。这就使得下面这条决定是否应该规格化数据的标准很明确:永远不要规格化数据,以免受支持的查询需要执行连结操作来选择记录。
受支持的查询是这样一种查询,即您可以合理地预期用户怎么对待您的数据。例如,如果构建一个用于销售录像带的应用程序,您可能预期用户会查询标题中包含某个关键字并且由某个导演执导的所有视频。因此,您肯定希望表示视频的 XML 文档包含视频标题和导演姓名。另一方面,对于这个特定的应用程序,您也许不希望支持用户查询标题中包含某个关键字并且由纽约出生的某位导演执导的所有视频。换句话说,对于这个视频应用程序示例,如果您拥有导演的详细信息(不仅仅是导演姓名),可以考虑将这些信息保存在一个单独的 XML 文档中。
使用以下两个互相链接的 XML 文档类型来描绘数据库:video-rec 和 director-rec,前者带有关于视频的信息,其中包含一个 director-rec 标识符;后者带有关于导演的信息。要查询标题中包含某个关键字且导演出生于纽约的记录,必须执行连结操作以选择记录。如前所述,也可以不支持这种类型的查询,因为这是一种更侧重于数据挖掘的查询,而不是大多数浏览在线视频商店的用户通常执行的查询类型。但是,除非您有具体原因需要将关于导演的详细信息移动到一个单独的文档类型中,否则应该将这些信息保存在 video-rec 文档中。
尽管在原生 XML 数据库中执行连结操作来选择记录总是低效的,但是在转换搜索结果中的数据时从多个 XML 文档提取数据通常是可取的。我此前描述的视频商店能够轻松高效地呈现包含导演出生地的结果,尽管获取这个地址需要从原始搜索结果之外的文档提取数据。以这种方法组装结果,所需的操作仅限于应用程序已经选择和计划显示的少数几条记录,与常规的搜索查询中连结多个文档类型所需的资源相比,这种方法的计算和内存需求可以忽略不计。
采用唯一的元素名称
唯一元素 总是指向一个 XML 文档中的同一元素。非唯一元素 可以出现在 XML 文档中的任意位置,必须前置一个路径才有意义。例如,如果一个 XML 文档包含 10 个类型完全不同的节点,每个节点都包含一个日期元素作为其子节点,那么日期元素就是非唯一元素名称。采用非唯一元素名称会影响您评估或剖析一些用于定位数据的 XQuery 或 XPath 替代方法。例如,非唯一元素能够阻止您正确评估执行较少索引查询的代码。此外,非唯一元素还会阻碍正在兴起的对面搜索结果(faceted search result)的支持。
以下小节提供各种优化的例子,您可以通过更改类似于 清单 1 中的文档的设计以便它使用唯一的元素名称,来支持这些优化。
清单 1. 带有非唯一元素名称的基础文档
#+BEGIN_SRC nxml-mode <class-info> <school>Lusher Elementary School</school> <grade>10</grade> <teachers> <teacher> <name> <first>Carol</first> <last>Osborne</last> </name> </teacher> <teacher> <name> <first>Dan</first> <last>Silver</last> </name> </teacher> </teachers> <students> <student> <name> <first>Barrie</first> <last>Stoff</last> </name> </student> <student> <name> <first>Andrew</first> <last>Silver</last> </name> </student> <student> <name> <first>Larry</first> <last>Cracchiolo</last> </name> </student> <student> <name> <first>Richard</first> <last>Hughes</last> </name> </student> <student> <name> <first>Bruce</first> <last>Silver</last> </name> </student> . . . </students> </class-info> #+END_SRC |
执行较少的索引查询
要查询姓 Silver 的学生的名,应该使用类似于 清单 2 中的 XPath 表达式。
清单 2. 查询姓 Silver 的学生的 XPath 表达式
: /class-info/students/student/name[last = "Silver"]/first |
如果将数据限制为 清单 1 中单个文档中的可见数据,那么计算 清单 2 中的 XPath 表达式总是正确地返回 清单 3 中的结果。
清单 3. XPath 结果
<first>Andrew</first> <first>Bruce</first> |
如果数据没有被索引,那么 清单 2 总是获得结果的最快方法。这个表达式限制了数据库找到相关结果所必须搜索的分支的数量。
但是,如果数据已经索引,并且根据您使用的特定数据库实现,假设您有一个非常大的数据集,那么类似于 清单 4 中的表达式可能计算速度更快。
清单 4. 数据已索引时使用的 XPath 表达式
: //name[last = "Silver"]/first |
性能可能会改进的原因是,系统只需检查索引中较少的元素。但是,鉴于 清单 1 中文档的设计(使用非唯一元素名称),清单 4 中的 XPath 表达式将返回错误的结果:包含一个教师的名字 Dan。这种设计阻止您编写利用较少索引的查询。更好的设计是使用唯一元素名称替换 清单 1 中的非唯一元素名称,如 清单 5 所示。
清单 5. 使用唯一元素名称替换清单 1 中的非唯一元素名称
//teacher/name => //teacher/teacher-name //teacher/name/first => //teacher/teacher-name/teacher-first //teacher/name/last => //teach/teacher-name/teacher-last //student/name => //student/student-name //student/name/first => //student/student-name/student-first //student/name/last => //student/student-name/student-last |
支持面搜索结果
面搜索的目标是显示一些链接,允许用户沿各种轴快速直观地缩小搜索的范围。在一个支持面搜索结果的应用程序中,一个列出数据库中所有教师的查询可能在用户界面中返回类似于 清单 6 中的信息。
清单 6. 面搜索
Tabor, Gavin Nance, Jamey Haas, Carlene Davies, Yesenia Singer, Lupe Narrow your search: School Lusher Elementary School (35) Academy of the Sacred Heart (34) Isidore Newman School (32) Audubon Charter School (28) Benjamin Franklin Elementary Math-Science Magnet (25) Grades 9 (5) 10 (6) 11 (6) 12 (6) |
清单 6 提供了两个面:School 和 Grades。每个面包含 4 到 5 个值,这些值链接到一个搜索,该搜索用于缩小最近的搜索的范围。每个面值旁边有一个数字(位于圆括号中),表示单击这个链接将会找到的教师总数。面搜索结果通常只显示每个面的几个可能值。如果一个面的确切值的数量很少,比如说 Grades 面,那么应用程序通常会显示所有面,并按照各个面的重要程度排序。但是,如果一个面包含许多可能值,那么应用程序通常只显示将返回最多结果的那些值,并根据结果数量按降序排列。
一些原生 XML 数据库正在包含对面搜索的支持,但是它们需要特殊的索引才能提供最佳性能。随着数据库中的记录数量的增加和一个面的可能值的数量增加,获取一个面的显示值的典型 XQuery 算法很快成为一个瓶颈。对于一个拥有多个包含数千个值的面的大型数据库来说,这样的算法是行不通的。要发挥面搜索的威力,原生 XML 引擎需要能够从一个元素在数据库中具有的值构建词典。这些词典可以从特殊的索引实现,而索引又需要唯一元素名称。
如果您拥有一个相对较小的不支持面搜索的原生 XML 数据库,并且需要自己编写代码来支持这种功能,那么您将明白,唯一元素名称对您的代码有多么重要,就跟它们对更高级的数据库中当前存在的面搜索支持代码一样重要。
预先计算值
将冗余数据添加到 XML 文档的想法对于老练的关系数据库管理员来说是不可思议的。但是,当您的主要关注点是性能时(例如,当您必须为查询几千万条记录的查询返回面搜索结果时),基于 XML 文档中的数据预先计算一些值并将结果添加到 XML 文档中有助于极大地改善响应时间。原生 XML 数据库都是以牺牲存储空间并容忍冗余为代价来换取性能的。
假定您拥有一些图像元数据 XML 文档。每个 XML 文档都有一个或多个以下元素:camera、device 和 scanner,这些元素都包含关于创建图像的设备的信息。device 元素表示一个复杂节点,它包含一个带有设备名称的元素和其他几个带有额外信息的元素。在本例中,所有这些 device 元素都需要在应用程序的其他部分使用,因此不能丢弃。这个应用程序实现面搜索并调用一个名为 scanning device 的面,该面显示创建图像的设备的名称。
类似地,这些图像元数据文档还具有高度和宽度元素,但是应用程序调用一个名为 size 的面,这个面可以从 height 和 width 元素轻松得到。
清单 7 是一个示例。
清单 7. 第一个图像元数据文档示例
<image> <id>123456789</id> <date>2009-11-16T03:14:42</date> <description>Eiffel Tower</description> <device> <device-name>Scanmelter 2000</device-name> <device-resolution>300dpi</device-resolution> <device-manufacturer>Scanners Inc.</device-manufacturer> <service-tag>ASDFQWER</service-tag> </device> <width& |