供稿人:毕永军
本文简单介绍了XML数据索引的基本概念,并通过一些例子讲解了如何为XML数据建立合适的索引以提高查询效率。
DB2 从版本9.1开始,提供了对XML数据的内置支持,你不仅可以方便地存储XML,而且可以方便快捷地访问XML文档中的元素和属性。为了提高对于XML字段查询的效率,DB2提供了在XML字段上建立索引的功能。
关系型数据表的索引会包含一个或多个数据列,与传统的关系型数据表的索引不同。XML索引会包含XML字段中的元素或者属性,建立这个索引时需要用到XML的模式表达式来唯一标识一个元素或者属性。创建一个索引后,可以允许我们访问文档内部的节点,而不是必须访问整个文档。
对于一个XML字段,我们可以允许建立多个索引。在XML字段上建立索引使用CREATE INDEX语句,删除索引使用DROP INDEX语句。
下面我们看一个创建索引的例子。
假设我们有一个表companyinfo,有两个列,一个是公司的ID,另外一个是companydocs,是一个XML列,建表语句如下:
CREATE TABLE companyinfo (ID INT NOT NULL PRIMARY KEY, companydocs XML)
我们有两条记录:ID分别为1 和2, 其中companydocs列的内容如下:
公司1的信息:
<company name="Company1">
<emp id="31201" salary="60000" gender="Female">
<name>
<first>Laura</first>
<last>Brown</last>
</name>
<dept id="M25">
Finance
</dept>
</emp>
</company>
公司2的信息:
<company name="Company2">
<emp id="31664" salary="60000" gender="Male">
<name>
<first>Chris</first>
<last>Murphy</last>
</name>
<dept id="M55">
Marketing
</dept>
</emp>
<emp id="42366" salary="50000" gender="Female">
<name>
<first>Nicole</first>
? Copyright IBM Corp. 2006 87
<last>Murphy</last>
</name>
<dept id="K55">
Sales
</dept>
</emp>
</company>
我们有很多查询都是通过职员标识来查询职员信息,我们可以通过建立如下的索引提高查询效率:
CREATE INDEX empindex on companyinfo(companydocs)
GENERATE KEY USING XMLPATTERN '/company/emp/@id'
AS SQL DOUBLE
第一行语句指定我们在companyinfo表中companydocs字段建立索引,第二行语句通过使用XML模式指定了为XML文档中的职员ID属性建立索引,第三行指定将建立索引的职员标识保存为DOUBLE数据类型。
下面我们看一下XML模式表达式,XML模式表达式和XQuery语言中定义的XPath路径表达式类似,但是XML模式表达式只支持一部分XQuery语句。
XML模式表达式以斜线“/"分割,在分割的每一部分,我们会选择child::、@、attribute::、descendant::、self::、descendant-or-self::前置标志符,后面跟一个XML名称或者XML类型。如果我们没有指定前置标志符,我们会默认使用child::标志符。其中@是attribute::的简写形式。//是/descendant-or-self::/的简写形式。下面我们看几个例子:
1. CREATE INDEX empindex on companyinfo(companydocs)
GENERATE KEY USING XMLPATTERN '/company/emp/@id' AS SQL DOUBLE
2. CREATE INDEX empindex on companyinfo(companydocs)
GENERATE KEY USING XMLPATTERN '/child::company/child::emp/ attribute::id' AS SQL DOUBLE
3. CREATE INDEX idindex on companyinfo(companydocs)
GENERATE KEY USING XMLPATTERN '//@id' AS SQL DOUBLE
4. CREATE INDEX idindex on companyinfo(companydocs)
GENERATE KEY USING XMLPATTERN '/descendant-or-self::node()/ attribute::id' AS SQL DOUBLE
5. CREATE INDEX empindex on companyinfo(companydocs)
GENERATE KEY USING XMLPATTERN '/company/emp/name/last/text()' AS SQL VARCHAR(25)
上述创建索引的语句中,第一个语句和第二个语句是同义的,都是在职员ID这个属性上创建索引。第三个语句和第四个语句也是同义的,是为文档中所有的ID属性创建索引。第5个语句是为文档中的last元素创建索引,其中最后的text()指明为这个节点的文本创建索引。
注意在第三个语句和第四个语句为文档中的所有id属性建立索引。在上面的例子中,dept和emp元素都有id属性,所以dept和emp元素的id都会存放在索引中。
下面我们通过执行一些查询来查看XQuery的执行计划,以及如何使用我们创建的索引。请看下面的查询语句:
SELECT XMLQUERY(
'for $d in $doc/company/emp/name
return <name>{$d/last}</name>'
passing companydocs as "doc")
FROM companyinfo as c
WHERE XMLEXISTS(
'$i/company/emp/dept[@id="M25"]'
passing c.companydocs as "i")
该查询语句返回属于部门ID为M25的雇员的名字。在部门ID上没有创建索引之前,我们可以得到如图1所示的存取计划,我们可以看到整个查询的代价是40.68,在查询中我们只用到了XSCAN,即对于XML文档的扫描。
图 1 没有使用索引的查询计划

