XQUERY:优化涉及大型集合的查询
XQUERY: optimizing a query that touches large collections
我在 ExistDB 中有以下 XQUERY 运行(针对遵循 TEI 模式的 XML 文档):
xquery version "3.1";
declare namespace tei="http://www.tei-c.org/ns/1.0";
let $data-collection := "/db/apps/deheresi/resources/documents"
let $people-collection := "/db/apps/deheresi/resources/documents/codes_people.xml"
for $msdoc in collection($data-collection)/tei:TEI[contains(@xml:id,'ms609')]
for $ordinal in $msdoc/tei:text/tei:front//tei:div[@type='registry_ordinal']/replace(@n, '#', '')
for $doctype in $msdoc/tei:text/tei:front//tei:div[@type='doc_type']/replace(@subtype, '#', '')
for $folio in $msdoc/tei:text/tei:front//tei:div[@type='folio']/replace(@n, '#', '')
for $nameref in $msdoc/tei:text/tei:body[1]/tei:p[1]/tei:seg[1]/tei:persName[@role = 'dep']/replace(@nymRef, '#', '')
for $persname in normalize-space(string-join(doc($people-collection)//tei:person[@xml:id = $nameref]))
return concat('<td>',$ordinal,'</td><td>',$folio,'</td><td>',$doctype,'</td><td>',$persname,'</td>')
XML 文档的组织:
有700+个TEI文档,每个文档都以<TEI xml:id="foo_1.xml">
为根节点(文档标识符递增foo_1.xml,foo_2.xml,foo_3.xml,等等)(总是在同一个地方)
每个 TEI 文档都包含一个唯一的元素来标识一个人 <persName role="dep" nymRef="#unique_foo_name">
(并不总是在文档中的相同位置)
一个单独的 XML 文档 codes_people.xml
,其中包含 1500 多个 xml:ids 不同的人
该函数执行以下操作:
从每个 xml 文档中获取标识 tei:TEI/@xml:id
和 tei:persName[@role="dep"]/@nymRef
用 tei:persName[@role="dep"]/@nymRef
我在 codes_people.xml/tei:person/xml:id="unique_foo_name"
中查找名称
这都是 returns 预期的结果...除了它真的非常慢(4 秒)。显然我是在本地计算机而不是服务器上进行测试,但我想在更强大的服务器上进行测试之前优化查询。
根据请求添加:
ExistDB 版本:3.3.0
示例输出(最终目标是 HTML table)
<td>0001</td><td>1r</td><td>Deposition</td><td>Arnald Garnier</td>
<td>0002</td><td>1r</td><td>Deposition</td><td>Guilhem de Rosengue</td>
<td>0003</td><td>1r</td><td>Deposition</td><td>Hugo de Mamiros</td>
<td>0004</td><td>1r</td><td>Deposition</td><td>P Lapassa senior</td>
非常感谢。
编辑:我在下面的自我回复中添加了更多信息,并在评论中对 Dropbox 中的所有文件添加了 link。
我试图通过用 let
和 concat()
:
替换某些 for
循环来简化 Xquery
xquery version "3.1";
declare namespace tei="http://www.tei-c.org/ns/1.0";
declare variable $people-collection := doc("/db/apps/deheresi/resources/documents/codes_people.xml");
let $data-collection := "/db/apps/deheresi/resources/documents"
for $msdoc in collection($data-collection)/tei:TEI[contains(@xml:id,'ms609')]
let $concat1 := concat('<td>',
$msdoc//tei:div[@type='registry_ordinal']/replace(@n, '#', ''),
'</td><td>',
$msdoc//tei:div[@type='doc_type']/replace(@subtype, '#', ''),
'</td><td>',
$msdoc//tei:div[@type='folio']/replace(@n, '#', ''),
'</td><td>')
(: obtain the attribute value of persName[@role = 'dep']/@nymRef :)
let $nameref := $msdoc//tei:persName[@role = 'dep']/replace(@nymRef, '#', '')
(: now use the attribute value to lookup a printable name using xml:id in document codes_people.xml :)
let $persname := normalize-space(string-join($people-collection//tei:person[@xml:id = $nameref]))
return concat($concat1,$persname,'</td>')
这些调整使查询执行时间减少了 0.5 秒(现在为 3.5 秒)。
如果我删除最终查找 ($persname
),查询将在 0.17 秒内执行。对文件 codes_people.xml
的查找似乎是瓶颈。
编辑:我添加了以下影响相关元素的索引,但它们没有产生任何优化
<collection xmlns="http://exist-db.org/collection-config/1.0">
<index xmlns:tei="http://www.tei-c.org/ns/1.0">
<range>
<create qname="tei:persName" type="xs:string"/>
<create qname="tei:person" type="xs:string"/>
</range>
</index>
<triggers>
<trigger class="org.exist.extensions.exquery.restxq.impl.RestXqTrigger"/>
</triggers>
</collection>
从查询分析器查看:
所以您的代码存在一些影响性能的问题。首先是您处理字符串而不是 xml 路径这一事实。例如当使用 replace()
而不是 some/@path[. = 'xyz']
时。只需使用 fn:id()
而不是 replace()
即可将执行时间缩短到 1 秒以下。
第二个是索引配置文件中缺少 xmlschema 命名空间声明,而不是使用了这些索引,因为您强制存在来处理字符串而不是 xml。
第三个事实是您的 xquery 代码不是 return 格式良好的 xml 片段,出于性能原因,这总是一个坏主意。
xquery version "3.1";
declare namespace tei="http://www.tei-c.org/ns/1.0";
declare variable $data-collection := "/db/apps/so-52709411/data";
(:always return a well-formed fragment:)
<table>{
let $people-collection := doc($data-collection || "/codes_people.xml")
let $msdoc := collection($data-collection)//tei:TEI[contains(@xml:id,'ms609')]
for $n in $msdoc
let $registry := $n//tei:div[@type='registry_ordinal']/data(@n)
let $type := $n//tei:div[@type='doc_type']/data(@subtype)
let $folio := $n//tei:div[@type='folio']/data(@n)
let $nym := substring-after($n//tei:persName[@role = 'dep']/data(@nymRef), '#')
let $persName := $people-collection//id($nym)/tei:persName
return
<tr>
<td>{$registry}</td>
<td>{$type}</td>
<td>{$folio}</td>
<td>{$persName/string()
}</td>
</tr>
}
</table>
结合
<collection xmlns="http://exist-db.org/collection-config/1.0">
<index xmlns:tei="http://www.tei-c.org/ns/1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<range>
<create qname="tei:persName" type="xs:string"/>
<create qname="tei:person" type="xs:string"/>
<create qname="@type" type="xs:string"/>
<create qname="@role" type="xs:string"/>
<create qname="@nymRef" type="xs:string"/>
</range>
</index>
<triggers>
<trigger class="org.exist.extensions.exquery.restxq.impl.RestXqTrigger"/>
</triggers>
</collection>
导致索引实际可用
但是数据样本还不够大,无法对重写的 xquery 产生太大的性能影响。因此,即使没有索引,您也应该 运行 在小于 1 的范围内(取决于内存、硬件等,YMMV)
您可以下载一个可用的应用程序 运行 您的代码 here
我在 ExistDB 中有以下 XQUERY 运行(针对遵循 TEI 模式的 XML 文档):
xquery version "3.1";
declare namespace tei="http://www.tei-c.org/ns/1.0";
let $data-collection := "/db/apps/deheresi/resources/documents"
let $people-collection := "/db/apps/deheresi/resources/documents/codes_people.xml"
for $msdoc in collection($data-collection)/tei:TEI[contains(@xml:id,'ms609')]
for $ordinal in $msdoc/tei:text/tei:front//tei:div[@type='registry_ordinal']/replace(@n, '#', '')
for $doctype in $msdoc/tei:text/tei:front//tei:div[@type='doc_type']/replace(@subtype, '#', '')
for $folio in $msdoc/tei:text/tei:front//tei:div[@type='folio']/replace(@n, '#', '')
for $nameref in $msdoc/tei:text/tei:body[1]/tei:p[1]/tei:seg[1]/tei:persName[@role = 'dep']/replace(@nymRef, '#', '')
for $persname in normalize-space(string-join(doc($people-collection)//tei:person[@xml:id = $nameref]))
return concat('<td>',$ordinal,'</td><td>',$folio,'</td><td>',$doctype,'</td><td>',$persname,'</td>')
XML 文档的组织:
有700+个TEI文档,每个文档都以
<TEI xml:id="foo_1.xml">
为根节点(文档标识符递增foo_1.xml,foo_2.xml,foo_3.xml,等等)(总是在同一个地方)每个 TEI 文档都包含一个唯一的元素来标识一个人
<persName role="dep" nymRef="#unique_foo_name">
(并不总是在文档中的相同位置)一个单独的 XML 文档
codes_people.xml
,其中包含 1500 多个 xml:ids 不同的人
该函数执行以下操作:
从每个 xml 文档中获取标识
tei:TEI/@xml:id
和tei:persName[@role="dep"]/@nymRef
用
tei:persName[@role="dep"]/@nymRef
我在codes_people.xml/tei:person/xml:id="unique_foo_name"
中查找名称
这都是 returns 预期的结果...除了它真的非常慢(4 秒)。显然我是在本地计算机而不是服务器上进行测试,但我想在更强大的服务器上进行测试之前优化查询。
根据请求添加:
ExistDB 版本:3.3.0
示例输出(最终目标是 HTML table)
<td>0001</td><td>1r</td><td>Deposition</td><td>Arnald Garnier</td>
<td>0002</td><td>1r</td><td>Deposition</td><td>Guilhem de Rosengue</td>
<td>0003</td><td>1r</td><td>Deposition</td><td>Hugo de Mamiros</td>
<td>0004</td><td>1r</td><td>Deposition</td><td>P Lapassa senior</td>
非常感谢。
编辑:我在下面的自我回复中添加了更多信息,并在评论中对 Dropbox 中的所有文件添加了 link。
我试图通过用 let
和 concat()
:
for
循环来简化 Xquery
xquery version "3.1";
declare namespace tei="http://www.tei-c.org/ns/1.0";
declare variable $people-collection := doc("/db/apps/deheresi/resources/documents/codes_people.xml");
let $data-collection := "/db/apps/deheresi/resources/documents"
for $msdoc in collection($data-collection)/tei:TEI[contains(@xml:id,'ms609')]
let $concat1 := concat('<td>',
$msdoc//tei:div[@type='registry_ordinal']/replace(@n, '#', ''),
'</td><td>',
$msdoc//tei:div[@type='doc_type']/replace(@subtype, '#', ''),
'</td><td>',
$msdoc//tei:div[@type='folio']/replace(@n, '#', ''),
'</td><td>')
(: obtain the attribute value of persName[@role = 'dep']/@nymRef :)
let $nameref := $msdoc//tei:persName[@role = 'dep']/replace(@nymRef, '#', '')
(: now use the attribute value to lookup a printable name using xml:id in document codes_people.xml :)
let $persname := normalize-space(string-join($people-collection//tei:person[@xml:id = $nameref]))
return concat($concat1,$persname,'</td>')
这些调整使查询执行时间减少了 0.5 秒(现在为 3.5 秒)。
如果我删除最终查找 ($persname
),查询将在 0.17 秒内执行。对文件 codes_people.xml
的查找似乎是瓶颈。
编辑:我添加了以下影响相关元素的索引,但它们没有产生任何优化
<collection xmlns="http://exist-db.org/collection-config/1.0">
<index xmlns:tei="http://www.tei-c.org/ns/1.0">
<range>
<create qname="tei:persName" type="xs:string"/>
<create qname="tei:person" type="xs:string"/>
</range>
</index>
<triggers>
<trigger class="org.exist.extensions.exquery.restxq.impl.RestXqTrigger"/>
</triggers>
</collection>
从查询分析器查看:
所以您的代码存在一些影响性能的问题。首先是您处理字符串而不是 xml 路径这一事实。例如当使用 replace()
而不是 some/@path[. = 'xyz']
时。只需使用 fn:id()
而不是 replace()
即可将执行时间缩短到 1 秒以下。
第二个是索引配置文件中缺少 xmlschema 命名空间声明,而不是使用了这些索引,因为您强制存在来处理字符串而不是 xml。
第三个事实是您的 xquery 代码不是 return 格式良好的 xml 片段,出于性能原因,这总是一个坏主意。
xquery version "3.1";
declare namespace tei="http://www.tei-c.org/ns/1.0";
declare variable $data-collection := "/db/apps/so-52709411/data";
(:always return a well-formed fragment:)
<table>{
let $people-collection := doc($data-collection || "/codes_people.xml")
let $msdoc := collection($data-collection)//tei:TEI[contains(@xml:id,'ms609')]
for $n in $msdoc
let $registry := $n//tei:div[@type='registry_ordinal']/data(@n)
let $type := $n//tei:div[@type='doc_type']/data(@subtype)
let $folio := $n//tei:div[@type='folio']/data(@n)
let $nym := substring-after($n//tei:persName[@role = 'dep']/data(@nymRef), '#')
let $persName := $people-collection//id($nym)/tei:persName
return
<tr>
<td>{$registry}</td>
<td>{$type}</td>
<td>{$folio}</td>
<td>{$persName/string()
}</td>
</tr>
}
</table>
结合
<collection xmlns="http://exist-db.org/collection-config/1.0">
<index xmlns:tei="http://www.tei-c.org/ns/1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<range>
<create qname="tei:persName" type="xs:string"/>
<create qname="tei:person" type="xs:string"/>
<create qname="@type" type="xs:string"/>
<create qname="@role" type="xs:string"/>
<create qname="@nymRef" type="xs:string"/>
</range>
</index>
<triggers>
<trigger class="org.exist.extensions.exquery.restxq.impl.RestXqTrigger"/>
</triggers>
</collection>
导致索引实际可用
但是数据样本还不够大,无法对重写的 xquery 产生太大的性能影响。因此,即使没有索引,您也应该 运行 在小于 1 的范围内(取决于内存、硬件等,YMMV)
您可以下载一个可用的应用程序 运行 您的代码 here