DB2 9 中 15 个 pureXML 性能最佳实践

DB2 9 引入了 pureXML 支持,这意味着 XML 数据将以其固有的层次格式被存储和查询。为了查询 XML 数据,DB2 提供了两种语言,SQL/XML 和 XQuery。此外,DB2 9 还具有成熟的 XML 索引功能和对 XML 模式验证的支持。虽然大多数已有的关于 DB2 性能的指南同样适用于 XML 数据,但是本文还将提供其他一些特定于 XML 的性能方面的提示。

Matthias Nicola (mnicola@us.ibm.com), DB2/XML Performance, IBM Silicon Valley Laboratory

Author photo: Matthias NicolaMatthias Nicola is a Senior Software Engineer for DB2 pureXML™ at IBM's Silicon Valley Lab. His work focuses on all aspects of XML in DB2, including XQuery, SQL/XML, storage, indexing and performance. Matthias also works closely with customers and business partners, assisting them in the design, implementation, and optimization of XML solutions. Prior to joining IBM, Matthias worked on data warehousing performance for Informix Software. He received his doctorate in computer science from the Technical University of Aachen, Germany.



2006 年 10 月 23 日

简介

DB2 9 中的 pureXML 支持为管理 XML 数据提供了有效的、多方面的功能。对于很多 XML 应用程序而言,性能是高度优先考虑的一个方面。DBA 和应用程序设计人员都可以通过他们份内的工作来确保良好的性能。首先,我们有关于 DB2 各个方面的所有传统的性能指南,包括 CPU/内存/磁盘配置的平衡、表空间和缓冲池的调优、锁、日志记录、查询执行计划等。所有这些话题在之前的 DB2 文章里都曾论述过(见 参考资料),并在管理 DB2 中的 XML 数据时仍然适用。

DB2 9 是同时支持关系型数据与 XML 数据的新一代数据服务器。请到 DB2 9 技术资源中心 获取更多的关于 DB2 9 的技术文章、教程、下载以及多媒体信息。

幸运的是,这些问题当中有很多问题是由 DB2 的自治存储和自调优内存管理等自治功能来处理的。它们为很多应用程序提供了高水平的性能,要求的手动干预很少。但是,具有更高性能需求的 XML 应用程序还可以从其他性能方面的考虑当中受益。本文集中讨论这方面的情形,同时给出为 DB2 9 中与 XML 相关的应用程序取得最佳性能的提示和指南。

本文将讨论和阐述 15 个 XML 性能提示(排序不分先后)。这 15 个提示涵盖了很多领域,但是经验表明,存在性能问题的应用程序通常只需要应用其中一两个提示就能达到所需的性能。

在对这些性能提示的讨论中,我们假设您熟悉基本的 DB2 管理和性能实践,并熟悉基本的 DB2s pureXML 支持。例如,您应该知道 XML 列、XML 索引,以及如何用 SQL/XML 和 XQuery 查询 XML 数据。所有这些先决条件在 developerWorks 上之前发表的文章中都有论述(见 参考资料)。


DB2 XML 性能提示

提示 1: 理智选择 XML 文档的粒度

当设计 XML 应用程序和 XML 文档结构时,您可能面临将哪些业务数据放在一个单独的 XML 文档中的选择。例如,在下面的部门表中,我们为每个部门使用一个 XML 文档(中等粒度)。如果部门是应用程序访问和处理数据所依赖的主要粒度,那么这是合理的选择。或者,我们也可以决定将多个部门组合到一个 XML 文档中,例如,将属于同一个单位的部门放入一个 XML 文档中(粗粒度)。然而,如果通常一次只处理一个部门,那么这种粒度带来的性能就是次优的。

表 1. 创建表 dept( unitID char(8), deptdoc xml)
unitIDdeptdoc
WWPR
<dept deptID='PR27'>
   <employee id='901'>
      <name>Jim Qu</name>
      <phone>408 555 1212</phone>
   </employee>
   <employee id='902'>
      <name>Peter Pan</name>
      <office>216</office>
   </employee>
</dept>
WWPR
<dept deptID='V15'>
   <employee id='673'>
      <name>Matt Foreman</name>
      <phone>416 891 7301</phone>
      <office>216</office>
   </employee>
   <description>This dept supports sales world wide</description>
</dept>
S-USE...
......

我们还可以决定为每个雇员使用一个 XML 文档(细粒度),并使用一个附加的 “dept” 属性,表明他或她属于哪个部门。如果雇员本身使用经常独立于同部门其他雇员被访问和处理的业务对象,那么这会是一个很好的选择。但是,如果应用程序常常一起处理一个部门中的所有雇员,那么每个部门一个文档会更好一些。

尤其是,我们不推荐将很多独立的业务对象成批地放入一个文档中。DB2 使用 XML 数据上的索引对文档进行过滤。因此,XML 文档的粒度越细,从基于索引的访问中得到的好处就越大。而且,如果应用程序使用 DOM 解析器来摄取从 DB2 检索的 XML,那么小型文档可以带来更好的性能。