优化后的语句如下所示:
SELECT Q9.$C0
FROM $INTERNAL_LET$ ((TABLE ($INTERNAL_XPATH$ ('($INTERNAL_XMLTOXML_NIEO$(Q10.COMPANYDOCS))/company/emp/(dept[(@id
= "M25")])(:-->$C0:)')) ) AS Q1),
(SELECT Q8.$C0
FROM
(SELECT $INTERNAL_LET-AGG$(Q7.$C0)
FROM
(SELECT $INTERNAL_XMLTOXML_NIEO$(Q6.$C0)
FROM
(SELECT $INTERNAL_XMLELEMENT$(elemNullAndBinaryOptions,
$INTERNAL_QNAME$(NULL, NULL, name), $INTERNAL_XMLNAMESPACES$((''),
('')), Q5.$C0)
FROM
(SELECT 1
FROM (VALUES 1) AS Q2) AS Q3, $INTERNAL_FOR$ ((TABLE
($INTERNAL_XPATH$ ('($INTERNAL_XMLTOXML_NIEO$(Q10.COMPANYDOCS))/company/emp/(name)(:-->$C0:)'))
) AS Q4), $INTERNAL_LET$ ((TABLE ($INTERNAL_XPATH$
('Q4.$C0/(last)(:-->$C0:)')) ) AS Q5)) AS Q6) AS
Q7) AS Q8) AS Q9, BIYJ.COMPANYINFO AS Q10
WHERE fn:exists(Q1.$C0)
从优化后的语句我们可以看出,DB2在处理XQuery时,会将一些XML模式表达式重写为数据表的形式进行查询的各种操作。
此时我们在dept的id属性上建立索引,语句如下:
CREATE INDEX DEPTINDEX ON COMPANYINFO(COMPANYDOCS) GENERATE KEY USING XMLPATTERN '/company/emp/dept/@id' AS SQL VARCHAR(10) PCTFREE 10 MINPCTUSED 10 ALLOW REVERSE SCANS PAGE SPLIT SYMMETRIC COLLECT STATISTICS
然后我们再次说明上面的查询语句,可以得到如图 2所示的访问存取计划,从中我们可以看到,整个查询的代价降为33.15,同时我们看到在查询中我们使用到了XISCAN,使用到的索引正是我们刚刚创建的DEPTINDEX。

图 2 使用了索引的查询计划
XML数据建立索引时会同时创建两个B型树索引,逻辑索引和物理索引。逻辑索引包含CREATE INDEX语句所指定的XML模式信息。物理索引具有DB2生成的键列来支持逻辑索引,并包含已建立索引的文档值。我们只能操作逻辑索引,物理索引在创建或者删除逻辑索引的时候透明地创建或者删除。我们没有提供操作物理索引的任何接口。
但是物理索引和逻辑索引都在SYSCAT.INDEXES目录视图中,你可以查看物理索引和逻辑索引。如对于我们的例子表companyinfo,我们可以通过下面的语句查询我们创建的逻辑索引和物理索引:
SELECT INDNAME,IID,TABNAME,INDEXTYPE FROM SYSCAT.INDEXES WHERE TABNAME='COMPANYINFO'
输出如下所示:
INDNAME IID TABNAME INDEXTYPE
SQL060926152915060 1 COMPANYINFO XRGN
SQL060926152916320 2 COMPANYINFO XPTH
SQL060926152916460 3 COMPANYINFO REG
EMPINDEX 4 COMPANYINFO XVIL
SQL060926153514190 5 COMPANYINFO XVIP
DEPTINDEX 6 COMPANYINFO XVIL
SQL060926170617910 7 COMPANYINFO XVIP
我们可以看到我们建表时的主键索引类型为REG(常规索引),在XML列上创建的两个索引EMPINDEX和DEPTINDEX的类型为XVIL,表明是逻辑索引,同时每一个索引后面都有一个类型为XVIP的物理索引,名字是系统自动命名的。
我们注意到除了这几个索引外,还有两个索引,类型分别是XRGN和XPTH,这两种索引分别是区域索引和路径索引。 XML路径索引记录存储在XML列中XML文档的所有唯一路径。XML区域索引记录一个XML列在内部保存的区域。所谓区域,就是在一个数据页上保存的多个节点的集合。因为所有的节点都是保存在数据页上的,所以使用比较大的页面大小,可以较少区域索引的大小,从而可能提高性能。区域索引和路径索引都是在创建表的时候自动创建的,仅供DB2内部使用。DB2没有提供访问这两种索引的任何接口。
参考文献:
1、 DB2手册《XML Guide》:
http://www-306.ibm.com/software/data/db2/udb/support/manualsv9.html
2、《XQeury Reference》:
http://www-306.ibm.com/software/data/db2/udb/support/manualsv9.html