我有一个名为queries_query
的PostgreSQL表,它有很多列。
我的应用程序经常在SQL查询中使用其中两列created
和user_sid
来确定给定用户在过去30天内完成的查询次数。在最近30天之前的任何时间查询这些统计数据是非常非常罕见的。
这是我的问题:
我目前通过运行:
在这两列上创建了我的多列索引CREATE INDEX CONCURRENTLY some_index_name ON queries_query (user_sid, created)
但是我想进一步限制索引,只关心创建日期在过去30天内的查询。我尝试过以下操作:
CREATE INDEX CONCURRENTLY some_index_name ON queries_query (user_sid, created)
WHERE created >= NOW() - '30 days'::INTERVAL`
但是这会抛出一个异常,说明我的函数必须是不可变的。
我很乐意让这个工作变得有效,这样我就可以优化我的索引,并减少Postgres重复查询所需的资源。
答案 0 :(得分:8)
您尝试使用now()
时遇到异常,因为该功能不是IMMUTABLE
(显然),我引用the manual here:
索引定义中使用的所有函数和运算符必须是“不可变的”......
我在这里看到了两种利用(效率更高)部分索引的方法:
CREATE INDEX queries_recent_idx ON queries_query (user_sid, created)
WHERE created > '2013-01-07 00:00'::timestamp;
假设 created
实际上定义为 timestamp
。为timestamp
列(timestamptz
)提供timestamp with time zone
常量是行不通的。从timestamp
到timestamptz
的投射(反之亦然)取决于当前时区设置, 不可变 。使用匹配数据类型的常量。了解带/不带时区的时间戳的基础知识:
在流量较低的小时内删除并重新创建该索引,可能每天或每周都有一个cron作业(或者对你来说足够好)。创建索引非常快,尤其是部分索引相对较小。此解决方案也不需要向表中添加任何内容。
假设表中的 没有并发访问 ,可以使用如下函数完成自动索引重新创建:
CREATE OR REPLACE FUNCTION f_index_recreate()
RETURNS void AS
$func$
BEGIN
DROP INDEX IF EXISTS queries_recent_idx;
EXECUTE format('
CREATE INDEX queries_recent_idx
ON queries_query (user_sid, created)
WHERE created > %L::timestamp'
, LOCALTIMESTAMP - interval '30 days'); -- timestamp constant
-- , now() - interval '30 days'); -- alternative for timestamptz
END
$func$ LANGUAGE plpgsql;
呼叫:
SELECT f_index_recreate();
now()
(与您一样)相当于CURRENT_TIMESTAMP
并返回timestamptz
。使用timestamp
投放到now()::timestamp
或改为使用LOCALTIMESTAMP
。
使用Postgres 9.2 - 9.4测试 SQL Fiddle.
如果您必须处理 并发访问 ,请使用CREATE INDEX CONCURRENTLY
。但是,您无法将此命令包装到函数中,因为per documentation:
...可以在事务中执行常规
CREATE INDEX
命令 阻止,但CREATE INDEX CONCURRENTLY
不能。
因此, 两个单独的交易 :
CREATE INDEX CONCURRENTLY queries_recent_idx2 ON queries_query (user_sid, created)
WHERE created > '2013-01-07 00:00'::timestamp; -- your new condition
然后:
DROP INDEX CONCURRENTLY IF EXISTS queries_recent_idx;
(可选)重命名为旧名称:
ALTER INDEX queries_recent_idx2 RENAME TO queries_recent_idx;
在表格中添加archived
标记:
ALTER queries_query ADD COLUMN archived boolean NOT NULL DEFAULT FALSE;
UPDATE
每隔一段时间选择“退出”旧行并创建如下索引的列:
CREATE INDEX some_index_name ON queries_query (user_sid, created)
WHERE NOT archived;
为查询添加匹配条件(即使看起来多余),以允许它使用索引。使用EXPLAIN ANALYZE
检查查询计划程序是否可以访问 - 它应该能够在较新的日期使用索引进行查询。但它不会理解更复杂的条件不完全匹配。
您不必删除并重新创建索引,但表中的UPDATE
可能比索引重新创建更昂贵,而且表格略大。
我会选择第一个选项(索引重新创建)。事实上,我在几个数据库中使用此解决方案。第二个会产生更高成本的更新。
随着时间的推移,两种解决方案都会保持其有用性,因为索引中包含更多过时的行,性能会逐渐降低。