与 XML 文档设计相关的一个常见的问题是,何时使用属性,何时使用元素,以及这里做出的选择对性能有怎样的影响。与其说这是一个性能问题,不如说这是一个数据建模问题。这个问题像 XML 的先驱 SGML 一样古老,而且曾经历过热烈的辩论,只是最后也没有广泛地达成一致。然而,与此有关的一个重要事实是,XML 元素比属性更灵活,因为 XML 元素可以重复和嵌套。例如,在我们的部门文档中,我们使用一个 “phone” 元素,如果一个雇员有多个电话号码,那么可以让这个元素出现多次。而且,当以后需要将电话号码拆成数段时,这个元素还是可扩展的。即 “phone” 元素可以有表示国家代码、区代码、分机号等等的子元素。如果 “phone” 是雇员元素的一个属性,那么对于每个雇员,它只能出现一次,我们也不能为之添加子元素,这将妨碍模式随时间而变化。虽然不用属性也可以对所有数据进行建模,但是对于预先知道不会重复(对于每个元素而言),也没有任何子字段的数据项,使用属性仍是最直观的选择。属性有助于使 XML 变得更短,因为属性只需一个标记,而元素则需要一对开始标记和结束标记。在 DB2 中,查询、谓词和索引定义中都可以使用属性,就像使用元素一样容易。由于属性的可扩展性不如元素,DB2 可以应用某些存储和访问优化。这应该看作是额外的性能收益,而不是将属性转换成元素的决定因素,尤其是当数据建模确实倾向于使用元素的时候更是如此。

总而言之,应该根据预期的访问的主要粒度来选择 XML 文档的粒度。如果仍然心存犹豫,那么倾向于较细的粒度和较小的 XML 文档要稳妥些。

提示 2:为了取得更好的 XML 性能,使用 DMS 和更大的页

数据库管理的表空间(DMS)可以比系统管理的表空间(SMS)提供更高的性能。 对于关系数据是如此,对于 XML 的读和写访问更是如此。在 DB2 9 中,缺省情况下,新创建的表空间是 DMS。 还建议将 DMS 表空间与自治存储一起使用,使 DMS 容器可以随需增长,而不需要人为干预。如果 XML 文档太大,一个表空间中的一个页面容纳不下,那么 DB2 就将该文档划分成多个区域,然后将它们分开存储到多个页面上。这一点对于应用程序而言是透明的,这也使得 DB2 可以处理 2 GB 的 XML 文档。

通常,每个文档划分成的区域越少,性能就越好,对于插入和全文搜索更是如此。如果一个文档大小超出了一个页面,每个文档分成的子块的数量取决于页宽(4KB、8KB、16KB 或 32KB)。表空间的页宽越大,每个文档划分成的子块数量就越小。例如,假设一个给定的文档要划分到 40 个 4KB 的页面上。那么,同样是这个文档,可以存储在 20 个 8KB 的页面上,或者存储在 10 个 16KB 的页面或 5 个 32KB 的页面上。如果 XML 文档远小于所选择的页宽,那么就不会浪费空间,因为多个小的文档可以存储在一个页面上。

经验法则是,为 XML 数据选择一个不小于平均预期文档大小的两倍而且不超过 32 KB 最大值的页宽。如果为关系数据和 XML 数据,或者为数据和索引使用单一的页宽,那么 32KB 的页宽对于 XML 数据而言可能有利,但是不利于关系数据和索引的访问。在这种情况下,16KB 或 8KB 的页宽对于两者而言都是较好的选择。

提示 3:必要时,如何将 XML 数据放入一个单独的表空间中

如果您做了性能分析,发现对于 XML 数据需要大的页宽,而对于关系数据或索引则需要小的页宽,那么可以使用不同的表空间来实现这一点。当定义一个表时,可以将 “long” 数据定向到一个具有不同页宽的单独的表空间中。long 数据包括 LOB 和 XML 数据。
下面的例子定义了两个缓冲池和两个表空间,一个表空间的页宽为 4KB,另一个表空间的页宽为 32KB。(注意,一个表空间总是要有一个缓冲池,且缓冲池要有一个匹配的页宽。)表 product 被分配到具有 4KB 页面的表空间 "relData"。它的所有列被存储在那个表空间中,只有 XML 列 "description" 存储在表空间 "xmldata" 中的 32KB 的页面上。

清单 1. 两个缓冲池和两个表空间的定义
create bufferpool bp4k pagesize 4k;
create bufferpool bp32k pagesize 32k;

create tablespace relData pagesize 4K managed by automatic storage bufferpool bp4k;
create tablespace xmlData pagesize 32K managed by automatic storage bufferpool bp32k;
create table product(pid bigint, name varchar(20), brand varchar(35), category integer, price decimal, description XML) in relData long in xmlData;

DB2 9 中的缺省表空间有所变化。除非显式指定,否则新创建的表空间都是具有较大行 ID 的 DMS。这意味着,一个具有 4KB 页面的表空间可以增长到 2TB,而不是 DB2 8 中的 64GB,具有 32KB 页面的表空间可以增长到 16TB,而不是 512GB。而且,每个页面 255 行的限制也去掉了,32KB 的页面上允许至多 2335 行。因此,每页的行数限制本身不再是为关系数据使用小型页面的理由。

总而言之,在为 XML 数据选择不同的表空间时,仍应按常理来考虑。更少的缓冲池和表空间,以及更少的页宽种类,可以导致更简单的物理数据库设计,从而更容易管理、维护和调优。因此,要避免引入多种页宽,除非您知道这样做确实可以带来性能好处。

提示 4:如何配置 DB2,以便快速地成块插入 XML 数据

DB2 9 支持两种将 XML 数据从文件系统转移到 DB2 表中的选项:插入和导入。从性能和调优的角度来看,这两种选项具有类似的特征,因为导入实用程序实际上是执行一系列的插入。不管是应用程序执行批量插入(可能通过并发插入线程),还是使用导入,下面的性能指南都适用:

  • 务必使用具有较大页宽的 DMS 表空间(见 提示 2),这是一个关键的先决条件。
  • 即使没有在要成批插入数据的表上定义任何索引,DB2 pureXML 存储机制仍然会透明地维护所谓的区域和路径索引,以便于进行高效的 XML 存储访问。因此,应提供足够的缓冲池空间来支持索引读操作。
  • 如果表上需要多个用户定义的 XML 索引,那么通常更好的做法是在批量插入之前定义它们,而不是事后才创建它们。在插入期间,每个 XML 文档只被处理一次,便可以为所有 XML索引生成索引条目。然而,如果发出多条 “create index” 语句,那么 XML 列中的所有文档将被访问多次。
  • “ALTER TABLE <tablename> APPEND ON” 为表启用追加(append)模式。新的数据被追加到表的末尾,而不是在已有的页面上搜索空闲空间。这可以提高批量插入的运行时性能。
  • 增加日志缓冲池大小(LOGBUFSZ)和日志文件大小(LOGFILSIZ)有助于提高插入性能。这一点对于 XML 插入尤其重要,因为每一行的数据量都比关系数据大得多。建议为日志使用快速的 I/O 设备。
  • 如果使用 “ALTER TABLE <tablename> ACTIVATE NOT LOGGED INITIALLY” (NLI),可以避免日志记录。然而要注意,如果有语句遭到失败,那么表将被标记为不可访问,而且必须被删除。这一点将妨碍生产系统中进行 NLI 的增量批量插入,但是对于空表的初始填充比较有用。
  • 在使用导入的情况下,如果为 COMMITCOUNT 参数采用一个较小的值,那么会不利于性能。每次提交 100 行或更多行比每次提交一行在性能上更好。也可以忽略 COMMITCOUNT 参数,让 DB2 按适当的频率进行提交。
  • 为了更好地利用多个 CPU 和磁盘,可以并发运行多个导入命令。但是应确保每个导入命令都是在它自己的数据库连接上运行的,并使用 “ALLOW WRITE ACCESS” 子句以避免表被锁定。为运行并发导入,不需要分割输入文件(DEL 文件)。每个导入命令可以读取一个输入文件的不同片段,因为导入命令允许指定 “SKIPCOUNT m ROWCOUNT n” 来读取输入文件中的 m+1 到 m+n 行。
  • 如果需要将非常多的小型 XML 文件从文件系统转移到一个 DB2 表中,那么将它们放在一个专用的、文件系统缓存被禁用的文件系统中有助于提高性能。由于每个文件只被读取和插入一次,因此缓存完全没有必要。在 AIX 上,用 -o cio 选项挂载这种文件系统被证明是有益的。

要获得更多关于插入性能的指南,请参阅文章 “Tips for improving INSERT performance in DB2 Universal Database”(见 参考资料)。

总而言之,传统的插入和日志记录性能调优对于 XML 插入和导入仍然适用。如果将 ALLOW WRITE ACCESS 子句添加到每个导入命令中,则可以运行并行导入会话。

提示 5:使用新的快照监视器元素检查 XML 性能

无论您是正在调查不同页宽的优点还是 XML 性能的其他方面,很可能都要像对于关系数据一样使用 DB2 快照监视器。您将发现,与用于数据和索引的已有的计数器相对应,DB2 9 为 XML 数据提供了新的缓冲池快照监视器元素。由于关系数据和索引存储在表空间内不同的存储对象中,因此它们具有不同的读和写计数器。DDB2 9 中的 pureXML 存储为 XML 数据引入了一种新的存储对象,即 XDA,它也有自己的缓冲池计数器。

下面的例子是快照监视器输出的一个片段。可以看到用于三种不同存储对象(即数据、索引和 XDA)各自的快照监视器元素。这样便于单独针对 XML 监视和分析缓冲和 I/O 活动,而与关系数据互不影响。与 XML 索引有关的任何活动都被包括在已有的索引计数器中。新的 XDA 计数器的解释与对应的关系计数器的解释相同。例如,如果 XDA 物理读与 XDA 逻辑读的比率较低,则表明 XML 数据的缓冲池命中率较高,这正符合期望。要了解关于缓冲池快照监视器元素的更多详细信息,请参阅 DB2 文档。

清单 2. 数据、索引和 XDA 存储对象的监视器输出
Buffer pool data logical reads             = 221759
Buffer pool data physical reads            = 48580
Buffer pool temporary data logical reads   = 10730
Buffer pool temporary data physical reads  = 0
Buffer pool data writes                    = 6
Asynchronous pool data page reads          = 0
Asynchronous pool data page writes         = 6

