优化慢查询xquery
Optimize slow query xquery
现在查询大约需要 2 分钟,在我进行一些更改之前需要 3:48m。
xml 文档取自网页,因为它每 5 米更改一次,并实时提供有关公交车的信息。
你能帮我优化这个查询吗?
xquery version "3.0";
declare namespace bus="http://docs.gijon.es/sw/busgijon.asmx";
declare function local:getNombreParada($numero)
{
for $parada in doc("http://datos.gijon.es/doc/transporte/busgijoninfo.xml")//paradas/bus:parada
where $numero=$parada/bus:idparada
return $parada/bus:descripcion
};
declare function local:getBusesPorLinea($linea)
{
let $numero:=$linea
let $nBuses:=count(doc("http://datos.gijon.es/doc/transporte/busgijontr.xml")//bus:llegada[bus:idlinea=$numero])
return
if($nBuses=0)
then(<p>No hay ningun bus en esta linea</p>)
else(
<div>
<h2>Numero de buses funcionando en la linea {$numero} : {$nBuses}</h2>
<table class="table table-hover">
<thead>
<tr>
<th>Parada</th>
<th>Minutos hasta la llegada</th>
</tr>
</thead>
<tbody>
{
for $l in doc("http://datos.gijon.es/doc/transporte/busgijontr.xml")//bus:llegada[bus:idlinea=$numero]
for $parada in doc("http://datos.gijon.es/doc/transporte/busgijoninfo.xml")//paradas/bus:parada[bus:idparada=$l/bus:idparada]
return <tr>
<td>{$parada/bus:descripcion}</td>
<td>{$l/bus:minutos}</td></tr>
}
</tbody>
</table>
</div>
)
};
local:getBusesPorLinea(1)
PD:我是 运行 这个存在 Db
是否缓存文档?我不是专家,但您的代码似乎多次访问同一个文档。如果您确定内容缓存在执行环境中,那没关系。否则我会尝试声明
declare variable $docinfo := doc("http://datos.gijon.es/doc/transporte/busgijoninfo.xml");
declare variable $doctr := doc("http://datos.gijon.es/doc/transporte/busgijontr.xml");
确保只提取一次文件。
您还至少扫描文档两次以获取相同类型的数据。我会这样做一次:
declare variable $paradas := $docinfo//paradas;
declare variable $llegadas := $doctr//bus:llegada;
然后只过滤集合:
declare function local:getNombreParada($numero)
{
$paradas/bus:parada[bus:idparada = $numero]/bus:descripcion
};
declare function local:getBusesPorLinea($linea)
{
let $numero:=$linea
let $llegadasNum:=$llegadas[bus:idlinea=$numero]
let $nBuses:=count($llegadasNum)
return
if($nBuses=0)
then(<p>No hay ningun bus en esta linea</p>)
else(
<div>
<h2>Numero de buses funcionando en la linea {$numero} : {$nBuses}</h2>
<table class="table table-hover">
<thead>
<tr>
<th>Parada</th>
<th>Minutos hasta la llegada</th>
</tr>
</thead>
<tbody>
{
for $l in $llegadasNum
for $parada in $paradas/bus:parada[bus:idparada=$l/bus:idparada]
return <tr>
<td>{$parada/bus:descripcion}</td>
<td>{$l/bus:minutos}</td></tr>
}
</tbody>
</table>
</div>
)
};
可能并没有快多少,但我希望它更具可读性。
没有智能优化,这个连接表达式:
for $l in doc("a.xml")//bus:llegada[bus:idlinea=$numero]
for $parada in doc("b.xml")//paradas/bus:parada[bus:idparada=$l/bus:idparada]
return <tr>...</tr>
将具有二次性能。您没有告诉我们任何关于文档大小的信息,但这是我要开始寻找的地方。
在 XML 数据库环境中处理此类问题的方法通常是创建适当的索引。
首先,在 eXist 中优化查询的最佳方法是将 XML 存储在本地并使用索引。请使用内置文档了解如何设置索引。
但是,您的代码会反复从网络中获取相同的数据。让我们来解决这个问题和另一个问题,您使用 in-memory XML 查询,另一个优化瓶颈。
最重要的第一步是让您在本地数据库中查询的XML。与针对 in-memory XML 节点的查询相比,数据库中节点的查询速度更快且使用的内存更少。 (至少,2.2 及以下的版本就是这种情况。)
所以,这里有一种在本地缓存数据的方法,在最新更新超过 5 分钟后刷新缓存。
xquery version "3.0";
declare namespace bus="http://docs.gijon.es/sw/busgijon.asmx";
(: Store the XML data in the collection /db/busgijon/data :)
declare variable $COL := "/db/busgijon/data";
declare variable $INFO-FILE := "busgijoninfo.xml";
declare variable $TR-FILE := "busgijontr.xml";
(: Fetch a page from cache or from web site, updating the cache :)
declare function local:fetchPage($filename) {
(: If the page was fetched more than 5 minutes ago, refresh it :)
let $expire := current-dateTime() - xs:dayTimeDuration('PT5M')
let $page := doc($COL || "/" || $filename)/page
return if (exists($page))
then if ($page/xs:dateTime(@timestamp) ge $expire)
then $page
else (update replace $page/* with doc("http://datos.gijon.es/doc/transporte/" || $filename)/*
, update value $page/@timestamp with current-dateTime()
, $page)
else doc(xmldb:store($COL, $filename, <page timestamp="{current-dateTime()}">{doc("http://datos.gijon.es/doc/transporte/" || $filename)/*}</page>))/page
};
declare function local:getBusesPorLinea($linea)
{
(: Get the two pages from the database cache for querying :)
let $info := local:fetchPage($INFO-FILE)/bus:BusGijonInfo
let $tr := local:fetchPage($TR-FILE)/bus:BusGijonTr
let $numero:=$linea
let $nBuses:=count($tr//bus:llegada[bus:idlinea=$numero])
return
if($nBuses=0)
then(<p>No hay ningun bus en esta linea</p>)
else(
<div>
<h2>Numero de buses funcionando en la linea {$numero} : {$nBuses}</h2>
<table class="table table-hover">
<thead>
<tr>
<th>Parada</th>
<th>Minutos hasta la llegada</th>
</tr>
</thead>
<tbody>
{
(: Loop through the TR page - fetched just once from cache :)
for $l in $tr//bus:llegada[bus:idlinea=$numero]
(: Loop through the Info page - fetched just once from cache :)
for $parada in $info//paradas/bus:parada[bus:idparada=$l/bus:idparada]
return <tr>
<td>{$parada/bus:descripcion}</td>
<td>{$l/bus:minutos}</td></tr>
}
</tbody>
</table>
</div>
)
};
local:getBusesPorLinea(1)
我在 local:getBusesPorLinea 函数中唯一更改的部分是从缓存中获取顶部的两个文档并在嵌入式循环中使用它们。
local:fetchPage 函数是大部分加速发生的地方。这是它的作用:
- 将到期时间设置为过去 5 分钟。
- 尝试从缓存中获取指定页面。
- 如果页面存在,将获取的时间戳与过期时间戳进行比较。
- 如果页面的时间戳小于 5 分钟前(大于过期时间戳),return 该页面。
- 如果页面的时间戳大于 5 分钟前,重新获取它,用刷新的文档更新页面的内容,更新页面的时间戳,然后 return 新页面。
- 如果页面尚不存在,则将页面保存到具有当前时间戳的指定集合中,returning 页面元素。
5 分钟后第一个访问此 XQuery 的人将有大约 5-10 秒的额外时间,因为缓存会被刷新。这允许缓存是被动的,因此您不必每五分钟手动刷新一次。
希望对您有所帮助。
另一个提示:对于eXist-db 中的查询,最好避免使用where
子句。 XPath 谓词通常执行得更好。
http://exist-db.org/exist/apps/doc/tuning.xml?q=performance&field=all&id=D2.2.2#D2.2.6
上有不少提示
现在查询大约需要 2 分钟,在我进行一些更改之前需要 3:48m。
xml 文档取自网页,因为它每 5 米更改一次,并实时提供有关公交车的信息。
你能帮我优化这个查询吗?
xquery version "3.0";
declare namespace bus="http://docs.gijon.es/sw/busgijon.asmx";
declare function local:getNombreParada($numero)
{
for $parada in doc("http://datos.gijon.es/doc/transporte/busgijoninfo.xml")//paradas/bus:parada
where $numero=$parada/bus:idparada
return $parada/bus:descripcion
};
declare function local:getBusesPorLinea($linea)
{
let $numero:=$linea
let $nBuses:=count(doc("http://datos.gijon.es/doc/transporte/busgijontr.xml")//bus:llegada[bus:idlinea=$numero])
return
if($nBuses=0)
then(<p>No hay ningun bus en esta linea</p>)
else(
<div>
<h2>Numero de buses funcionando en la linea {$numero} : {$nBuses}</h2>
<table class="table table-hover">
<thead>
<tr>
<th>Parada</th>
<th>Minutos hasta la llegada</th>
</tr>
</thead>
<tbody>
{
for $l in doc("http://datos.gijon.es/doc/transporte/busgijontr.xml")//bus:llegada[bus:idlinea=$numero]
for $parada in doc("http://datos.gijon.es/doc/transporte/busgijoninfo.xml")//paradas/bus:parada[bus:idparada=$l/bus:idparada]
return <tr>
<td>{$parada/bus:descripcion}</td>
<td>{$l/bus:minutos}</td></tr>
}
</tbody>
</table>
</div>
)
};
local:getBusesPorLinea(1)
PD:我是 运行 这个存在 Db
是否缓存文档?我不是专家,但您的代码似乎多次访问同一个文档。如果您确定内容缓存在执行环境中,那没关系。否则我会尝试声明
declare variable $docinfo := doc("http://datos.gijon.es/doc/transporte/busgijoninfo.xml");
declare variable $doctr := doc("http://datos.gijon.es/doc/transporte/busgijontr.xml");
确保只提取一次文件。
您还至少扫描文档两次以获取相同类型的数据。我会这样做一次:
declare variable $paradas := $docinfo//paradas;
declare variable $llegadas := $doctr//bus:llegada;
然后只过滤集合:
declare function local:getNombreParada($numero)
{
$paradas/bus:parada[bus:idparada = $numero]/bus:descripcion
};
declare function local:getBusesPorLinea($linea)
{
let $numero:=$linea
let $llegadasNum:=$llegadas[bus:idlinea=$numero]
let $nBuses:=count($llegadasNum)
return
if($nBuses=0)
then(<p>No hay ningun bus en esta linea</p>)
else(
<div>
<h2>Numero de buses funcionando en la linea {$numero} : {$nBuses}</h2>
<table class="table table-hover">
<thead>
<tr>
<th>Parada</th>
<th>Minutos hasta la llegada</th>
</tr>
</thead>
<tbody>
{
for $l in $llegadasNum
for $parada in $paradas/bus:parada[bus:idparada=$l/bus:idparada]
return <tr>
<td>{$parada/bus:descripcion}</td>
<td>{$l/bus:minutos}</td></tr>
}
</tbody>
</table>
</div>
)
};
可能并没有快多少,但我希望它更具可读性。
没有智能优化,这个连接表达式:
for $l in doc("a.xml")//bus:llegada[bus:idlinea=$numero]
for $parada in doc("b.xml")//paradas/bus:parada[bus:idparada=$l/bus:idparada]
return <tr>...</tr>
将具有二次性能。您没有告诉我们任何关于文档大小的信息,但这是我要开始寻找的地方。
在 XML 数据库环境中处理此类问题的方法通常是创建适当的索引。
首先,在 eXist 中优化查询的最佳方法是将 XML 存储在本地并使用索引。请使用内置文档了解如何设置索引。
但是,您的代码会反复从网络中获取相同的数据。让我们来解决这个问题和另一个问题,您使用 in-memory XML 查询,另一个优化瓶颈。
最重要的第一步是让您在本地数据库中查询的XML。与针对 in-memory XML 节点的查询相比,数据库中节点的查询速度更快且使用的内存更少。 (至少,2.2 及以下的版本就是这种情况。)
所以,这里有一种在本地缓存数据的方法,在最新更新超过 5 分钟后刷新缓存。
xquery version "3.0";
declare namespace bus="http://docs.gijon.es/sw/busgijon.asmx";
(: Store the XML data in the collection /db/busgijon/data :)
declare variable $COL := "/db/busgijon/data";
declare variable $INFO-FILE := "busgijoninfo.xml";
declare variable $TR-FILE := "busgijontr.xml";
(: Fetch a page from cache or from web site, updating the cache :)
declare function local:fetchPage($filename) {
(: If the page was fetched more than 5 minutes ago, refresh it :)
let $expire := current-dateTime() - xs:dayTimeDuration('PT5M')
let $page := doc($COL || "/" || $filename)/page
return if (exists($page))
then if ($page/xs:dateTime(@timestamp) ge $expire)
then $page
else (update replace $page/* with doc("http://datos.gijon.es/doc/transporte/" || $filename)/*
, update value $page/@timestamp with current-dateTime()
, $page)
else doc(xmldb:store($COL, $filename, <page timestamp="{current-dateTime()}">{doc("http://datos.gijon.es/doc/transporte/" || $filename)/*}</page>))/page
};
declare function local:getBusesPorLinea($linea)
{
(: Get the two pages from the database cache for querying :)
let $info := local:fetchPage($INFO-FILE)/bus:BusGijonInfo
let $tr := local:fetchPage($TR-FILE)/bus:BusGijonTr
let $numero:=$linea
let $nBuses:=count($tr//bus:llegada[bus:idlinea=$numero])
return
if($nBuses=0)
then(<p>No hay ningun bus en esta linea</p>)
else(
<div>
<h2>Numero de buses funcionando en la linea {$numero} : {$nBuses}</h2>
<table class="table table-hover">
<thead>
<tr>
<th>Parada</th>
<th>Minutos hasta la llegada</th>
</tr>
</thead>
<tbody>
{
(: Loop through the TR page - fetched just once from cache :)
for $l in $tr//bus:llegada[bus:idlinea=$numero]
(: Loop through the Info page - fetched just once from cache :)
for $parada in $info//paradas/bus:parada[bus:idparada=$l/bus:idparada]
return <tr>
<td>{$parada/bus:descripcion}</td>
<td>{$l/bus:minutos}</td></tr>
}
</tbody>
</table>
</div>
)
};
local:getBusesPorLinea(1)
我在 local:getBusesPorLinea 函数中唯一更改的部分是从缓存中获取顶部的两个文档并在嵌入式循环中使用它们。
local:fetchPage 函数是大部分加速发生的地方。这是它的作用:
- 将到期时间设置为过去 5 分钟。
- 尝试从缓存中获取指定页面。
- 如果页面存在,将获取的时间戳与过期时间戳进行比较。
- 如果页面的时间戳小于 5 分钟前(大于过期时间戳),return 该页面。
- 如果页面的时间戳大于 5 分钟前,重新获取它,用刷新的文档更新页面的内容,更新页面的时间戳,然后 return 新页面。
- 如果页面尚不存在,则将页面保存到具有当前时间戳的指定集合中,returning 页面元素。
5 分钟后第一个访问此 XQuery 的人将有大约 5-10 秒的额外时间,因为缓存会被刷新。这允许缓存是被动的,因此您不必每五分钟手动刷新一次。
希望对您有所帮助。
另一个提示:对于eXist-db 中的查询,最好避免使用where
子句。 XPath 谓词通常执行得更好。
http://exist-db.org/exist/apps/doc/tuning.xml?q=performance&field=all&id=D2.2.2#D2.2.6
上有不少提示