Postgres可以在部分索引where子句中使用函数吗?

tha*_*mes 3 postgresql indexing function

我有一个大的Postgres表,我想在索引的2列中的1列上进行部分索引.我可以和如何在部分索引的where子句中使用Postgres函数,然后让select查询使用该部分索引?

示例场景

第一列是"杂志",第二列是"卷",第三列是"问题".所有的杂志都可以有相同的"音量"和"问题"#,但我希望索引只包含该杂志的两个最新卷.这是因为杂志可能比其他杂志更老,并且具有比年轻杂志更高的卷数.

创建了两个不可变的严格函数来确定杂志f_current_volume('gq')和f_previous_volume('gq')的当前和去年的卷.注意:当前/过去的卷#每年只更改一次.

我尝试使用函数创建部分索引,但是当在查询上使用说明时,它只对当前的卷杂志进行seq扫描.


CREATE INDEX ix_issue_magazine_volume ON issue USING BTREE ( magazine, volume ) 
  WHERE volume IN (f_current_volume(magazine), f_previous_volume(magazine));

-- Both these do seq scans.
select * from issue where magazine = 'gq' and volume = 100;
select * from issue where magazine = 'gq' and volume = f_current_volume('gq');

做这项工作我做错了什么?如果有可能为什么需要以这种方式为Postgres使用索引呢?


-- UPDATE: 2013-06-17, the following surprisingly used the index.
-- Why would using a field name rather than value allow the index to be used?
select * from issue where magazine = 'gq' and volume = f_current_volume(magazine);

Cra*_*ger 5

不变性和'当前'

如果您的f_current_volume函数改变了它的行为 - 正如其名称所暗示的那样,以及f_previous_volume函数的存在,那么数据库可以自由地返回完全伪造的结果.

PostgreSQL会拒绝让你创建索引,抱怨你只能使用IMMUTABLE函数.问题是,标记一个函数IMMUTABLE意味着正在告诉PostgreSQL关于函数行为的一些东西,根据文档.你说的是"我保证这个功能的结果不会改变,可以在此基础上做出假设."

最大的假设之一是建立指数时.如果函数返回多个调用不同的输入不同的输出,事情图示.或者可能是繁荣,如果你运气不好.从理论上讲,你可以通过改变REINDEX一切来改变不可变函数,但唯一真正安全的方法是DROP使用它的每个索引,DROP函数,用它的新定义重新创建函数并重新创建索引.

如果你的东西很少发生变化,那么这实际上非常有用,但你真的在不同的时间点有两个不同的不可变函数,恰好具有相同的名称.

部分索引匹配

PostgreSQL的部分索引匹配非常愚蠢 - 但正如我在为此编写测试用例时发现的那样,比以前更加智能.它忽略了一个假人OR true.它WHERE (a%100=0 OR a%1000=0)WHERE a = 100查询使用索引.它甚至还具有非内联身份功能:

regress=> CREATE TABLE partial AS SELECT x AS a, x 
          AS b FROM generate_series(1,10000) x;
regress=> CREATE OR REPLACE FUNCTION identity(integer) 
          RETURNS integer AS $$
          SELECT $1; 
          $$ LANGUAGE sql IMMUTABLE STRICT;
regress=> CREATE INDEX partial_b_fn_idx 
          ON partial(b) WHERE (identity(b) % 1000 = 0);
regress=> EXPLAIN SELECT b FROM partial WHERE b % 1000 = 0;
                                      QUERY PLAN                                       
---------------------------------------------------------------------------------------
 Index Only Scan using partial_b_fn_idx on partial  (cost=0.00..13.05 rows=50 width=4)
(1 row)
Run Code Online (Sandbox Code Playgroud)

但是,它无法证明IN子句匹配,例如:

regress=> DROP INDEX partial_b_fn_idx;
regress=> CREATE INDEX partial_b_fn_in_idx ON partial(b)
          WHERE (b IN (identity(b), 1));
regress=> EXPLAIN SELECT b FROM partial WHERE b % 1000 = 0;
                               QUERY PLAN                                 
----------------------------------------------------------------------------
 Seq Scan on partial  (cost=10000000000.00..10000000195.00 rows=50 width=4)
Run Code Online (Sandbox Code Playgroud)

那么我的建议呢?重写INOR列表:

CREATE INDEX ix_issue_magazine_volume ON issue USING BTREE ( magazine, volume ) 
  WHERE (volume = f_current_volume(magazine) OR volume = f_previous_volume(magazine));
Run Code Online (Sandbox Code Playgroud)

......并且在当前版本上它可能正常工作,只要你记住上面列出的不变性规则.好吧,第二个版本:

select * from issue where magazine = 'gq' and volume = f_current_volume('gq');
Run Code Online (Sandbox Code Playgroud)

威力.更新:不,它不会; 为了使用它,Pg必须认识到这一点,magazine='gq'并意识到这f_current_volume('gq')是因为它是等价的f_current_volume(magazine).它不会尝试使用部分索引匹配来证明该级别的等价性,因此您在更新中已经注意到,您必须f_current_volume(magazine)直接编写.我应该发现这一点.理论上,如果规划者足够聪明,PostgreSQL可以使用第二个查询的索引,但我不确定你如何有效地寻找像这样的替换值得的地方.

第一个例子,volume = 100永远不会使用索引,因为在查询计划时,PostgreSQL不知道f_current_volumne('gg');会评估100.你可以OR volume = 100在你的部分索引WHERE子句中添加一个OR子句,然后PostgreSQL可以解决它.