Buffer pool index logical reads            = 8340915
Buffer pool index physical reads           = 54517
Buffer pool temporary index logical reads  = 0
Buffer pool temporary index physical reads = 0
Buffer pool index writes                   = 0
Asynchronous pool index page reads         = 0
Asynchronous pool index page writes        = 0

Buffer pool xda logical reads              = 2533633
Buffer pool xda physical reads             = 189056
Buffer pool temporary xda logical reads    = 374243
Buffer pool temporary xda physical reads   = 0
Buffer pool xda writes  
                   = 0
Asynchronous pool xda page reads           = 97728
Asynchronous pool xda page writes          = 0
Asynchronous data read requests            = 0
Asynchronous index read requests           = 0
Asynchronous xda read requests             = 83528

总而言之, 快照监视器中新的 XDA 计数器反映了 XML 活动。它们对于了解 XML 数据的缓冲池、I/O 和临时空间的使用情况很有用。

提示 6:了解 XML 模式验证的开销

XML 模式可以定义一组 XML 文档所允许的结构、元素和属性、它们的数据类型、取值范围等。DB2 允许(可选地)根据 XML 模式验证 XML 文档。如果选择验证文档,那么通常是在插入时进行验证。这是为了达到两个目的。首先,通过验证可以确保插入到数据库中的数据符合模式定义,也就是说可以防止将 “垃圾数据(junk data)” 输入到表中。其次,模式验证将来自模式的类型注释添加到每个 XML 元素和属性,这些类型在 DB2 XML 存储中得到持久化。例如,如果一个 XML 模式定义 dept 表中的雇员 ID(如 提示 1 所示)为整数,并根据该模式对文档进行验证,那么 DB2 会记得,在每个文档中雇员 ID 具有类型 xs:integer。在查询运行时,尝试在雇员 ID 上执行字符串比较将遭到失败,并产生一个类型错误。

在 XML 解析期间,XML 模式验证是一个可选的活动。性能研究表明,如果启用模式验证,那么 XML 解析通常要更密集地使用 CPU。根据 XML 文档的结构和大小,尤其是根据使用的 XML 模式的大小和复杂性的不同,这一开销相差很大。例如,您可以发现,由于采用中等复杂程度的模式进行模式验证,对 CPU 的消耗增加了 50%。除非 XML 插入中 I/O 占极大一部分,否则增加的 CPU 消耗通常会导致插入的吞吐量下降。

判断应用程序是否需要更严格地对 XML 查询进行类型检查,以及检查 XML 模式的遵从性。例如,如果在将 XML 文档存储到数据库之前,使用一个应用服务器来接收、验证和处理 XML 文档,那么可能不需要在 DB2 中对文档进行验证。此时,您已经知道它们是有效的。在这种情况下,应避免进行模式验证,以提高插入性能。但是,如果 DB2 数据库从不信任的地方接收 XML 数据,而又需要在 DB2 上确保模式遵从性,那么就需要在这方面花费一些额外的 CPU 周期。

总而言之,为提高插入性能,如果没有必要,应避免在 DB2 中执行模式验证。

提示 7:在 XPath 表达式中,尽可能使用全限定路径

假设有一个包含 XML 列的表

create table customer(info XML);

要管理具有以下结构的 “customerinfo” 文档:

清单 3. 示例 XML 文档
<customerinfo Cid="1004">
    <name>Matt Foreman</name>
    <addr country="Canada">
          <street>1596 Baseline</street>
          <city>Toronto</city>
          <state>Ontario</state>
          <pcode>M3Z-5H9</pcode>
    </addr>
    <phone type="work">905-555-4789</phone>
    <phone type="home">416-555-3376</phone>
</customerinfo>

如果要检索客户的电话号码或他们所居住的城市,无论使用 XQuery 还是 SQL/XML,都有多种可能的路径表达式可获得该数据。通过 /customerinfo/phone//phone 都可以获得电话号码。同样,/customerinfo/addr/city/customerinfo/*/city 都返回城市。为了得到最佳的性能,使用全限定路径比使用 * 或 // 更可取,因为使用全限定路径可以使 DB2 直接导航到所需的元素,而忽略文档中不相关的部分。

换句话说,如果您知道所需的元素位于文档中的什么位置,那么以全限定路径的形式提供位置信息会比较有帮助。如果请求 //phone 而不是 /customerinfo/phone,那么就是在请求文档中任何地方的 phone 元素。这需要 DB2 向下导航到文档的 "addr" 子树中,在文档的任何级别上查找 phone 元素,而这本是可以避免的开销。

注意,* 和 // 还可能导致不需要的或期望之外的查询结果。例如,如果有些 “customerinfo” 文档还包含 “assistant” 信息,就像下面的文档一样。那么路径 //phone 将返回客户的电话号码助手的电话号码,而没有将它们区分开。从查询结果中无法知道是客户的电话号码还是助手的电话号码,甚至会把助手的电话号码当作客户的电话号码来处理。

清单 4. 文档中多个层次中的 phone 和 name 元素
<customerinfo Cid="1004">
    <name>Matt Foreman</name>
    <addr country="Canada">
          <street>1596 Baseline</street>
          <city>Toronto</city>
          <state>Ontario</state>
          <pcode>M3Z-5H9</pcode>
    </addr>
    <phone type="work">905-555-4789</phone>
    <phone type="home">416-555-3376</phone>
    <assistant>
          <name>Peter Smith</name>
          <phone type="home">416-555-3426</phone>
     </assistant>
