postgres 9.6索引仅在逻辑上扫描功能索引但不执行

bil*_*oor 10 postgresql

我已经阅读了Postgres发布的docs/wiki中的功能索引和仅索引扫描.

我现在有一个查询:

SELECT(xpath('/document/uuid/text()', xmldata))[1]::text,  
      (xpath('/document/title/text()', xmldata))[1]::text
FROM xmltable
WHERE(xpath('/document/uuid/text()', xmldata))[1]::text = 'some-uuid-xxxx-xxxx'
Run Code Online (Sandbox Code Playgroud)

和索引:

CREATE INDEX idx_covering_index on xmltable using btree (
    ((xpath('/document/uuid/text()', xmldata))[1]::text),     
    ((xpath('/document/title/text()', xmldata))[1]::text)
)
Run Code Online (Sandbox Code Playgroud)

从逻辑上看,这个索引是覆盖索引,应该启用仅索引扫描,因为所有查询的值都包含在索引中(uuid和title)

我现在碰巧知道,如果函数调用中使用的列也包含在内,Postgres只识别覆盖函数索引的索引

例如.:

SELECT to_upper(column1) from table where id >10
Run Code Online (Sandbox Code Playgroud)

1)此索引不能涵盖:

CREATE INDEX idx_covering_index on xmltable using btree (id, to_upper(column1));
Run Code Online (Sandbox Code Playgroud)

2)但可以涵盖这一个:

CREATE INDEX idx_covering_index on xmltable using btree (column1, id, to_upper(column1));
Run Code Online (Sandbox Code Playgroud)

从而导致仅索引扫描.

如果我现在尝试使用我的xml设置:

CREATE INDEX idx_covering_index on xmltable using btree (xmldata,
    ((xpath('/document/uuid/text()', xmldata))[1]::text),     
    ((xpath('/document/title/text()', xmldata))[1]::text)
)
Run Code Online (Sandbox Code Playgroud)

我收到一个错误:

数据类型xml没有访问方法"btree"的默认运算符类

很公平,不幸的是通常使用"text_ops""text_pattern_ops"不接受"xml"作为输入 - 从而呈现我的索引 - 尽管它将覆盖所有值 - 无法支持仅索引扫描.

这可以通过提供仅索引扫描的可能性来处理吗?

@ EDIT1:

我知道postgres不能使用1)中看到的索引作为覆盖索引,但可以使用像2)这样的索引

我也尝试过非常简单的表格来验证这种行为,我也记得要读过这个 - 但我不能为我的生活记住在哪里.

create table test (
    id serial primary key,
    quote text
)



insert into test (number, quote) values ('I do not know any clever quotes');
insert into test (number, quote) values ('I am sorry');



CREATE INDEX idx_test_functional on test using btree ((regexp_replace(quote, '^I ', 'BillDoor ')));
set enable_seqscan = off;

analyze test;

explain select quote from test where regexp_replace(quote, '^I ', 'BillDoor ') = 'BillDoor do not know any clever quotes'

--> "Index Scan using idx_test_functional on test  (cost=0.13..8.15 rows=1 width=27)"

drop index idx_test_functional;
CREATE INDEX idx_test_functional on test using btree (quote, (regexp_replace(quote, '^I ', 'BillDoor ')));

analyze test;

explain select quote from test where regexp_replace(quote, '^I ', 'BillDoor ') = 'BillDoor do not know any clever quotes'

--> "Index Only Scan using idx_test_functional on test  (cost=0.13..12.17 rows=1 width=27)"
Run Code Online (Sandbox Code Playgroud)

@ EDIT2:

xmltable的完整表定义:

id serial primary key (clustered),
xmldata xml (only data used to filter queries)
history xml (never queried or read, just kept in case of legal inquiry)
fileinfo text (seldom quieried, sometimes retrieved)
"timestamp" timestamp (mainly for legal inquiries too)
Run Code Online (Sandbox Code Playgroud)

