gor*_*nyj 4 postgresql indexing optimization xpath
我们正在尝试在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>
Run Code Online (Sandbox Code Playgroud)
我们想搜索这个字段.测试表包含400k行,以下选择在90秒内执行:
select *
from params
where (xpath('//prod_form_id/text()'::text, xmlvalue))[1]::text::int=34
Run Code Online (Sandbox Code Playgroud)
所以我创建了这个索引:
create index prod_form_idx
ON params using btree(
((xpath('//prod_form_id/text()'::text, xmlvalue))[1]::text::int)
);
Run Code Online (Sandbox Code Playgroud)
它没有任何区别.执行仍然是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)
Run Code Online (Sandbox Code Playgroud)
我不是伟大的计划解释器所以我想这意味着正在使用索引.问题是:速度在哪里?我能做些什么来优化这种查询?
peu*_*feu 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
Run Code Online (Sandbox Code Playgroud)
我们可以看到,
结论:
此外,由于您的xml数据非常大,它将被烘烤(压缩并存储在主表之外).这使得主表中的行更小,因此每页更多行,这降低了位图扫描的效率,因为必须重新检查页面上的所有行.
你可以解决这个问题.由于某种原因,xpath()函数(非常慢,因为它处理xml)具有相同的成本(1个单位),如整数运算符"+"...
update pg_proc set procost=1000 where proname='xpath';
Run Code Online (Sandbox Code Playgroud)
您可能需要调整成本值.当给出正确的信息时,规划器知道xpath很慢并且将使用索引扫描来避免位图索引扫描,而不需要重新检查页面上所有行的条件.
请注意,这不能解决行估计问题.由于您无法分析xml(或hstore)的内部,因此您将获得行数的默认估计值(此处为500).因此,如果涉及某些联接,计划者可能完全错误并选择灾难性计划.唯一的解决方案是使用适当的列.