</customerinfo>

总而言之,在路径表达式中避免使用 * 和 //,尽量使用全限定路径。

提示 8:定义倾斜的 XML 索引,并避免为任何东西都建索引

假设我们的查询常常根据客户姓名搜索 “customerinfo” 文档。客户姓名元素上的索引可以大大提高那些查询的性能。让我们来看看下面的例子:

清单 5. 利用索引为根据客户姓名搜索文档提供支持
create table customer(info XML);

create index custname1 on customer(info) 
generate key using xmlpattern '/customerinfo/name' as sql varchar(20);

create index custname2 on customer(info) 
generate key using xmlpattern '//name' as sql varchar(20);

select * from customer
where xmlexists('$i/customerinfo[name = "Matt Foreman"]' passing info as $i);

上面定义的两个索引都适合用于客户姓名上的 XMLEXISTS 谓词的计算。但是实际上,索引 custname2 比索引 custname1 更大一些,因为它不仅包含客户姓名的索引条目,而且包括助手姓名的索引条目。只是因为 XML 模式 //name 与文档中任何地方的 name 元素相匹配。但是,如果我们永远不需要根据助手姓名来进行搜索,那么就不需要为它们编索引。

对于读操作,索引 custname1 更小一些,因此可能带来更好的性能。对于插入、更新和删除操作,索引 custname1 只会引起用于客户姓名的维护开销,而索引 custname2 则需要用于客户助手姓名的索引维护。如果想得到最佳的插入/更新/删除性能,并且不需要根据助手姓名进行基于索引的访问,那么当然不想花费额外的代价。

另外,请考虑下面的 heavyIndex 索引,它 “为任何东西编索引”。它包含每个文本节点(即 XML 列中的每个 XML 文档中的每个叶子元素值)的索引条目。在插入/更新/删除操作期间,那样的索引维护起来非常消耗成本,因而通常不推荐使用这样的索引。惟一的例外是,具有较少写活动和不可预测的查询工作负载的应用程序,这种应用程序难于定义更明确的索引。

create index heavyIndex on customer(info) 
generate key using xmlpattern '//text()' as sql varchar(20);

总而言之,在定义 XML 索引时,应该尽可能精确一点,尽量避免使用 * 和 //。

提示 9:将文档过滤谓词放入 XMLEXISTS 中,而不是放入 XMLQUERY 中

让我们来考虑下面的表和数据:

create table customer(info XML);
表 2. customer 表中的三行数据
<customerinfo>
    <name>Matt Foreman</name>
    <phone>905-555-4789</phone>
</customerinfo>
<customerinfo>
    <name>Peter Jones</name>
    <phone>905-123-9065</phone>
</customerinfo>
<customerinfo>
    <name>Mary Poppins</name>
    <phone>905-890-0763</phone>
</customerinfo>

对于这个表,假设您想返回电话号码为 “905-555-4789” 的客户的姓名。 您可能禁不住想编写下面这样的查询:

select xmlquery('$i/customerinfo[phone = "905-555-4789"]/name' passing info as "i") 
from customer;

但是,这个查询并不是您想要的,原因有好几个:

  1. 它返回下面这样的结果集,其中的行数与表中的行数一样多。这是因为 SQL 语句没有 where 子句,因此不能排除任何行。

    <name>Matt Foreman</name>

    3 record(s) selected
  2. 对于表中与谓词不匹配的每一行,返回一个包含空的 XML 序列的行。这是因为 XMLQUERY 函数中的 XQuery 表达式每次应用于一行(文档),并不会从结果集中去掉一行,只是修改它的。那个 XQuery 产生的值,当谓词为 true 时为客户的 name 元素,否则为空的序列。这些空行在语义上是正确的(根据 SQL/XML 标准),如果按这种方式编写查询,则必须返回它们。
  3. 该查询的性能并不好。首先,不能使用 /customerinfo/phone 上的索引,因为查询不允许排除行。其次,返回很多空行使查询速度不必要地慢了下来。

为了解决性能问题 得到所需的输出,应该在 select 子句中使用 XMLQUERY 函数,只提取客户姓名,将应该排除行的搜索条件转移到 where 子句的 XMLEXISTS 谓词中。这将允许使用索引和对行进行过滤,还可以避免空结果行带来的开销。像下面这样编写查询:

select xmlquery('$i/customerinfo/name' passing info as "i") 
from customer
where xmlexists('$i/customerinfo[phone = "905-555-4789"]' passing info as "i")
<name>Matt Foreman</name>

1 record(s) selected

总而言之,XMLQUERY 函数中的谓词只应用于每个 XML 值当中,所以它们不会排除任何行。文档过滤和行过滤谓词应该放入到 XMLEXISTS 函数中。

提示 10:使用方括号 [ ] 来避免 XMLEXISTS 中的 Boolean 谓词

一种常见的错误是在 XMLEXISTS 函数中不使用方括号来编写前面的查询:

select xmlquery('$i/customerinfo/name' passing info as "i") 
from customer
where xmlexists('$i/customerinfo/phone = "905-555-4789"' passing info as "i")

这将产生以下结果:

<name>Matt Foreman</name>
<name>Peter Jones</name>
<name>Mary Poppins</name>

3 record(s) selected

XMLEXISTS 谓词中的表达式使 XMLEXISTS 总是为 true。因此,没有行被排除。这是因为,对于一个给定的行,只有当里面的 XQuery 表达式返回空序列时,XMLEXISTS 谓词才为 false。然而,如果不使用方括号,XQuery 表达式就是一个总是返回 Boolean 值的 Boolean 表达式,而不会返回空序列。注意,XMLEXISTS 只是检查一个值的存在,如果存在一个值,即使这个值碰巧为 Boolean 值 “false”,XMLEXISTS 也将返回 true。虽然这并不是您想实现的效果,但是根据 SQL/XML 标准,这的确是正确的行为。

同样,其影响是不能使用 phone 上的索引,因为没有行被排除,所以会收到大量不需要的行。而且,在使用两个或更多谓词时,不要犯同样的错误,例如在下面这个查询中:

清单 6. XMLEXISTS 中两个谓词的不恰当的使用
select xmlquery('$i/customerinfo/name' passing info as "i") 
from customer
where xmlexists('$i/customerinfo[phone = "905-555-4789"] and 
		 $i/customerinfo[name = "Matt Foreman"]' 
      passing info as "i")

这个查询使用了方括号,那么它错在哪里呢?XQuery 表达式仍然是一个 Boolean 表达式,因为它的形式是 “exp1 and exp2”。下面是编写这个查询的正确方式,这样编写查询可以过滤行,并允许使用索引:

清单 7. 可以过滤行并允许使用索引的正确查询
select xmlquery('$i/customerinfo/name' passing info as "i") 
from customer
where xmlexists('$i/customerinfo[phone = "905-555-4789" and name = "Matt Foreman"]' 
      passing info as "i")

总而言之,在 XMLEXISTS 中不要使用 Boolean 谓词。将谓词放在方括号中,包括任何 “and” 和 “or”。

提示 11:使用 RUNSTATS 收集 XML 数据和索引的统计信息

RUNSTATS 实用程序已经被扩展,现在可以收集关于 XML 数据和 XML 索引的统计信息。DB2 基于成本的优化器使用这些统计信息为 XQuery 和 SQL/XML 查询生成有效的执行计划。因此,像对待关系数据那样,继续使用 RUNSTATS。如果表包含关系数据和 XML 数据,而您又只想刷新关系数据的统计信息,那么可以带新的子句 “EXCLUDING XML COLUMNS” 来执行 RUNSTATS。如果没有这个子句,缺省的也是可取的行为是总是收集关系数据和 XML 数据的统计信息。

对于关系数据和 XML 数据,可以启用抽样(sampling)来减少执行 runstats 的时间。在一个大型的数据集上,10% 的数据(或者更少)的统计信息通常仍然非常具有代表性。无论选择多大的抽样百分比,runstats 允许对行(Bernoulli sampling)或页面(system sampling)进行抽样。行级的抽样读取所有数据页,但是只考虑每页上一定百分比的行。而页级的抽样则可以显著减少 I/O,因为它只读取一定百分比的数据页。因此,如果表中不仅包含 XML 数据,而且还包含相当数量的关系数据,页抽样可以显著提高性能。但是,如果关系数据值是高度聚集的,那么行级抽样可以产生更精确的统计信息。

下面是一些例子。第一个 runstats 命令为表 customer 和它的所有索引收集最全面、最详细的统计信息,而没有采用抽样。如果执行时间允许的话,这样做是理想的。第二个命令收集同样的统计信息,但是只收集 10% 的页面的统计信息。在很多情况下,这样做可以为优化器提供接近于第一个命令的精确性的统计信息,但是可以更快地返回结果。第三个命令抽取 15% 的行,但是不收集分布统计信息,并对索引也应用了抽样,这不同于第一个命令和第二个命令。

清单 8. 使用 RUNSTATS 收集统计信息
runstats on table myschema.customer 
with distribution on all columns and detailed indexes all;

runstats on table myschema.customer 
with distribution on all columns and detailed indexes all tablesample system (10);

runstats on table myschema.customer 
on all columns and sample detailed indexes all tablesample bernoulli (15);

总而言之,如果有可用的 XML 统计信息,DB2 可以生成更好的执行计划。像通常那样使用 runstats,或者利用抽样使用 runstats,以减少它的执行时间。

提示 12:如何使用 SQL/XML 发布视图将关系数据暴露为 XML

SQL/XML 发布函数允许将关系数据转换成 XML 格式。较好的做法是将 SQL/XML 发布函数藏在一个视图定义中,使应用程序和其他查询可以从视图中选择构造好的 XML 文档,而不必与发布函数本身打交道。

清单 9. 隐藏在视图中的 SQL/XML 发布函数
create table unit( unitID char(8), name char(20), manager varchar(20));

create view UnitView(unitID, name, unitdoc) as
   select unitID, name, 
          XMLELEMENT(NAME "Unit",
             XMLELEMENT(NAME "ID", u,unitID),
             XMLELEMENT(NAME "UnitName", u.name),
             XMLELEMENT(NAME "Mgr", u.manager)
                  )
   from unit u;

