XML性能最佳实践 Db2
通过遵循某些最佳实践,您可以帮助提高存储在 Db2 for z/OS® 中的XML数据的性能。
请谨慎选择XML文档的粒度
在设计XML应用程序时,尤其是设计XML文档结构时,您可以选择将哪些业务数据保存在同一个XML文档中。
例如,样本部门数据中的每个XML文档都包含一个部门的信息。
CREATE TABLE DEPT(UNITID CHAR(8), DEPTDOC XML);| unitID | deptdoc |
|---|---|
| WWPR | |
| WWPR | |
| S-USE | ... |
| ... | ... |
如果您的应用程序主要通过部门来访问和处理数据,那么这种中等粒度是一个合理的选择。 或者,您可以决定将多个部门合并为一个XML文档,例如属于一个单位的多个部门。 然而,如果您的应用程序通常一次只处理一个部门,这种粗糙的粒度就不是最佳选择。
您也可以为每位员工选择一份XML文档,并附加一个“部门”属性,以表明他或她所属的部门。 如果员工使用感兴趣的业务对象,这种精细粒度将是一个很好的选择,因为这些对象通常由同一部门中的其他员工独立访问和处理。 然而,如果应用程序通常将一个部门的所有员工放在一起处理,那么每个部门一个XML文档可能是更好的选择。
在XML中正确使用属性和元素
一个与XML文档设计相关的常见问题就是何时使用属性而不是元素,以及这种选择对性能有何影响。
这个问题与数据建模的关系比与性能的关系更大。 然而,一般来说,XML元素比属性更灵活,因为它们可以重复和嵌套。
例如,前例中显示的部门文档使用了“电话”元素,允许拥有多个号码的员工多次出现“电话”。 这种设计也可以扩展,以便日后将电话号码分解为多个部分,例如国家代码、区号、分机号等子元素。
相反,如果“电话”是员工元素的属性,则每个员工只能有一个电话,并且不能添加子元素。 这些限制可能会阻碍未来架构的演变。
虽然您可以在不使用属性的情况下对所有数据进行建模,但对于已知不会重复单个元素且没有子字段的数据项,属性是一个非常直观的选择。 属性可以稍微减小XML数据的大小,因为它们只有一个名称-值对,而元素则有两个,即开始标记和结束标记。
在 Db2 中,您可以像使用元素一样轻松地在查询、谓词和索引定义中使用属性。 由于属性比元素更难以扩展, Db2 可以应用特定的存储和访问优化。 然而,这些优点应被视为额外的性能奖励,而不是为了性能而将元素转换为属性的激励,特别是在数据建模需要元素时。
请注意XML模式验证的开销
XML模式验证是XML解析过程中的可选活动。 性能研究表明,如果启用模式验证,XML解析通常会显著增加CPU的占用率。
根据您的XML文档的结构和大小,尤其是所使用的XML Schema的大小和复杂程度,开销可能会发生巨大变化。 例如,使用中等复杂度的模式进行模式验证时,CPU消耗可能会增加50%。 除非您的XML插入大量依赖I/O,否则CPU消耗的增加通常会导致插入吞吐量降低。
XML模式定义了一组XML文档中允许的结构、元素和属性、数据类型以及值范围。 Db2 允许您根据XML模式验证XML文档。 如果您选择验证文件,通常会在插入时进行验证。 验证可确保插入数据库的数据符合模式定义,这意味着您可以提高输入表格的数据的完整性。
当您确定应用程序是否需要对XML查询和XML模式合规性进行更严格的类型检查时,请考虑对性能的影响。 例如,如果您正在使用一个应用程序服务器,该服务器在将XML文档存储到数据库之前接收、验证并处理这些文档,那么这些文档可能不需要在 Db2 中再次验证。 此时,您已经知道它们是有效的。 同样,如果数据库从受信任的应用程序(甚至可能是您自己控制的应用程序)接收XML文档,并且您知道XML数据始终有效,那么为了避免影响插入性能,请避免进行模式验证。 然而,如果您的 Db2 数据库从不可信的来源接收XML数据,并且您需要确保 Db2 级别的架构合规性,那么您需要为此花费一些额外的CPU周期。
有关更多信息,请参阅:
在XPath表达式中尽可能指定完整路径
当你知道元素在XML文档结构中的位置时,请以完全指定的路径形式提供该信息,以避免不必要的开销。
考虑一下由以下SQL语句创建的表。
CREATE TABLE customer(info XML);下图显示了信息栏中的示例数据。
<customerinfo Cid="1004">
<name>Matt Foreman</name>
<addr country="Canada">
<street>1596 Baseline</street>
<city>Toronto</city>
<state/>Ontario
<pcode>M3Z-5H9</pcode>
</addr>
<phone type="work">905-555-4789</phone>
<phone type="home">416-555-3376</phone>
</customerinfo>如果您想获取客户的电话号码或他们居住的城市,可以从多个可能的路径表达式中进行选择,以获取这些数据。
/customerinfo/phone 和 //phone 都可以为您提供电话号码。 同样, /customerinfo/addr/city 和 /customerinfo/*/city 都返回城市。 为了获得最佳性能,首选完全指定的路径,而不是使用 * 或 // ,因为完全指定的路径使 Db2 能够直接导航到元素,跳过文档中不相关的部分。 如果您要求使用 //phone 而不是 /customerinfo/phone ,那么您要求在文档中的任何位置添加电话元素。 这需要 Db2 向下导航到文档的“地址”子树,以查找文档中任何级别的电话元素。
使用 * 和 // 也可能导致意外的查询结果(例如,如果某些“客户信息”文档还包含“助理”信息,如下图所示)。 路径 //phone 将返回客户的电话和助理的电话号码,不区分两者。 从查询结果中,您可能会误将助手的电话号码当作客户的电话号码。
<customerinfo Cid="1004">
<name>Matt Foreman</name>
<addr country="Canada">
<street>1596 Baseline</street>
<city>Toronto</city>
<state/>Ontario
<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>为XML数据定义精简索引,以避免额外开销
假设查询经常按客户名称搜索“customerinfo”XML文档。 如下面的语句所示,客户名称索引可以大大提高此类查询的性能。
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 与文档中任何位置的名称元素匹配。 但是,如果我们从不按助手的名字搜索,那么我们就不需要为他们编制索引。
对于读取操作, custname1 索引较小,因此性能可能更好。 对于插入、更新和删除操作, custname1 索引仅对客户姓名进行索引维护,而 custname2 索引需要对客户和助理姓名进行索引维护。 如果您需要最大的插入、更新和删除性能,并且不需要基于助手名称的索引访问,那么您肯定不想支付额外的费用。
有关更多信息,请参阅:
在文档级别使用XMLEXISTS进行过滤谓词
请参考下表和示例数据:
CREATE TABLE customer(info XML);<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 Clark</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;然而,出于以下几个原因,您并不需要这样的查询:
- 它返回的结果集与表中行数相同。 这是因为SQL语句没有where子句,因此无法删除任何行。 结果如下图所示。
图5 前一个示例查询的结果 <name>Matt Foreman</name> 3 record(s) selected - 对于表中与谓词不匹配的每一行,将返回一行包含空XML序列的行。 这是因为XMLQUERY函数中的XQuery表达式每次只作用于一行或一个文档,并且不会从结果集中删除一行,只会修改其值。 如果谓词为真,则XQuery产生的值是客户的名字元素,否则为空序列。 根据SQL/XML标准,这些空行在语义上是正确的,如果查询语句按照所示形式编写,则必须返回这些空行。
- 查询性能不佳。 首先,/customerinfo/phone上可能存在的索引无法使用,因为此查询不允许删除任何行。 其次,返回许多空行会使查询速度变得缓慢。
要解决性能问题并获得所需的输出,请在选择子句中使用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")有关更多信息,请参阅:
使用方括号避免XMLEXISTS中的布尔谓词
一个常见的错误是在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 Clark</name>
3 record(s) selectedXMLEXISTS谓词的表达方式使得XMLEXISTS总是求值为true。 因此,没有行被删除。 对于给定的行,XMLEXISTS谓词仅当内部的XQuery表达式返回空序列时才评估为false。 然而,如果没有方括号,XQuery表达式就是一个布尔表达式,它总是返回布尔值,而绝不会返回空序列。 请注意,XMLEXISTS 真正检查的是值是否存在,如果存在,则返回 true,即使该值恰好是布尔值“false”。 根据SQL/XML标准,这种行为是正确的,尽管它可能不是您想要表达的意思。
结果还是一样,因为不会删除任何行,所以电话上的索引无法使用,而且您收到的行数比实际需要的多得多。 此外,在使用两个或多个谓词时,请注意不要犯同样的错误,如下面的查询所示。
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表达式仍然是一个布尔表达式,因为它的形式是“ exp1 和 exp2 ” 您可以按照以下查询示例编写查询语句,以筛选行并允许使用索引。
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")更多信息,请参阅逻辑表达式。
使用RUNSTATS收集XML数据和索引的统计信息
RUNSTATS实用程序已扩展为可收集XML表和索引的统计数据, Db2 优化器使用这些统计数据为XML查询生成高效的执行计划。 因此,请继续像处理关系数据一样,在XML表和索引上使用RUNSTATS。 您需要明确指定 XML 表空间名称,或使用 LISTDEF 包含 ALL 或 XML 对象,以获取 XML 表统计信息。
有关更多信息,请参阅:
使用SQL/XML发布视图将关系数据以XML格式公开
您可以在SQL/XML发布视图中包含关系列,并在查询该视图时,对这些列而不是对构建的XML表达任何谓词。
SQL/XML发布功能允许您将关系数据转换为XML格式。 在视图定义中隐藏SQL/XML发布功能可能是有益的。 应用程序或其他查询只需从视图中选择已构建的XML文档,而无需处理发布功能本身。 以下语句创建了一个包含隐藏的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;请注意,视图定义包括关系列。 这不会产生任何物理冗余,因为它只是一个视图,而不是一个实体化的视图。 显示关联列有助于高效查询此视图。
以下查询使用关系谓词来确保只构建“WWPR”的XML文档,从而缩短运行时间,特别是在处理大型数据集时。
SELECT unitdoc
FROM UnitView
WHERE unitID = "WWPR";使用XMLTABLE视图以关系格式显示XML数据
您可能还想使用视图以关系格式显示 XML 数据。 需要像以前一样谨慎,但方式相反。 在下面的示例中,SQL/XML函数XMLTABLE以表格格式从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列信息,有助于高效查询视图。 假设您想获取特定邮政编码的客户ID和姓名列表。 以下两个查询都可以做到这一点,但第二个查询的性能往往优于第一个。
在第一个查询中,过滤谓词在由XMLTABLE函数生成的CHAR列“Zip”上表示。 然而,并非所有关系谓词都能应用于基础XML列或索引。 因此,查询要求视图为所有客户生成行,然后从中挑选邮政编码为“95141”的客户。
SELECT CustomerID, Name
FROM myview
WHERE Zip = '95141';第二个查询使用XML谓词来确保只生成“95141”的行,从而缩短运行时间,特别是在处理大型数据集时。
SELECT CustomerID, Name
FROM myView
WHERE xmlexists('$i/customerinfo[addr/pcode = "95141"]' passing info as "i");
在短查询和OLTP应用程序中使用带有参数标记的SQL和XML语句
SQL/XML函数XMLQUERY、XMLTABLE和XMLEXISTS支持外部参数。
数据库查询时间通常很短,以至于编译和优化这些查询的时间占其总响应时间的大部分。 因此,您可能希望只编译或“准备”一次,每次执行时只传递谓词字面值。 建议在查询次数少且重复性强的应用中使用此技术。 以下查询显示了如何使用参数标记来获得与上述示例相同的结果。
SELECT info
FROM customer
WHERE xmlexists('$i/customerinfo[phone = $p]'
passing info as "i", cast(? as varchar(12)) as "p")在XML插入和检索过程中避免代码页转换
XML与 Db2 中的其他类型数据不同,因为它可以在内部和外部进行编码。 内部编码是指XML数据的编码可以从数据本身推导出来。 外部编码是指编码来自外部信息。
您用于与 Db2 交换XML数据的应用变量数据类型决定了编码的生成方式。 如果您的应用程序使用字符类型变量来表示XML,那么它就是外部编码的。 如果您使用二进制应用程序数据类型,则XML数据被视为内部编码。
内部编码是指编码由Unicode字节顺序标记(BOM)或XML文档本身的编码声明决定,例如: <?xml version="1.0"
encoding="UTF-8" ?>
从性能的角度来看,我们的目标是尽可能避免代码页转换,因为它们会消耗额外的CPU周期。 内部编码的XML数据优于外部编码的数据,因为它可以防止不必要的代码页转换。
这意味着在您的应用程序中,您应该优先选择二进制数据类型,而不是字符类型。 例如,在 ODBC 中,当您使用 SQLBindParameter() 将参数标记绑定到输入数据缓冲区时,应使用SQL_C_BINARY数据缓冲区,而不是 SQL_C_CHAR, SQL_C_DBCHAR 或 SQL_C_WCHAR。 在主机应用程序中,将XML AS BLOB作为主机变量类型。
从Java™应用程序插入XML数据时,以二进制流(setBinaryStream )读取XML数据比以字符串(setString )读取要好。同样,如果您的Java应用程序从 Db2 接收XML并将其写入文件,则如果XML以非二进制数据写入,则可能会发生代码页转换。
当您从 Db2 将XML数据检索到应用程序中时,数据会被序列化。 序列化是XML解析的反向操作。 这是 Db2 将内部XML格式(解析后的树状表示)转换为应用程序可理解的文本XML格式的过程。 在大多数情况下,最好让 Db2 执行隐式序列化。 这意味着您的SQL/XML语句只是简单地选择XML类型的值,如下例所示, Db2 会尽可能高效地将序列化结果放入您的应用程序变量中。
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...;使用XMLMODIFY语句更新XML文档的一部分
当您只需要修改XML文档的一部分时,可以使用XMLMODIFY函数来更高效地更改XML数据,而不是替换整个XML文档,尤其是对于大型XML文档。 对于小型XML文档,对于符合单个记录的文档,XMLMODIFY语句不会带来性能优势。
当应用程序不使用XMLMODIFY语句更新XML列时,XML列中的XML文档将被完全删除,并由新的XML文档替换。 当使用XMLMODIFY更新XML列时,只需要删除或替换XML表空间中由XMLMODIFY函数修改的行。
有关更多信息,请参阅:
使用 Extensible Dynamic Binary XML Db2 Client/Server Binary XML Format 输入解析的XML文档数据
XML数据的解析是影响XML数据INSERT、LOAD和UPDATE操作性能的最重要因素之一。 如果您在插入、更新或加载数据时使用 Extensible Dynamic Binary XML Db2 Client/Server Binary XML Format ,CPU 负载将降低。 Db2 DRDA zIIP 重定向不受二进制XML影响,但 z/OS XML系统服务 zIIP 会受到二进制XML的影响,因为不需要解析。 zAAP 会受到二进制XML的影响,因为不需要解析。
从客户端的Java应用程序发送 Extensible Dynamic Binary XML Db2 Client/Server Binary XML Format 数据,可以减少 Db2 服务器所需的CPU时间。 然而,将XML数据解析为二进制格式的工作由 IBM® Data Server Driver for JDBC and SQLJ 客户端上运行。 与发送文本XML相比,客户端的1类耗时可能会增加。 如果增加的运行时间不会影响您的环境,请使用二进制XML格式来减少 Db2 服务器的CPU时间。
在决定何时使用XPath或XQuery时,请考虑性能
一般来说,如果您使用XQuery和XPath执行类似操作,性能应该差不多。 然而,在某些情况下,XPath可能性能更好。
有关更多信息,请参阅:
请设置XML_RANDOMIZE_DOCID子系统参数,以获得最佳性能。
通过为包含XML列的表随机化DOCID值,可以减少插入XML数据的等待时间。 当按顺序插入文档标识符(DOCID)值时,可能会出现热点情况,即多个线程在同时插入XML数据时必须等待同一数据页上的锁存器。
但是,当XML_RANDOMIZE_DOCID子系统参数的值为YES时, Db2 会在CREATE TABLE时为任何包含XML列的新表随机生成DOCID值,并在ALTER TABLE时为任何现有表添加第一个XML列。 更改XML_RANDOMIZE_DOCID子系统参数的值对包含XML列的现有表没有影响。 任何包含随机DOCID值的表格都无法转换为使用顺序DOCID值的表格。 同样,任何已经包含顺序DOCID值的表都不能转换为使用随机DOCID值的表。
您可以在 SYSIBM.SYSSEQUENCES 目录表中查看ORDER列的值,以确定特定表格是否具有随机的DOCID值。
有关更多信息,请参阅:
使用FETCH WITH CONTINUE快速访问XML数据
使用FETCH WITH CONTINUE语句可以提高某些查询的性能,这些查询引用了未知或最大长度非常大的XML列。
使用二进制XML提高LOAD性能
当输入数据由UNLOAD实用程序使用BINARYXML选项创建时,可以减少XML数据的加载时间。 此外,如果XML数据之前已经过验证,且满足以下条件,则可以避免再次验证:
- 为正在加载的XML列定义了一个XML模式。
- 在加载XML文档的初始阶段,根元素命名空间和架构位置提示与XML架构的匹配。
- 根元素命名空间匹配,但 xsi:schemaLocation 不存在,或者第一个 xsi:schemaLocation 属性对不包含与根元素命名空间匹配的命名空间。
有关更多信息,请参阅: