我们正在尝试在Postgresql中创建OEBS模拟功能。假设我们有一个表单构造函数,需要将表单结果存储在数据库中(例如电子邮件正文)。在Oracle中,您可以使用包含150~列的表(以及存储在其他位置的某些映射)将每个字段存储在单独的列中。但与Oracle相反,我们希望将所有表单存储在postgresql xml字段中。 树的例子是
<row xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<object_id>2</object_id>
<pack_form_id>23</pack_form_id>
<prod_form_id>34</prod_form_id>
</row>
我们想搜索这个字段。 测试表包含400k行,以下选择在90秒内执行:
select *
from params
where (xpath('//prod_form_id/text()'::text, xmlvalue))[1]::text::int=34
所以我创建了这个索引:
create index prod_form_idx
ON params using btree(
((xpath('//prod_form_id/text()'::text, xmlvalue))[1]::text::int)
);
它没有任何区别。执行仍然是90秒。 EXPLAIN计划显示:
Bitmap Heap Scan on params (cost=40.29..6366.44 rows=2063 width=292)
Recheck Cond: ((((xpath('//prod_form_id/text()'::text, xmlvalue, '{}'::text[]))[1])::text)::integer = 34)
-> Bitmap Index Scan on prod_form_idx (cost=0.00..39.78 rows=2063 width=0)
Index Cond: ((((xpath('//prod_form_id/text()'::text, xmlvalue, '{}'::text[]))[1])::text)::integer = 34)
我不是伟大的计划解释器所以我想这意味着正在使用索引。问题是:速度在哪里?我该怎么做才能优化这种查询?
答案 0 :(得分:13)
好吧,至少使用索引。您获得了位图索引扫描而不是普通的索引扫描,这意味着xpath()函数将被多次调用。
让我们做一点检查:
CREATE TABLE foo ( id serial primary key, x xml, h hstore );
insert into foo (x,h) select XMLPARSE( CONTENT '<row xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<object_id>2</object_id>
<pack_form_id>' || n || '</pack_form_id>
<prod_form_id>34</prod_form_id>
</row>' ),
('object_id=>2,prod_form_id=>34,pack_form_id=>'||n)::hstore
FROM generate_series( 1,100000 ) n;
test=> EXPLAIN ANALYZE SELECT count(*) FROM foo;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------
Aggregate (cost=4821.00..4821.01 rows=1 width=0) (actual time=24.694..24.694 rows=1 loops=1)
-> Seq Scan on foo (cost=0.00..4571.00 rows=100000 width=0) (actual time=0.006..13.996 rows=100000 loops=1)
Total runtime: 24.730 ms
test=> explain analyze select * from foo where (h->'pack_form_id')='123';
QUERY PLAN
----------------------------------------------------------------------------------------------------
Seq Scan on foo (cost=0.00..5571.00 rows=500 width=68) (actual time=0.075..48.763 rows=1 loops=1)
Filter: ((h -> 'pack_form_id'::text) = '123'::text)
Total runtime: 36.808 ms
test=> explain analyze select * from foo where ((xpath('//pack_form_id/text()'::text, x))[1]::text) = '123';
QUERY PLAN
------------------------------------------------------------------------------------------------------
Seq Scan on foo (cost=0.00..5071.00 rows=500 width=68) (actual time=4.271..3368.838 rows=1 loops=1)
Filter: (((xpath('//pack_form_id/text()'::text, x, '{}'::text[]))[1])::text = '123'::text)
Total runtime: 3368.865 ms
我们可以看到,
结论:
此外,由于您的xml数据非常大,它将被烘烤(压缩并存储在主表之外)。这使得主表中的行更小,因此每页更多行,这降低了位图扫描的效率,因为页面上的所有行都必须重新检查。
你可以解决这个问题。由于某种原因,xpath()函数(非常慢,因为它处理xml)具有相同的成本(1个单位),如整数运算符“+”......
update pg_proc set procost=1000 where proname='xpath';
您可能需要调整成本值。当给出正确的信息时,规划器知道xpath很慢并且将使用索引扫描来避免位图索引扫描,而不需要重新检查页面上所有行的条件。
请注意,这并不能解决行估计问题。由于您无法分析xml(或hstore)的内部,因此您将获得行数的默认估计值(此处为500)。因此,如果涉及某些联接,计划者可能完全错误并选择灾难性计划。解决这个问题的唯一方法是使用合适的列。