这篇文章深入探讨了有关提升Microsoft® SQL Server™ 2005中XML数据类型的查询和修改操作性能的诸多问题。为了更好的理解本文,您最好事先熟悉SQL Server 2005中有关XML的相关特性。作为背景材料,您可以查阅MSDN当中的“Microsoft SQL Server 2005中对XML的支持”和“Microsoft SQL Server 2005的XML最佳实践”两篇文章。
目录
简介
应用XML数据类型的数据建模
XML数据的结构
应用特殊标记
以属性为中心的标记
类型化和非类型化的XML
属性提升
应用计算列
应用属性表
批量加载 XML 数据
应用OpenRowset
存储空间的考虑
索引XML数据
XML索引
部分XML更新
联合类型限制优化
禁用XML Index Selection
XML列上的全文检索
快照隔离和XML索引
查询和数据修改
为索引XML合并多个value()方法的执行
用exist()方法检验存在
将 nodes() 和 value() 方法组合使用
优化XML blob
添加多个tempDB文件使XML变量和参数更加可伸缩
消除到XML数据类型的额外转换
指定唯一元素
消除XML数据类型方法的多次执行
Data(), text(), 和string() 访问器
非类型化XML的文本聚合
参数化XQuery和XML DML表达式
优化谓词和序数
将序号移到路径表达式的末尾
应用上下文节点计算谓词
范围条件
父轴
动态查询
从XML数据产生行集
小结
简介
在企业级的应用中,越来越多地在使用XML来进行半结构化和非结构化数据的建模。为了帮助这种应用程序的开发,Microsoft® SQL Server™ 2005 对于XML的数据处理提供了广泛的支持。XML数据能够存储在一个原生XML数据列中,而这种数据列可以由一些XML架构来结构化,或者保持非类型化。细粒度的数据操作可以通过XQuery来实现,其中XQuery是近来W3C在Last Call中所建议的一种XML数据修改语言。XML数据列可以通过索引来提高查询的性能。在企业级的应用中,越来越多地在使用XML来进行半结构化数据和非结构化数据的建模,并将从SQL Server 2005的XML的支持中受益匪浅。
本篇文章提出了一些建议,用来优化使用XML数据类型的应用程序的数据存储、查询和修改。这些概念将通过一些代码示例来阐述。有关讨论XML数据建模和使用的最佳实践,请查阅MSDN上的相关白皮书“XML Best Practices for Microsoft SQL Server 2005”。有关通过映射来实现XML视图技术的优化的相关信息,查阅MSDN文档库中“Optimizing SQLXML Performance”。
在这篇文章中,首先要考虑的问题是用XML来进行数据建模的指导原则,包括数据库设计的原则,然后讨论优化应用程序性能时进行查询和数据修改的几点指导原则。
应用XML数据类型的数据建模
XML数据类型提供了企业中半结构化和非结构化数据的数据建模的能力。XML存储和查询处理的性能取决于数据库的架构设计,包括XML数据的结构和粒度、XML数据列的属性提升等因素。
首先,我们应该做出的判断是一个应用程序是否需要XML数据建模。对于结构化的数据,由于其关联性,它可以进行在列相关的表中存储,实现最佳的建模。而对于半结构化的或是标记型的数据,虽然有时这种数据有可能需要保留其文档的顺序和容纳层次,并可能含有递归的结构,它仍然可以很好的进行XML的数据建模。
有时,在XML数据类型列中存储结构化的数据是有益的,比如数据的结构是可变的或事先不知道数据的结构。
在属性管理中,当对象的原数据信息被建模为XML或被存储在XML数据类型列中时,可能发生这种情况。对象的不同类型的属性,或不同结构和内容模型的对象,都能够存储在相同的XML列中并可以进行查询。被查询的属性通常被提升到同一个数据表或不同的数据表的列中,这些被提升的属性同样可以索引和查询,并且这样的查询执行计划比直接查询XML列要更为简单。
另外,输入的XML数据可以被分解到表中并用SQL语句来进行查询。如果生成XML是查询工作中的有效部分,那么比较好的方法是在XML数据类型列中为XML数据存储一个冗余的副本。其中的冗余的副本避免了运行时XML的生成所带来的消耗。
对于XML数据类型的建模,并没有一个绝对正确的规则,但当遇到这种建模的情况时,必须要仔细斟酌正反两方面的因素。同等重要的事情是在类型化和非类型化XML列之间的选择,并且XML标记的方式也被引入了数据中。这些问题以及其它的问题将在本节的其余部分进行讨论。
XML数据的结构
同样的数据可以用不同的方式标记(以元素为中心,以属性为中心,和二者综合)。如何选择,就要依据对构成内容的元素值和构成元信息的属性值的理解,以及标记的基数。以其中一种方式将XML标记引入半结构化和非结构化的数据,比其他的方式在存储和查询处理上要更加有效。
应用特殊标记
有时,用通用的元素名称并用附加的属性来区分不同的类型确实很方便。但是这样查询的效率并不高,因为XML索引检查不能有效的完成。关于索引XML数据的更多信息,请查阅本文后面的Indexing XML Data部分。
特别地,语意丰富的元素名称一方面可以提高标记的可读性并帮助生成更有效的查询执行计划。但另一方面,大量的标记增加了存储的消耗。下面的示例说明了这一点。
示例:通用标记和特殊标记
假设您用XML来标记图书和DVD的信息。一个选择是用通用标记<item>,并令其属性@type可选值为 “book” 和 “DVD” 来区分两种不同的物品。这样,图书和DVD就可以用下面的方式表示:
<item type=”book”><title>Writing Secure Code</title></item>
<item type=”DVD”><title>The Godfather</title></item>
图书和DVD的路径表达式就可以分别写成/item[@type = “book”]和/item[@type = “DVD”]。
另一方面,用<book>和<DVD>作为标记将更加的直观:
<book><title>Writing Secure Code</title></book>
<DVD><title>The Godfather</title></DVD>
这种表示方法生成的路径表达式更加的简单为/book和/DVD。查询执行计划也更简单和高效,因为原先的属性@type已经被删去。
此外,用这种表示方法还削减了非类型化XML的主索引的行数,其行数从4行(第1行是<item>,第2行是@type和它的值,第3行是<title>,第4行是title的值)变成了3行(第1行是<book>或是<DVD>,第2行是<title>,第3行是title的值)。关于索引XML数据的更多信息,请查阅本文后面的Indexing XML Data部分。
对于类型化的XML,<title>是一个单值元素,而<title>元素的值存储在元素的同一行中。这样,存储的开销将从3行减少到2行,达到了节省资源的目的。
以属性为中心的标记
不论是类型化XML,还是非类型化XML,属性值都是存储在主XML索引的同一行中的属性标记中。相比较而言,非类型化XML中的单值元素的值与元素标记存储在不同的行中。因此,在非类型化XML中,应用属性值可以节省存储。
此外,因为属性值从主XML索引的属性标记的同一行获得,所以谓词计算更加有效。这样就不需要去访问其它行的值。下面的示例将会说明。
示例:以属性为中心的标记
在上面的示例中,title可以被建模为一个属性,而不是元素,如下:
<book title="Writing Secure Code"/>
<DVD title="The Godfather"/>
对于非类型化XML,这样可以把主XML索引从3行(分别是<book>,<title>和title的值)削减到2行(1行是<book>,另一行是属性@title)。DVD也是如此。
对于以元素为中心的标记title来说,路径表达式/DVD[title = "The Godfather"] 查找title为 “The Godfather” 的DVD。而对于以属性为中心的标记来说,相同功能的路径表达式为 /DVD[@title = "The Godfather"] ,而且不需要联接。
类型化和非类型化的XML
非类型化XML中的元素和属性的值(XML数据没有被XML架构所描述)被存储为Unicode字符串。对它们的操作需要进行适当的类型转换。比如,当给出路径表达式 (/book/price)[1] > 19.99 ,字符串值<price> 就通过数值转换而被转换成decimal类型。大量的这种转换会相当消耗资源。
通过XML架构所提供的类型信息被数据库引擎在很多地方应用。插入和更新的XML数据被检验与XML架构的一致性,并以二进制的方式(XML blob)存储。元素和属性值在XML实例中被存储为类型化的值。这样解析XML blob就比相应的文本形式更加的高效。类型化的值在XML索引中存储,并且只要没有数据类型转换就可以进行索引。编译查询时,通过类型信息来检查XQuery表达式和数据修改语句的静态类型的正确性。类型不匹配的错误在编译时就可以被检测到,并可以用显示类型转换来避免这种错误。
基于类型推理的查询优化也可以实现,比如<price>和<book>的类型是xs:decimal,那么(/book/price)[1]到xs:decimal的类型转换就被省去了。这样对XML索引查找有着积极的影响。一个范围谓词,比如(/book/price)[1] < 19.99,它在基于值的辅助XML索引上执行了范围的检查。关于索引XML数据的更多信息,请查阅后面的Indexing XML Data。
非类型化XML的数据转换防止了这种类型检查。此外,如果XML架构信息指定了只有一个<price>元素,并且在每个XML实例中只允许包含一个<book>元素,那么在(/book/price)[1]中的序号[1]就是多余的了。
类型化XML在XML数据插入和修改过程中需要进行有效性验证。验证所带来的开销可能会非常大,并且由架构定义的复杂性和XML数据中出现的标记个数等多种因素来决定。
属性提升
在处理查询的过程中,结构信息(如文档顺序和容器层次)在XML实例中被保留。从而,查询执行计划将会十分复杂。将XML列中的标量值提升到相同或不同数据表的关系列中,并直接根据这些关系列来写查询,可以简化一些查询的执行计划。当然,也可以将这些被提升的列编入索引。属性值经过实例化并编入索引,比直接在XML列上应用XQuery查询要更加高效,同样,这些预先计算的值加快了查询的速度。
当属性值被检索或者属性值作为检索相应XML对象的筛选条件时,属性提升能够提高性能。另外,当属性值作为检索相应XML对象的筛选条件时,属性值的选择是一个重要的因素。
单值属性能够被提升到同一个表的列中,并作为计算列。单值和多值的属性都能够被提升到不同表的列中,并用触发器来维护。这两种提升属性的方法在下面的部分进行讨论。
应用计算列
首先,用XML数据类型的方法来创建一个 T-SQL 用户定义函数 (UDF)来提取标量值。然后,通过这个用户定义函数来定义一个计算列,并添加到表中。通过这两步就可以提升属性,如果需要的话,还可以在这些关系列上建立索引。
之前基于XML列的XQuery表达式就要用基于计算列的SQL语句来重写,并且XML实例可以通过行的匹配来检索。查询优化器会根据查询的消耗来选择计算列上的索引。被提升的列是经过预编译的,因此它比直接在XML列中的查询更快。
如果计算列只是出现在SELECT列表中,并且不需要进行谓词计算,那么就没有必要再该列上建立索引。在这种情况下,数据列的持续性足够帮助提高性能。当计算列被编入索引时,如果计算列的表达式是非确定的,那么就需要数据列是持续的。
示例:应用计算列实现属性提升
假设您的工作量大多是根据已知的ISBN号码来查询相应的图书,那么此时就应该把ISBN号码提升到计算列中。定义一个用户定义函数来检索ISBN号码,如下:
CREATE FUNCTION udf_get_book_ISBN (@xData xml) RETURNS varchar(20)
WITH SCHEMABINDING
BEGIN
RETURN @xData.value('(/book/@ISBN)[1]', 'varchar(20)')
END
向表docs中添加一个计算列ISBN:
CREATE TABLE docs (id int PRIMARY KEY, xCol XML)
ALTER TABLE docs ADD ISBN AS dbo.udf_get_book_ISBN(xCol)
在ISBN列上创建一个非聚集索引:
CREATE INDEX COMPUTED_IDX ON docs (ISBN)
改写您的查询语句为:
SELECT xCol
FROM docs
WHERE xCol.exist ('/book/@ISBN[. = "0-2016-3361-2"]') = 1
应用计算列,如下:
SELECT xCol
FROM docs
WHERE ISBN = '0-2016-3361-2'
由于被提取出的ISBN值经过了预编译,所以改写后的查询的执行计划更加简单。
下面的示例阐述了如何应用计算列来实现属性提升。
应用属性表
为了维护单独的属性表,需要分别建立插入、删除和更新的触发器。这种方式适合于多值属性,其属性表的每一行存储了一个属性值(unpivoted表示)。关于创建和维护属性表的示例可以在XML Best Practices for Microsoft SQL Server 2005中找到。
如果同辈节点间的相互顺序对于应用程序来说是有用的,那么就需要在属性表中建立一个序号列。可是,属性表的维护(如XML子树的插入和删除操作)将十分复杂。
为了方便,单值属性列可以添加到表中。它虽然增加了列的冗余度,但是当两个属性都需要时会避免联接。
如果被提升的属性的最大基数N很小并且预先已经知道,那么最好是创建N个计算列并由查询处理器来维护这些列,而不是创建一个单独的属性表。
批量加载 XML 数据
可以通过SQL Server批量加载的功能,将XML数据批量加载到数据列中,包括BCP IN,BULK INSERT和OPENROWSET方法。
BCP输入已经被优化,避免了一些XML数据的复制拷贝。因此,如果在XML列上没有(行或列)约束,BCP在上面提到的三种方法中是最佳的。
应用OpenRowset
从文件向XML列、变量和参数中加载数据,应用OPENROWSET是一个方便的方法。但在变量或参数中多次查询XML数据可能会导致多次从文件中检索数据。所以,最好先把XML数据读到一个变量中,然后去查询这个变量。如下例所示:
示例:查询OPENROWSET的输出
在下面的查询中,XML数据从文件中读到表表达式XmlFile的列[Contents]中。nodes() 方法找到XML实例的<author>元素。每个value()方法都计算一个相对于<author>元素的路径表达式,从而每次从文件中加载XML数据。
WITH XmlFile ([Contents]) AS (
SELECT CONVERT (XML, [BulkColumn])
FROM OPENROWSET (BULK N'C:\temp\Filedata.xml', SINGLE_BLOB) AS [XmlData]
)
SELECT nref.value('first-name[1]', 'nvarchar(32)') FirstName,
nref.value('last-name[1]', 'nvarchar(32)') LastName
FROM [XmlFile] CROSS APPLY [Contents].nodes('//author') AS p(nref)
通过修改上面的查询语句,文件数据可以被一次性加载,这样获得了更好的性能。文件的内容一次就读到了XML变量@xmlData中,然后再SELECT语句中被重复的使用:
DECLARE @xmlData XML;
SELECT @xmlData = CONVERT (XML, [BulkColumn])
FROM OPENROWSET (BULK N'C:\temp\Filedata.xml', SINGLE_BLOB) AS [XmlData];
SELECT nref.value('first-name[1]', 'nvarchar(32)') FirstName,
nref.value('last-name[1]', 'nvarchar(32)') LastName
FROM @xmlData.nodes ('//author') AS p(nref)
存储空间的考虑
在主XML索引存在的情况下,向XML列中批量加载数据并插入一个非常大的XML实例会在事务日志中占用大量的空间。这个问题可以通过延迟XML列上XML索引的创建并用SIMPLE恢复模型来避免,如下所示:
1. 运行下面的命令(其中<database_name>是XML数据将加载入的数据库的名字):
ALTER DATABASE <database_name> SET RECOVERY SIMPLE
2. 在新表或存在的表中创建XML列,但是不要建索引。
3. 将XML数据插入到XML列中。
4. 在XML列上创建XML索引。
5. 运行下面的命令重置恢复模型为FULL:
ALTER DATABASE <database_name> SET RECOVERY FULL
另外,您可以用BCP来加载XML数据到数据库中,并用BULK_LOGGED恢复模型来代替SIMPLE。关于SIMPLE和BULK_LOGGED恢复模型的更多信息,请查阅SQL Server 2005联机丛书。
在两种恢复模式中,如果数据文件自最后一次备份之后就已经损坏,那么数据库必须要从那个备份恢复并重做所有的操作。
在这两种情况中,在插入XML数据前预分配数据文件比动态的增长数据文件执行的更快。
索引XML数据
XML索引
¬对于XML列的细粒度的查询,建议在XML列上创建主XML索引。主XML索引能在类型化和非类型化的XML列上创建,并且能够索引整个XML列的所有路径和值。主XML索引基于XML列中XML实例的shredded表示创建了一棵B+树。这棵B+树在XML列的XML BLOB之外创建,它的大小比XML列中的XML blob的总大小要大。应用这棵B+树,通过XML数据类型的方法来查询XML数据。当整个XML blob从基表中被检索时,XML blob用来做优化,比如SELECT * FROM * docs这样的情况。这样比从主XML索引中序列化XML内容要快,因为它的容量小且序列化需要很多消耗。
辅助XML索引为优化查询提供了更进一步的选择,能够生成更好的查询计划。通过使用PATH,PROPERTY,和VALUE类型的辅助XML索引,您的应用程序能够得到更进一步的提升。
• 当路径表达式(如/book[@ISBN = "0-2016-3361-2"])在XML数据类型上出现时,PATH索引相当有用。路径表达式越长,受益就越明显。PATH索引提供的是全面的提升。
• 当一个XML实例的多个属性在SELECT语句中被检索时,PROPERTY索引非常有用。聚集每个XML实例的属性,会得到更好的性能。
• 对于含有子代轴(/-operator)和通配符(/book[@* = "novel"])的路径表达式而言,使用VALUE索引非常的有帮助。
为了决定一个或几个辅助XML索引是否有用,需要分析查询工作量。要计算索引XML数据的总收益,维护索引需要的开销也要考虑在内。
很多应用程序已知其预期的工作量,并且只需要基于路径的索引。这些路径可以被提升为属性,像Property promotion 一节所讨论的。
部分XML更新
在细粒度查询过程中,XML数据类型的原地更新会带来重大的性能改善。新的(数据修改之后)和老的(数据修改之前)状态被计算并应用到XML列存储中,主XML索引也是一样。主XML索引的变化同样也传递到了辅助XML索引中。性能的收益来源于少量的数据更新和相应记录事务日志的节省。这些节省在大多数情况下能够补偿比较新老状态所带来的消耗。
最好的场景是用XML DML中的“replace value of”语句来修改一个元素的一个属性值。这需要更新一行中的XML列上的主XML索引和辅助XML索引。更新操作要到包含更新的属性或元素的XML blob的磁盘页执行。当然,用一个大值来替换老值会引起新的磁盘页面的写入。下面是一个有效更新的示例。
示例:更新属性值
如下方式修改<book>的<price>的值,执行XML实例和XML索引的原地更新:
UPDATE docs
SET xCol.modify ('replace value of (/book/price/text())[1] with 29.99')
对于插入一个属性、元素或者子树来说,新插入的节点和它后面的同辈节点,和它们的子树一起被更新或插入。相似的改变发生在XML blob中。节点删除的情况也非常相似,只是删除点之前的同辈节点被更新。
最坏的场景,发生在插入节点恰好是XML数据类型实例的最左边的片断时,或者在向根元素插入最左边的子节点时。这时,将更新整个XML实例。这种情况可以通过插入XML实例的最右片断的节点或向根元素插入最右子节点来避免。
删除最左边的片断或删除根元素最左边的子节点也有相似的消耗。如果一个元素被经常的插入和删除,那么最好作为最右边的片断或根元素最右子节点来插入。下面的示例说明了这种情况。
示例:代价高的更新
元素<publisher>作为<book>元素的最左子节点插入,并引起了<book>的所有子元素的更新。
UPDATE docs
SET xCol.modify ('
insert <publisher>Microsoft Press</publisher>
before (/book/title)[1]')
将<publisher>作为<book>的最右子节点插入,更加有效:
UPDATE docs
SET xCol.modify ('
insert <publisher>Microsoft Press</publisher> into (/book)[1]')
XML架构约束可能决定了插入点,插入最右边的允许的点将得到最好的性能。
联合类型限制优化
需要隐式转换的联合类型的值,阻止了基于值的辅助XML索引的检查,即使辅助XML索引可以用来匹配路径。因此,它也阻止了基于值的辅助XML索引的范围检查。更多信息,请查阅本文的Range Conditions部分。相同的推理也适用于<xs:anyAttribute>。
模型组(<xs:choice> 和 <xs:all>),替换组,和通配符(xs:any)拥有一个联合类型的内容模型。在查询编译和优化过程中,如果不知道确切的类型,这些值的操作就需要进行运行时的类型转换。这样会减慢查询的速度。因此,出于性能的原因,如果可能的话,这样的XML架构信息和数据类型应该避免去使用。
应用架构信息来指定元素的唯一性能够帮助优化查询。所以,一个<xs:choice>结构比一个带有可选择元素的<sequence>更好。
禁用XML Index Selection
XML index slection在检查约束时被禁用,因为查询优化器不能保证XML索引在计算约束前被修改,反之亦然。必须足够注意,依照本文中的执行准则,保证约束能够在XML blob上被有效的计算。此外,XML index selection在带CHECK OPTION的视图中被禁用。
XML列上的全文检索
在XML列上,可以独立于列的XML索引建立XML列的全文检索。这种索引能够索引元素内容,而不管XML标记的标签和属性值,并用标记的标签作为标记的边界。
XQuery的函数fn:contains()只包含字面语义,执行字符串匹配,并且对大小写敏感。另一方面,应用CONTAINS()进行全文搜索,是使用单词衍生的标记匹配。因此,它们具有不同的语义。比如说:用XQuery来查找字符串“data”,那么单词“database”也会与其匹配,但是用CONTAINS()全文搜索就匹配不到;相反,用CONTAINS()来全文搜索单词“drove”,则单词“driving”会与其匹配,而用XQuery则匹配不到。此外,全文搜索不能搜索属性值,而XQuery表达式需要用聚合函数fn:string()来搜索混合内容。
当XML列上有全文索引时,下面给出一些建议:
• 使用全文搜索筛选感兴趣的XML值。
• 用XML数据类型的方法来查询这些筛选出的XML实例。这里会使用XML列上的XML索引。
这样就需要将全文索引和XML索引结合起来使用。通过全文搜索实现的词语和短语的高选择性,减少了XQuery的进一步的处理,使XQuery检索一个相对于基表来说较少的行集。这样能够较大的提高查询速度。这种方法在查询由关键字的词干所组成的短语时也同样适用。
XQuery搜索为搜索指定了内容(节点集)。一部分XML数据可以被提升到计算列XC中,并为该列建立全文索引。这样定义了XC为全文搜索的内容。
示例:全文搜索和XQuery一起使用
下面的查询执行了keyword和data的全文搜索,而且验证单词“data”出现在<book>的<title>元素的内容中。它用全文contains()方法定位包含搜索词的XML实例。XML数据类型方法exist()验证XML实例在正确的上下文中包含子串。
select *
from docs
where contains(xCol, 'data')
AND xCol.exist('/book/title/text()[contains(.,"data")]') = 1
示例:在全文搜索中应用前缀搜索
在全文索引中可以进行前缀搜索。下面的查询表示要找到所有以“data”开头的单词,如“database”。同样,XQuery搜索也匹配 “database”。
select *
from docs
where contains(xCol, '"data*"')
and xCol.exist('/book/title/text()[contains(.,"data")]') = 1
注意在全文搜索contains()方法中双引号的使用。
快照隔离和XML索引
XML数据的修改是用新的来代替旧的XML实例。这种改变被传递到主和辅助XML索引中。在查询优化器的控制下,基表中被修改的行和XML索引被锁定,行和页被锁定,并可能逐步上升到表的锁定。由于锁的增加,并发性减弱了,尤其当修改操作为日常的主要工作量时。
在SQL Server 2005种,基于快照的隔离引入了一个新的隔离级别,叫做“snapshot”,它是read-committed隔离级别的一种新的执行方式。更多相关内容,请查阅SQL Server联机丛书。它是基于一种内部的版本控制机制来实现的,当数据库的snapshot隔离级别可用时,它能够消除读取器和写入器的锁的争用。
基于快照隔离级别下的读操作,能够访问数据的某一个版本,同时不会被另一个并发的更新操作所阻塞。这种阻塞的减少可以在并发工作中提高事务处理的吞吐量。
应用快照隔离,XML列的值和相应的主XML索引和辅助XML索引行根据更新操作保存版本。当修改非XML列时,避免了不必要的XML列的版本化。这种优化使快照隔离对于XML的处理相当有用。
查询和数据修改
为索引XML合并多个value()方法的执行
在索引的情况下,在一个SELECT列表中,合并相同的类型化XML列上多个value()方法的执行,会提高执行的效率。执行的合并是由查询优化器根据查询的成本所决定的。这样会大大提高性能。如下例所示:
示例:合并多个value()方法的执行
假设<book>元素的内容模型是由XML架构命名空间中的一个XML架构集合bookCollection所定义的。此外,创建了一个表TypedBooks,包含一个XML列xBook,该列类型由bookCollection所定义,并在该列创建主XML索引。XML架构定义如下:
CREATE XML SCHEMA COLLECTION bookCollection AS
'<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://www.microsoft.com/book"
targetNamespace="http://www.microsoft.com/book">
<xsd:element name="book" type="bookType" />
<xsd:complexType name="bookType">
<xsd:sequence>
<xsd:element name="title" type="xsd:string" />
<xsd:element name="author" type="authorName"
maxOccurs="unbounded"/>
<xsd:element name="price" type="xsd:decimal" />
</xsd:sequence>
<xsd:attribute name="subject" type="xsd:string" />
<xsd:attribute name="releasedate" type="xsd:integer" />
<xsd:attribute name="ISBN" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="authorName">
<xsd:sequence>
<xsd:element name="first-name" type="xsd:string" />
<xsd:element name="last-name" type="xsd:string" />
</xsd:sequence>
</xsd:complexType>
</xsd:schema>'
GO
CREATE TABLE TypedBooks
(id int PRIMARY KEY, xBook XML(DOCUMENT bookCollection))
CREATE PRIMARY XML INDEX idx_priXML_xBook ON TypedBooks (xBook)
在下面的查询中,value()方法被合并,因为它们是在同一个XML列中被调用,并且<title>和<price>元素的只包含单个基数已经从XML架构中推断得到。
WITH XMLNAMESPACES ('http://www.microsoft.com/book' AS "bk")
SELECT xBook.value ('/bk:book/title', 'nvarchar(128)') Title,
xBook.value ('/bk:book/price', 'decimal(5,2)') Price
FROM TypedBooks
下列条件适合优化:
• XML列必须是类型化的,从而节点只包含单个基数才可能从XML架构集合中来推断。无论什么情况下,XML列都应该用列选项DOCUMENT来声明。另外,应用nodes()方法来产生单个节点的引用。当为非类型化的XML应用nodes()方法是,优化仍然进行,从而保证了唯一的元素,并且value()方法从这些元素中提取属性值。
• 为了优化的执行,必须要指定完全的路径。如果路径中包含通配符(*),子代轴(//-operator),祖先轴(..),XPath函数,和节点测试(node()),那么优化将被阻止。
• Value()方法中的路径表达式不包含谓词或序数。
• 当路径表达式是用nodes()方法获得内容相关的,则会发生优化。这时,在上条限制的基础上,还要求value()方法的第一个参数必须是完整的相对路径。
• value()方法必须在一个SELECT列表中被连续的调用才可能合并。非连续的value()方法的执行不会被合并。
在T-SQL的WHERE子句包含形如xCol.value(…) = xCol.value(…)时,也会发生优化。但像xCol.value(…) = 常量的谓词不会发生优化。
用exist()方法检验存在
只要情况允许,就对XML数据类型应用exist()方法,而不是value()方法,因为这样会得到更好的性能。当用在SQL的WHERE子句中时,exist()方法是最有帮助的,并且它比value()方法更加有效的利用XML索引。当在XQuery表达式中应用sql:variable()和sql:column()时,也是这样的。
比如,考虑下面的查询,用exist()方法来检索标题为“Writing Secure Code”的图书:
SELECT *
FROM docs
WHERE xCol.exist('(/book/title/text())[.="Writing Secure Code"]') = 1
PATH或VALUE类型的辅助XML索引,用来计算路径表达式((/book/title/text())[.="Writing Secure Code"]),包括索引中值的检索(实例中的"Writing Secure Code"),它们生成XML实例并返回。如果路径和查找的值非常特殊,那么相应的执行会比为XML列的XML blob计算路径表达式更快。查找值可以通过sql:variable()或者sql:column()来提供。更多信息,请查阅本文的Parameterize Your XQuery and XML DML Expression部分。
下面是用value()方法写出的查询,它首先检索出所有图书的标题,然后再应用筛选"Writing Secure Code":
SELECT *
FROM docs
WHERE xCol.value('(/book/title)[1]', 'varchar(50)') = 'Writing Secure Code'
这种方式得到较低效的查询执行,因为在XML索引查找中没有用到筛选值"Writing Secure Code"。类似的情况也出现在通过SQL变量或另一个value()方法来指定筛选值时。
示例:应用sql:column()
下面的查询找出作者数大于id的图书:
SELECT *
FROM docs
WHERE xCol.exist('/book [count(author) > sql:column("id")]') = 1
将 nodes() 和 value() 方法组合使用
nodes()方法声称一个内部节点的引用的行集,而value()方法可以从这些节点中提取标量值。这两个方法一起使用可以将XML数据以关系形式表示出来。
nodes()方法输出的每一行都代表了一个引用,因而在value()方法中,用来选择内容节点的属性序数谓词就可以省略了,如下例所示。此外,如果nodes()方法确定只生成一个引用,那么完全删去nodes()方法会更高效。这些优化对于XML变量和参数来说非常有帮助。
实例:用nodes()方法除去原有的谓词
这个查询从表docs的xCol列的每个book实例中提取出属性ISBN。其中nodes()方法分别返回每个<book>元素(内容节点)的引用,并且在内容节点中最多只有一个@ISBN属性存在。
SELECT ref.value('@ISBN', 'nvarchar(32)')
FROM docs CROSS APPLY xCol.nodes('/book') AS node(ref)
如果在每个XML实例中只有一个<book>元素,那么下面的查询会更快:
SELECT xCol.value('(/book/@ISBN)[1]', 'nvarchar(32)')
FROM docs
优化XML blob
添加多个tempDB文件使XML变量和参数更加可伸缩
只要XML变量和参数的值很小,它们可以存储在主存中。但是比较大的值就要放在tempdb存储中。在一个多用户的场景中,如果出现很多较大的XML blob,那么到tempdb的连接就成为了一个瓶颈。创建多个tempdb文件减少了存储的连接,并会提高可伸缩性。下个例子说明了怎样添加多个tempdb文件。
示例:创建多个tempdb文件
这个示例为tempdb创建了两个附加的数据文件,其中每个文件的初始大小为8MB,且两个日志文件的初始大小均为1MB。
USE TEMPDB
GO
ALTER DATABASE tempdb ADD FILE
(NAME = 'Tempdb_Data1',
FILENAME = 'C:\temp\Tempdb_Data1.MDF', SIZE = 8 MB),
(NAME = 'Tempdb_Data2',
FILENAME = 'C:\temp\Tempdb_Data2.MDF', SIZE = 8 MB)
GO
ALTER DATABASE tempdb ADD log FILE
(NAME = 'Tempdb_Log1',
FILENAME = 'C:\temp\Tempdb_Log1.LDF', SIZE = 1 MB),
(NAME = 'Tempdb_Log2',
FILENAME = 'C:\temp\Tempdb_Log2.LDF', SIZE = 1 MB)
GO
这些文件可以通过ALTER DATABASE tempdb REMOVE FILE命令来删除。更多信息,请查阅SQL Server联机丛书。
消除到XML数据类型的额外转换
在一个包含XML类型参数的用户定义函数中,调用者可以提供一个text或binary类型的值,并隐式的转换为XML数据类型。函数体内的XML参数的每次使用都要从输入的值转换为XML数据类型。所以可以声明一个XML数据类型的变量,并把参数值赋到该变量中,以避免频繁转换所带来的消耗。这样只需要一次从参数到变量的转换,之后是在函数体或存储过程中重复使用该XML变量。下面的示例阐述了这一点。
示例:消除转换
考虑下面的函数GetTitleAndIsbnOfBook(),它返回图书的标题和ISBN号码:
CREATE FUNCTION GetTitleAndIsbnOfBook (@book XML)
RETURNS TABLE AS
RETURN
SELECT @book.value ('(/book/@ISBN)[1]', 'nvarchar(32)') ISBN,
@book.value ('(/book/title)[1]', 'nvarchar(128)') title
如果这个函数调用输入的是string值,那么每次调用value()方法时都要将其转换为XML数据类型。下面重写了函数,使得函数体内只需要一次转换。可是,table变量@retTab需要一个多语句的表值函数,而这样又引入了额外的消耗。但是当XML数据很大时,可以通过对XML变量的多次访问来弥补。
CREATE FUNCTION GetTitleAndIsbnOfBookOpt (@book varbinary(max))
RETURNS @retTab TABLE (ISBN nvarchar(32), title nvarchar(128)) AS
BEGIN
DECLARE @xbook XML
SET @xbook = @book
INSERT INTO @retTab
SELECT @xbook.value ('(/book/@ISBN)[1]', 'nvarchar(32)'),
@xbook.value ('(/book/title)[1]', 'nvarchar(128)')
RETURN
END
指定唯一元素
唯一性基数的推理,消除了在查询和修改数据过程中序号的指定。它简化了查询执行计划并生成了有效的联接操作。特别的,它为嵌套循环联接做出适当的选择。
在类型化XML里,XML架构中的元素默认有唯一性基数,除非用minOccurs和maxOccurs重写。另外,在类型化XML列,变量,参数上的DOCUMENT约束,保证了在XML数据类型的实例中最高层的元素只有一个。
对于非类型化的数据或架构中的多个同辈元素都允许存在的数据来说,节电的唯一性基数在路径表达式中通过给出一个序号来指定,就像下面的示例所展示的一样。序号[1]用T-SQL语句TOP 1 ascending来计算,同样last()用TOP 1 descending来计算。nodes()方法也为每个返回的XML实例设定了唯一性。
如果一个单节点的序号被忽略,那么查询优化器会使用一个默认的基数,这个基数要非常的大。这样一来,在谓词计算时,可能会为嵌套循环的联接做出不太理想的选择。当没有XML索引存在,并且没有可用的统计信息来估计基数时,这样的影响会更加明显。
示例:为非类型化XML指定唯一性基数
假设xCol列中的每个XML实例只包含一个顶级元素<book>,而该元素只有一个单一的子元素<title>。考虑下列查询:
SELECT xCol.query ('/book/title')
FROM docs
查询优化器用一个默认的基数来估计<title>元素的基数。每个<book>含有一个单一的title,所以<title>是一个唯一的元素,但是优化器的估计值比这要大的多。下面重写的查询给优化器传递了正确的基数:
SELECT xCol.query ('(/book/title)[1]')
FROM docs
表达式(/a/b)[1]和/a/b [1] 看上去十分相似,它们的语义上的区别已经在MSDN白皮书XML Best Practices for Microsoft SQL Server 2005中讨论过。
消除XML数据类型方法的多次执行
一个查询,如下:
SELECT case isnumeric (xCol.value ('(/book/price)[1]', 'nvarchar(32)'))
when 1 then xCol.value ('(/book/price)[1]', 'decimal(5,2)')
else 0
end
FROM docs
计算图书的<price>,如果price是一个数字类型的值,则要将其转换为decimal(5,2)。当应用程序可能遇到非数字类型的price值时,这个逻辑会非常有用。
但是,在查询中计算了两次value()方法,这可以通过用子查询来避免。其中value()方法在子查询中被计算,然后再在外层的SELECT中使用这个结果。
SELECT case isnumeric(Price)
when 1 then CAST (Price AS decimal(5,2))
else 0
end
FROM (SELECT xCol.value ('(/book/price)[1]', 'nvarchar(32)') Price
FROM docs) T
同样的优化也可以用在其它的地方,比如用在NULLIF:
SELECT NULLIF (Title, '')
FROM (SELECT xCol.value ('(/book/title)[1]', 'nvarchar(64)') Title
FROM docs) T
为了保证返回的是非空字符串,如果在NULLIF()中使用value()方法来计算的话,则需要计算两次value()方法。
Data(), text(), 和string() 访问器
XQuery 提供了一个可从节点中提取标量的、类型化值的函数 fn:data(),一个可返回文本节点的节点测试 text(),以及可返回节点的字符串值的函数 fn:string()。但是,它们的用法容易引起混淆。下面是有关在 SQL Server 2005 中正确使用它们的准则,考虑XML实例<age>12</age>:
• 非类型化 XML:路径表达式 /age/text() 返回文本节点"12"。函数 fn:data(/age) 返回字符串值"12",fn:string(/age) 也是如此。
• 类型化 XML:对于任何简单的类型化 元素,表达式 /age/text() 都会返回静态错误。另一方面,fn:data(/age) 返回整数 12,而 fn:string(/age) 会产生字符串"12"。
这些函数具有不同的性能特性。函数fn:string()递归的聚集它的上下文的所有文本节点。当上下文节点为单值时,这种情况十分严重。因此,fn:data()和text()不但可以满足使用,而且他们更加的高效。
对于非类型化的XML,当想得到一个节点的值时,用text()来返回文本节点比fn:data()快。路径表达式/book/text()返回<book>元素的文本子节点。在query()方法中,这种文本节点被序列化,并且看上去像是一些文本节点的值拼接而成的。另一方面,fn:data()聚合<book>元素的子树中的所有值。这种聚合使得fn:data()的计算所付出的代价,比用text()直接获得元素的简单内容还要昂贵。
非类型化XML的文本聚合
依照XQuery的语义,一个像下面所示的非类型化XML的查询:
SELECT xCol.value ('(/book/title[.="Writing Secure Code"])[1]',
'nvarchar(64)')
FROM docs
或
SELECT xCol.value ('(/book/title
[fn:string()="Writing Secure Code"])[1]'), 'nvarchar(64)')
FROM docs
需要<title>元素下的所有文本节点的聚合,来计算谓词。这阻止了搜索字符串的XML索引检查。
如果<title>元素只有一个文本节点,那么在文本节点上计算谓词的一个更有效的方法,如下所示:
SELECT xCol.value ('(/book/title/text())[1]
[. = "Writing Secure Code"]', 'nvarchar(64)')
FROM docs
这时,值"Writing Secure Code" 的XML索引检查就会发生了。
参数化XQuery和XML DML表达式
XQuery和XML DML表达式不是自动参数化。因此,如果两个XQuery表达式只是参数的值不同,那么最好使用sql:column()或sql:variable()来为您的XQuery和XML DML表达式提供参数值,而不是用动态SQL语句。用这些函数使查询自动参数化。
下面的示例展示了一个存储过程的执行。这种技术可以用到任意查询,函数或方法调用,或者是数据修改语句的参数化中。
例如,下面的存储过程查找价格比输入值底的图书:
CREATE PROC sp_myProc
@Price decimal
AS
SELECT *
FROM docs
WHERE 1 = xCol.exist('(/book/price)[. < sql:variable("@Price")]')
在ADO.NET和OLEDB中,应将输入值@Price绑定为参数。这避免了当参数被绑定为不同值时的查询的重新编译。用sql:column()将产生相似的好处。
下面的Microsoft® Visual Basic® .NET代码展示了在调用存储过程时的参数绑定:
'myConn is the connection string
SqlCommand cmd = New SqlCommand("sp_myProc", myConn)
cmd.CommandType = CommandType.StoredProcedure
'Parameter binding
Dim myParm As SqlParameter = cmd.Parameters.Add("@Price", _
SqlDbType.Decimal)
myParm.Direction = ParameterDirection.Input
myParm.value = 2
'Invoke the stored procedure
SqlDataReader myReader = cmd.ExecuteReader()
'Invoke the stored procedure a second time
myParm.value = 49.99
SqlDataReader myReader = cmd.ExecuteReader()
更多信息,请查阅Microsoft® Visual Studio® .NET文档。
示例:在数据修改中应用sql:variable()
假设ISBN为"0-2016-3361-2" 的<book>的<price>打了百分之十的折扣。其中,折扣值和ISBN号码都作为参数传递到数据修改语句中,并且语句格式是固定的,不会因为折扣值和ISBN号码的不同而改变。
DECLARE @discountFactor float, @sqlisbn nvarchar(32)
SET @discountFactor = 0.9
SET @sqlisbn = N'0-7356-1588-2'
UPDATE docs
SET xCol.modify('replace value of (/book/price/text())[1] with
sql:variable("@discountFactor")*(/book/price/text())[1]')
WHERE xCol.exist('/book[@ISBN = sql:variable("@sqlisbn")]') = 1
示例:在创建元素时使用sql:variable()
下面的modify()方法阐明了sql:variable()的用法,它给被构造的元素提供变量值
DECLARE @name nvarchar(64)
SET @name = 'Microsoft Press'
UPDATE docs
SET xCol.modify ('
insert <publisher Name = "{sql:variable("@name")}"></publisher>
into (/book/title)[1]')
优化谓词和序数
不包含节点测试和分支条件(在路径的中间节点处不包含谓词或序号)的完全路径(从根节点到所选节点的绝对位置路径,只包含子代轴和自身轴)比带分支条件的路径表达式计算效率更高。在有索引的情况下,完全路径可以用作索引查找。对于XML blob,这种路径的语法分析的速度比带分支条件和通配符的路径要快。
一个完全路径末端的节点测试和谓词用来筛选选择的节点。索引也用到了。对于XML blob,语法分析是高效的。请看下面的示例。
示例:完全路径的计算
考虑路径表达式,它选择作者名为Davis的图书:
SELECT xCol.query ('/book[author/first-name = "Davis"]')
FROM docs
虽然谓词的判断不是直接在<book>元素上,但是用路径/book/author/first-name来定位的节点<first-name>使用“Davis”来筛选的。返回的<book>元素就是满足所给谓词的那些元素。
即使是不带谓词和序数的部分指定路径,比如/book//first-name,基于路径的查找也非常有效。在XML索引中,查询编译器用LIKE运算符来匹配这种路径。因此,指定路径越详尽,就越容易提高处理效率。
路径表达式/book[@ISBN = “1-8610-0157-6”]/author/first-name中的分支条件(节点测试和谓词在路径表达式的中间)要计算路径表达式/book[@ISBN = “1-8610-0157-6”]和/book/author/first-name,并且取两个<book>元素集合的交集。因此,执行起来比没有分支条件的路径表达式慢一些。所以在路径表达式的中间应该避免使用节点测试和谓词。有时在数据建模时就要仔细考虑,就像在前面Generic vs. Specific Markup的示例中讨论过的。
将序号移到路径表达式的末尾
路径表达式中的序号,最好应该放在路径表达式的末尾。如果每个<book>元素都有一个<title>,那么路径表达式/book[1]/title[1]就等价于(/book/title)[1]。而无论在XML索引情况下,还是在XML blob情况下,后者的执行都会快一些,因为它已经确定了<book>元素里含有第一个<title>元素。类似的,路径表达式(/book/@ISBN)[1]比/book[1]/@ISBN执行的更快。
应用上下文节点计算谓词
除了将谓词,序数,和节点测试移到路径表达式的尾端以外,应用上下文节点计算谓词也会产生不错的效果。下面就是这样的一个示例:
示例:应用上下文节点的谓词计算
下面的查询找出subject为“security”的图书。它需要两个路径表达式的计算,即/book和/book/@subject,而在后面的表达式中检查值“security”。
SELECT *
FROM docs
WHERE xCol.exist ('/book[@subject = "security"]') = 1
下面的重写了查询,它只需要计算一个路径/book/@subject并检查是否路径含有值“security”。这样会生成一个较为简单的查询计划,并且比前面的查询执行的更快。
SELECT *
FROM docs
WHERE xCol.exist ('/book/@subject[. = "security"]') = 1
范围条件
范围条件有益于类型化XML的使用。存储在XML列中的数据和XML索引依照XML架构中的类型定义被类型化。在比较值时避免了运行时的类型转换,并允许VALUE型辅助XML索引的范围扫描。这时也需要在范围条件中指定上下文节点,就像下面的示例中所描述的那样。
示例:范围条件中的上下文节点
考虑这样一个查询,从表TypedBooks的类型化XML列xBook中找出价格在$9.99到$49.99范围内的图书:
SELECT xBook
FROM TypedBooks
WHERE xBook.exist ('
declare default element namespace "http://www.microsoft.com/book";
/book[price > 9.99 and price < 49.99]') = 1
路径表达式/book/price > 9.99和/book/price < 49.99被分别计算。而查询优化器并不知道这两个<price>元素是同一个元素,因为在<book>元素中可以存在多个<price>元素。这样就约束了基于VALUE型的辅助XML索引的范围检查。下面的重写的查询保证了使用同一个<price>上下文节点,并且基于VALUE型的辅助XML索引在值9.99和49.99的范围扫描也起到作用。这样产生了更好的效果。
SELECT xBook
FROM TypedBooks
WHERE xBook.exist ('
declare default element namespace "http://www.microsoft.com/book";
/book/price[. > 9.99 and . < 49.99]') = 1
父轴
路径表达式中父轴的使用将阻碍某些优化。XQuery编译器拼接不带分支的路径表达式的片断,使之成为一个长路径,它会比分别计算各个片断更高效。这种优化被成为path collapsing。
可是,这种技术在父轴上是不起作用的。此外,XML索引不能被用来计算父轴。取而代之的,路径只做前向的遍历会提高性能。例如,原有的路径表达式为/book/title[../@ISBN = "0-7356-1588-2"],而将它改写为/book[@ISBN = "0-7356-1588-2"]/title将会更好。
当导航到类型化XML的一个节点的父节点时,会丢失节点的类型信息,并会返回一个通用类型xs:anyType的元素。对这种节点的进一步的操作则需要显式转换,并会降低查询处理的速度。而且,这种类型转换可能会阻止XML索引起作用。所以,最好是从父节点向下导航,而不是向上导航到父节点。
动态查询
XQuery表达式在字面上用XML数据类型方法来表示。它们的计算应用可用的XML索引,并由查询优化器来选择。
当XQuery表达式能够动态的指定时,应用程序的开发将会十分方便。可以使用下面的一些方法:
• 查询构造
创建查询为一个字符串,并用sp_executesql来执行。和EXEC不同,它将编译后的查询计划缓存起来,并且查询优化器可能会重用编译后的查询计划。查询可以被参数化,因为它是字符串形式的并可以含有嵌入的参数。这里要十分小心避免SQL注入攻击。
• 应用XPath函数
用name()函数,或local-name()和namespace-URI()函数来代替XPath表达式中的每一个定位函数。这样会生成一个查询,您可以向其中传入节点名称和查找值。您可以进一步参数化查询,如Parameterize Your XQuery or XML DML Expression所描述的。这种参数化查询非常便于应用程序开发。但是,它产生的查询计划会忽视XML索引,因为在编译时并不知道制定的路径。
虽然查询构造方法优于参数化路径表达式,但是它包括了运行时查询编译的消耗,使它比直接指定全部查询要慢。用户传入的实际查询必须确认能够避免SQL注入攻击。此外,如果参数化查询可以使用,那么要尽量避免使用这种方法。更多信息,请查阅Parameterize Your XQuery and XML DML Expression中的示例。下面的示例说明了这个方法。
第二种方法用节点名称指定节点测试,避免了SQL注入攻击的问题。但是,查询计划会变得十分复杂,并且不如原来的查询效果好。这一点在下面的第二个事例中得到说明。
示例:应用sp_executesql的查询
假设您想动态创建下面的查询,并且用参数传入搜索值@subject:
SELECT *
FROM docs
WHERE xCol.exist('/book/@subject [. = "security"]') = 1
动态查询可以按如下方式被创建和执行。查询字符串在变量@SQLString中被创建并且用exist()方法引入一个嵌入的变量@bksubj。变量@subj为参数提供运行时的值。用@SQLString传入的动态查询应当被验证以避免SQL注入攻击。
DECLARE @SQLString NVARCHAR(500)
DECLARE @subj NVARCHAR(64)
DECLARE @ParmDefinition NVARCHAR(500)
--- Build the SQL string once
SET @SQLString =
N'SELECT *
FROM docs
WHERE xCol.exist(''/book/@subject[. =sql:variable("@bksubj")]'')=1'
SET @ParmDefinition = N'@bksubj NVARCHAR(64)'
--- Execute the string with the first parameter value
SET @subj = 'security'
EXECUTE sp_executesql @SQLString, @ParmDefinition,
@bksubj = @subj
示例:用local-name()查询
前面的查询可以将标签名称作为字符来重写,如下:
DECLARE @elemName nvarchar(4000), @attrName nvarchar(4000)
DECLARE @subjValue nvarchar(4000)
SET @elemName = N'book'
SET @attrName = N'subject'
SET @subjValue = N'security'
SELECT *
FROM docs
WHERE xCol.exist('/*[local-name() = sql:variable("@elemName") and
@*[local-name() = sql:variable("@attrName") and
. = sql:variable("@subjValue")]]') = 1
重写的查询包含了通配符(*)和用节点名称的节点测试,并且很难优化。因此,它的性能远远不如原来的查询和查询构造方法。
从XML数据产生行集
一些应用程序需要通过将一个或多个属性提升到行集的列中的方法,来从XML数据产生行集。例如,一个应用程序可能查询图书的作者,并且显示结果为一个表,其中含有两列,分别存储姓和名。这些行集的生成可以在服务器或客户端来完成,但二者有不同的性能特性。
• 在服务器端,应用下面的一种机制:
• 在XML数据类型上组合使用nodes()和value()方法
• OpenXML
• 使用 CLR 流式表值函数
• 另外,XML结果被返回给客户端,它可以用客户端的编程(DataSet)来转换数据为行集。
在客户端生成行集减轻了服务器端的负载,当几乎从服务器发送给客户端的整个数据都要映射为行集时,这种方法非常有用。另外,数据传输的开销可能要超过客户端处理所得到的好处。
在服务器端生成行集对于服务器上的XML数据的行集生成非常有用。通常,当服务器上的一小部分XML数据存储被提升到行集的列中时,这种方法是可取的。关于这种服务器端方法的优点和缺点的更多讨论,请查阅MSDN上的XML Best Practices for Microsoft SQL Server 2005。
小结
本文讨论了关于使用XML数据类型的应用程序性能优化的诸多概念。这些概念涉及了从数据管理方面和XML架构设计,到有关XML数据类型的查询的书写格式等多方面的内容。使用这些技术可以带来巨大的性能提升。学习Showplan输出,观察到底查询如何从XML索引当中获益,并在实验中重写您的查询语句来体会查询计划的改变,您将因此获益匪浅!