该表包含大约500.000个记录,xmldata的大小在350到800字节之间,历史记录要大得多,但很少检索并且从未在过滤器中使用

为了记录,确保得到真实的结果,我总是analyze xmltable在创建或删除索引后运行

查询的完整执行计划:

explain analyze select (xpath('/document/uuid/text()', d.xmldata))[1]::text as uuid
from xmltable as d
where
(xpath('/document/uuid/text()', d.xmldata))[1]::text = 'some-uuid-xxxx-xxxx' and (xpath('/document/genre/text()', d.xmldata))[1]::text = 'bio'
Run Code Online (Sandbox Code Playgroud)

这些阴谋所涵盖的:

create index idx_genre on xmltable using btree (((xpath('/document/genre/text()', xmldata))[1]::text));

create index idx_uuid on xmltable using btree (((xpath('/document/uuid/text()', xmldata))[1]::text)); 

create index idx_uuid_genre on xmltable using btree (((xpath('/document/uuid/text()', xmldata))[1]::text), ((xpath('/document/genre/text()', xmldata))[1]::text));
Run Code Online (Sandbox Code Playgroud)

首先导致:

"Index Scan using idx_genre on xmldata d  (cost=0.42..6303.05 rows=18154 width=32)"
"  Index Cond: (((xpath('/document/genre/text()'::text, xmldata, '{}'::text[]))[1])::text = 'bio'::text)"
"  Filter: (((xpath('/document/uuid/text()'::text, xmldata, '{}'::text[]))[1])::text = 'some-uuid-xxxx-xxxx'::text)"
Run Code Online (Sandbox Code Playgroud)

公平的,我想,只是为了测试我会强迫它使用 - 在我的脑海里 - 覆盖索引:

drop index idx_uuid;
drop index idx_genre;
Run Code Online (Sandbox Code Playgroud)

现在我得到:

"Bitmap Heap Scan on xmltable d  (cost=551.13..16025.51 rows=18216 width=32)"
"  Recheck Cond: ((((xpath('/document/genre/text()'::text, xmldata, '{}'::text[]))[1])::text = 'bio'::text) AND (((xpath('/document/uuid/text()'::text, xmldata, '{}'::text[]))[1])::text = 'some-uuid-xxxx-xxxx'::text))"
"  ->  Bitmap Index Scan on idx_uuid_genre  (cost=0.00..546.58 rows=18216 width=0)"
"        Index Cond: ((((xpath('/document/genre/text()'::text, xmldata, '{}'::text[]))[1])::text = 'bio'::text) AND (((xpath('/document/uuid/text()'::text, xmldata, '{}'::text[]))[1])::text = 'some-uuid-xxxx-xxxx'::text))"
Run Code Online (Sandbox Code Playgroud)

我也尝试在索引中切换uuid和genre的位置,相同的执行计划.

Ily*_*hin 7

编辑决赛:为什么不可能

根据文档:postgresql可以在索引类型支持时执行indexonly扫描(即btree始终支持此功能,GiST和SpGiST仅适用于某些特定运算符,而GIN根本不支持).并且可以从索引重建原始索引值.

第二个要求是最有趣的.

如果是列,则它很简单(a, b),您的索引可以重建原始存储值.

如果函数使函数索引工作,您应该创建具有原始值的索引.这意味着 (f1(a), f2(b))索引将再次出现,因为您无法(a, b)从这些值重建索引数据.开发人员提议的解决方法是创建索引, (f1(a), f2(b), a, b) 在这种情况下,查询计划程序能够确定可以运行仅索引扫描,因为索引包含原始数据.

回到你的问题,创建索引只扫描xml列是不可能的:没有运算符支持对btree至关重要的xml数据比较.xml数据没有比较运算符的定义.因此,您不能在任何类型的索引中使用此列,但在仅索引扫描中需要它以提示查询优化器执行仅索引扫描.

编辑:( 解决方案如何在特定的xpath表达式上实现仅索引扫描)