注意,我们在视图定义中包括了一些关系列。这并没有产生任何物理上的冗余,因为它只是一个视图,而不是物化的视图。暴露关系列有助于有效地查询这个视图。假设我们需要取一个对应于特定单位的 XML 文档。下面三个查询都可以实现这一点,但是第三个查询要好于前两个查询。

在前两个查询中,过滤谓词是在构造的 XML 上表达的。但是,XML 谓词不能应用于底层关系列或它的索引。因此,这些查询要求视图为所有单位构造 XML,然后选出对应于单位 “WWPR” 的 XML。这并非最佳方法。

以下查询可能取得次优性能:

清单 10. 次优的查询
select unitdoc
from UnitView
where xmlexists('$i/Unit[ID = "WWPR"]' passing unitdoc as "i");

for $u in db2-fn:xmlcolumn('UNITVIEW.UNITDOC')/UNIT 
where $u/ID = "WWPR"
return $u;

第三种方法使用一个关系谓词来确保只为 “WWPR” 构造 XML 文档,从而减少了运行时间,对于大型的数据集,这种方法带来的性能好处尤其显著。这个查询可以取得很好的性能:

清单 11. 性能较好的查询
select unitdoc
from UnitView
where UnitID = "WWPR";

总而言之,将关系列包括在 SQL/XML 发布视图中,在查询视图时,在那些关系列上表达谓词,而不是在构造的 XML 上表达谓词。

提示 13:如何使用 XMLTABLE 视图以关系格式暴露 XML 数据

创建一个视图以 XML 格式暴露关系数据,这样做很有用。同样,您可能也想使用一个视图将 XML 数据暴露为关系格式。提示 12 中的提醒也适用于这里的情况,只不过顺序要倒过来。让我们来看看下面的例子,其中使用了 SQL/XML 函数 XMLTABLE 以标量格式返回 XML 文档中的值:

清单 12. 以标量格式返回的 XML 文档中的值
create table customer(info XML);

create view myview(CustomerID, Name, Zip, Info) as 
SELECT T.*, info 
FROM customer, XMLTABLE ('$c/customerinfo' passing info as "c" 
   COLUMNS 
   "CID"     INTEGER      PATH './@Cid',
   "Name"    VARCHAR(30)  PATH './name',
   "Zip"     CHAR(12)     PATH './addr/pcode' ) as T;

注意,我们将 XML 列 info 包括在视图定义中,以便帮助有效地查询这个视图。假设您想基于给定的 ZIP 号码检索客户 ID 和姓名的一个标量列表。下面两个查询都可以实现这一点,但是第二个查询比第一个查询的性能要好。在第一个查询中,过滤谓词是在由 XMLTABLE 函数生成的 CHAR 列 “Zip” 上表达的。但是,关系谓词不能应用于底层的 XML 列或它的索引。因此,这个查询要求视图为所有 客户生成行,然后根据邮政编码 “95141” 选择符合条件的行。这不是最佳方法。第二个查询使用一个 XML 谓词来确保只生成对应于 “95141” 的行,从而减少了运行时间,对于大型的数据集,这种方法带来的性能好处尤其显著。

清单 13. 包含 XML 谓词的查询
-- may perform suboptimal:

select CustomerID, Name 
from myview
where Zip = "95141";


-- will perform well:

select CustomerID, Name
from myView
where xmlexists('$i/customerinfo[addr/pcode = "95141"]' passing info as "i");

如果定义视图所用的基表不仅包含一个 XML 列,还包含有索引的关系列,那么应该将那些关系列包含在视图定义中。如果对视图的查询包含关系列上的具有高度限制性的谓词,那么 DB2 使用关系索引来过滤符合条件的行,得到较小数量的行,然后在返回最终结果集之前,对这个中间结果应用 XMLTABLE 和其他谓词。

总而言之,在使用 XMLTABLE 视图将 XML 数据暴露为关系格式时要加以小心。应该尽可能在视图定义中包含其他的列,以便在那些列上表达过滤谓词,而不是在 XMLTABLE 列上表达过滤谓词。

提示 14:对于短小的查询或 OLTP 应用程序,使用带参数占位符的 SQL/XML 语句

非常短的数据库查询常常执行得很快,以至于编译和优化它们的时间占总体响应时间的很大比例。因此,只将它们编译(“准备”)一次,然后在每次执行时只传递谓词的字面值,这样做很有用。虽然 DB2 9 XQuery 不支持外部参数,但是 SQL/XML 函数 XMLQUERY、XMLTABLE 和 XMLEXISTS 却支持外部参数。这些函数允许传递 SQL 参数占位符,作为嵌入式 XQuery 表达式的一个变量。对于具有短小、重复的查询的应用程序,建议这样做。

清单 14. 硬编码的字面值
for $c in db2-fn:xmlcolumn('CUSTOMER.INFO')/customer
where $c/phone = "905-555-4789"
return $c;

select info 
from customer
where xmlexists('$i/customerinfo[phone = "905-555-4789"]' 
                passing info as "i")
清单 15. 使用参数占位符
select info 
from customer
where xmlexists('$i/customerinfo[phone = $p]' 
                passing info as "i", cast(? as varchar(12)) as "p")

