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

标签 postgresql

我在 Postgres 发布的文档/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'

和索引:

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

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

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

例如:

SELECT to_upper(column1) from table where id >10

1) 不能被该索引覆盖:

CREATE INDEX idx_covering_index on xmltable using btree (id, to_upper(column1));

2) 但可以被这个覆盖:

CREATE INDEX idx_covering_index on xmltable using btree (column1, id, to_upper(column1));

从而导致仅索引扫描。

如果我现在用我的 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)
)

我得到一个错误:

data type xml has no default operator class for access method "btree"

很公平,不幸的是,通常使用的 "text_ops""text_pattern_ops" 不接受“xml”作为输入 - 因此呈现我的索引 - 尽管它会涵盖所有值 - 无法支持仅索引扫描。

能否以提供仅索引扫描可能性的方式处理此问题?

@编辑1:

我知道 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)"

@编辑2:

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)

该表包含大约: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'

被这些 indizies 覆盖:

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));

首先导致:

"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)"

我认为很公平,只是为了测试我会强制它使用 - 在我看来 - 覆盖索引:

drop index idx_uuid;
drop index idx_genre;

现在我得到:

"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))"

我也试过在索引中调换 uuid 和 genre 的位置,同样的执行计划。

最佳答案

编辑最后:为什么这是不可能的

根据文档:当索引类型支持时,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 表达式上实现仅索引扫描的解决方案)

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

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();

然后你可以简单地做:

SELECT  xpath_uuid, xpath_title
  FROM public.xmltable
  where xpath_uuid = ' uuid1 '

这将显示仅索引扫描:

"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)"

假设读取的数据多于写入的数据,这种方法是最优的。从插入或更新的成本来看,它通常与在 xpath 表达式上创建函数索引相同。

原始回复:(对于那些愿意调整查询优化器的人来说可能会很有趣)

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

如果您将 xpath 调用成本增加到比方说 1000,查询优化器将发现这样的调用明显更难(这实际上是事实),并且将尝试执行仅索引扫描。在我的测试设置中,我执行了

update pg_proc set procost=1 where proname='xpath';  

执行计划是

"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)"

但是当我这样做的时候

update pg_proc set procost=1000 where proname='xpath';

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

"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)"

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

希望这会有所帮助,并且出于好奇,向我们展示使用仅索引查询的好处。

关于postgresql - postgres 9.6 索引仅在逻辑上可能但不执行的功能索引上扫描,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42206575/

相关文章:

python - "Matching"/Django中跨数据库的关系数据

ruby-on-rails - Redmine/Ruby/Rails/Postgresql - 无法加载此类文件 -- pg_ext

数据库可索引或串行重叠可能吗?

ruby-on-rails - 如何在 Rails 中使用 postgresql 函数

ruby-on-rails - 对每个请求持续运行的这两个奇怪的查询是什么?

database - 查询的 SQL 空结果

sql - 利用 Rails ahoy 和 groupdate gems 计算每日活跃用户的最有效方法是什么?

mysql - 如何组合两个 SQL 查询,使一个查询的输出成为另一个查询的条件

sql - 如何使用 PostgreSQL 计算分层定价

postgresql - 在 PostgreSQL 中复制表 FROM 错误