如果您知道这些数据会被频繁使用,我建议您通过触发器功能解决此问题并创建另外两个字段并按索引覆盖它们.像这样的东西:

ALTER TABLE public.xmltable ADD COLUMN xpath_uuid character varying(36);
ALTER TABLE public.xmltable ADD COLUMN xpath_title character varying(100);


CREATE INDEX idx_covering_materialized_xml_data
  ON public.xmltable
  USING btree
  (xpath_uuid COLLATE pg_catalog."default", xpath_title COLLATE pg_catalog."default");

CREATE OR REPLACE FUNCTION public.introduce_xml_materialization()
  RETURNS trigger AS
$BODY$BEGIN 

NEW.xpath_uuid = (xpath('/document/uuid/text()', NEW.xmldata))[1]::text;
NEW.xpath_title = (xpath('/document/title/text()', NEW.xmldata))[1]::text;

RETURN NEW; 
END;$BODY$
  LANGUAGE plpgsql STABLE
  COST 100;



CREATE TRIGGER index_xml_data
  BEFORE INSERT OR UPDATE
  ON public.xmltable
  FOR EACH ROW
  EXECUTE PROCEDURE public.introduce_xml_materialization();
Run Code Online (Sandbox Code Playgroud)

然后你可以做到:

SELECT  xpath_uuid, xpath_title
  FROM public.xmltable
  where xpath_uuid = ' uuid1 '
Run Code Online (Sandbox Code Playgroud)

这将显示仅索引扫描:

"Index Only Scan using idx_covering_materialized_xml_data on xmltable  (cost=0.14..8.16 rows=1 width=308)"
"  Index Cond: (xpath_uuid = ' uuid1 '::text)"
Run Code Online (Sandbox Code Playgroud)

假设读取的数据多于写入数据,则此方法将是最佳的.从插入或更新的成本来看,它通常与在xpath表达式上创建功能索引相同.

原始响应:( 对于那些愿意调整查询优化器的人来说可能很有意思)

那么问题是你的查询优化器认为xPath函数调用是最简单的.即它就像调用简单的数学运算符,其成本为1.在这种情况下,查询优化器认为,从表中获取并再次计算更容易,然后只进行纯索引扫描.

如果你将xpath调用成本增加到1000,那么查询优化器会看到这样的调用非常困难(实际上是真的)并且会尝试执行仅索引扫描.在我的测试设置中,我执行了

update pg_proc set procost=1 where proname='xpath';  
Run Code Online (Sandbox Code Playgroud)

而执行计划是

"Bitmap Heap Scan on xmltable  (cost=4.17..11.30 rows=3 width=64)"
"  Recheck Cond: (((xpath('/document/uuid/text()'::text, xmldata, '{}'::text[]))[1])::text = 'some-uuid-xxxx-xxxx'::text)"
"  ->  Bitmap Index Scan on idx_covering_index_3  (cost=0.00..4.17 rows=3 width=0)"
"        Index Cond: (((xpath('/document/uuid/text()'::text, xmldata, '{}'::text[]))[1])::text = 'some-uuid-xxxx-xxxx'::text)"
Run Code Online (Sandbox Code Playgroud)

但是,当我这样做

update pg_proc set procost=1000 where proname='xpath';
Run Code Online (Sandbox Code Playgroud)

执行计划正在切换到仅索引扫描

"Index Scan using idx_covering_index_3 on xmltable  (cost=0.15..31.20 rows=3 width=64)"
"  Index Cond: (((xpath('/document/uuid/text()'::text, xmldata, '{}'::text[]))[1])::text = 'some-uuid-xxxx-xxxx'::text)"
Run Code Online (Sandbox Code Playgroud)

在我的卷上(即没有数据),仅索引查询的最小成本明显小于原始索引+表扫描,但最大成本更大.因此,您可能需要在查询优化上作弊,以便在xpath调用成本上放置更高的值.

希望这会有所帮助,出于好奇,只需向我们展示使用仅索引查询的收益.