总而言之,短小的查询和 OLTP 事务就像含参数占位符的预置语句一样,具有更快的速度。对于 XML,这要求 SQL/XML 将 SQL 型参数传递给 XQuery 表达式。

提示 15:避免 XML 插入和检索期间出现代码页转换

XML 不同于 DB2 中其他类型的数据,因为它可以在内部编码,也可以在外部编码。内部编码意味着 XML 数据的编码可以源于数据本身。外部编码意味着编码源于外部信息。用于与 DB2 交换 XML 数据的应用程序变量的数据类型决定了如何得到编码。如果应用程序为 XML 使用字符类型的变量,那么它就要在外部编码,即在应用程序代码页中编码。如果使用二进制应用程序数据类型,那么 XML 数据考虑采用内部编码。内部编码意味着编码是由 XML 文档本身包含的 Unicode Byte-Order mark(BOM)或编码声明决定的,例如

<?xml version="1.0" encoding="UTF-8" ?>

从性能的角度来看,我们的目标是尽可能避免代码页转换,因为它们要消耗额外的 CPU 周期。内部编码的 XML 数据比外部编码的数据更为可取,因为它可以防止不必要的代码页转换。这意味着,在应用程序中,应该优先选择二进制数据类型,而不是字符类型。例如,在 CLI 中,当使用 SQLBindParameter() 将参数占位符绑定到输入数据缓冲区时,应该使用 SQL_C_BINARY 数据缓冲区,而不是 SQL_C_CHAR、SQL_C_DBCHAR 或 SQL_C_WCHAR 数据缓冲区。当从 Java 应用程序中插入 XML 数据时,将 XML 数据读入为二进制流(setBinaryStream)比字符串(setString)更好。类似地,当 Java 应用程序从 DB2 接收 XML,并将它写到一个文件的时候,如果 XML 被写成非二进制数据,就会发生代码页转换。

当从 DB2 中将 XML 数据检索到应用程序中时,该数据被串行化。串行化是 XML 解析的逆向操作。这个过程将 DB2 的内部 XML 格式(解析后的树型表示)转换成应用程序能理解的原始的 XML 格式。在大多数情况下,最好是让 DB2 执行隐式的串行化。这意味着 SQL/XML 语句只需选择 XML 类型的值,而 DB2 尽可能高效地将其串行化为应用程序变量,如下面的例子所示:

清单 16. 含隐式串行化的查询
create table customer(info XML);

select info from customer where...;

select xmlquery('$i/customerinfo/name' passing info as "i") 
from customer
where...;

如果应用程序要处理非常大的 XML 文档,那么可以将 LOB 定位符用于数据检索。这就要求显式地串行化为 LOB 类型,更可取的是 BLOB 类型,因为串行化为字符类型(例如 CLOB)会带来编码问题和不必要的代码页转换。显式串行化使用 XMLSERIALIZE 函数:

select XMLSERIALIZE(info as BLOB(1M)) from customer where...;

总而言之,在应用程序中应该使用二进制数据类型来与 DB2 交换 XML,因为这样做可以避免不必要的代码页转换。要了解编码问题,如果有疑问,可以参考 DB2 9 文档中的详细指南。


结束语

为了在 DB2 中达到最佳 XML 性能,首先要从使用 DB2 的自治特性(例如自治存储和自调优内存管理)开始。这样可以为很多应用程序提供很好的、开箱即用的性能。这样做还为 DBA 节省了宝贵的时间,让他们可以将精力放在更有针对性的性能调优上。所有传统的 DB2 性能方面的知识仍然适用于 XML,下面列出的各种 developerWorks 文章都对它们做了论述。

在此基础上,本文中的 15 个提示可以在一些常见的与 XML 有关的性能方面为您提供帮助。如果需要改善 XML 应用程序的性能,不必应用所有这 15 个提示,其中往往只有一两个提示是与您的情况相关的。例如,如果由于表空间配置有问题,系统在 I/O 方面存在瓶颈,那么减少不必要的代码页转换仍然无济于事。类似地,如果实际上是需要执行 runstats 来支持更好的查询执行计划,那么使用 SQL/XML 参数占位符可能没有帮助。简而言之,本文中的提示可以帮助您避免 性能问题,但是要修复观察到的 性能问题,首先需要找出问题的根源和瓶颈。和对待关系数据一样,可以使用 DB2 中的标准诊断工具(例如 visual explain、db2exfmt 和快照监视器)来调查 XML 性能。


致谢

感谢 Cindy Saracco、Irina Kogan、Henrik Loeser、Nikolaj Richers 和 Marcus Roy,是他们对本文做了审校,并提供了各种帮助。

参考资料

学习

获得产品和技术

  • 访问 DB2 Snapshot Monitor,了解该工具的最新消息,并获得该产品。
  • 下载 DB2 Enterprise 9 的免费试用版。
  • 现在可以免费使用 DB2。可以下载 DB2 Express-C,这是为社区提供的 DB2 Express Edition 的免费版本,它具有与 DB2 Express Edtion 相同的核心数据特性,并为构建和部署应用程序提供了坚实的基础。
  • 下载 DB2 Developer Workbench,获得用于 DB2 的集成开发环境。

讨论

条评论

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, XML
ArticleID=170073
ArticleTitle=DB2 9 中 15 个 pureXML 性能最佳实践
publish-date=10232006