不变性和“当前”
如果你的f_current_volume
函数改变了它的行为——正如它的名字所暗示的那样,并且存在一个f_previous_volume
函数,那么数据库可以自由地返回完全虚假的结果。
PostgreSQL 会拒绝让你创建索引,抱怨你只能使用IMMUTABLE
函数。问题是,根据文档,标记一个函数IMMUTABLE
意味着你正在告诉PostgreSQL一些关于函数行为的信息。你是说“我保证这个函数的结果不会改变,在此基础上随意做出假设。”
最大的假设之一是在构建索引时。如果函数在多次调用时为不同的输入返回不同的输出,事情就会发生splat。或者如果你不走运,可能会繁荣。从理论上讲,您可以通过更改REINDEX
所有内容来更改不可变函数,但唯一真正安全的方法是DROP
使用它的每个索引,DROP
函数,使用新定义重新创建函数并重新创建索引.
如果您有一些不经常更改的内容,那么这实际上非常有用,但是您确实在不同的时间点有两个不同的不可变函数恰好具有相同的名称。
部分索引匹配
PostgreSQL 的部分索引匹配非常愚蠢——但是,正如我在为此编写测试用例时发现的那样,它比以前聪明得多。它忽略了一个 dummy 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)
但是,它无法证明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)
所以我的建议?重写IN
为OR
列表:
CREATE INDEX ix_issue_magazine_volume ON issue USING BTREE ( magazine, volume )
WHERE (volume = f_current_volume(magazine) OR volume = f_previous_volume(magazine));
...并且在当前版本上它可能只是工作,只要您牢记上面概述的不变性规则。嗯,第二个版本:
select * from issue where magazine = 'gq' and volume = f_current_volume('gq');
可能。更新:不,不会;要使用它,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
不过,您可以在部分索引子句中添加 OR 子句,WHERE
然后 PostgreSQL 会弄清楚。