向 PostgreSQL 多列部分索引添加日期时间约束

问题描述 投票:0回答:1

我有一个名为

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 执行这些重复查询所需的资源。

postgresql indexing timestamp postgresql-performance
1个回答
16
投票

使用

now()
会出现异常,因为该函数不是
IMMUTABLE
(显然),并且引用 手册

索引定义中使用的所有函数和运算符都必须是“不可变的”...

我看到有两种利用(更有效)部分索引的方法:

1.使用constant日期条件的部分索引:

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
  LANGUAGE plpgsql 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$;

致电:

SELECT f_index_recreate();

now()
(就像您一样)相当于
CURRENT_TIMESTAMP
并返回
timestamptz
。使用
timestamp
投射到
now()::timestamp
或改用
LOCALTIMESTAMP

小提琴
sqlfiddle


如果您必须处理对表的并发访问,请使用

DROP INDEX CONCURRENTLY
CREATE INDEX CONCURRENTLY
。但是您无法将这些命令包装到函数中,因为根据文档

...常规

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;

2.带有“已存档”标签条件的部分索引

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
可能比重建索引更昂贵,并且表会变得稍大。

我会选择first选项(索引重建)。事实上,我正在几个数据库中使用这个解决方案。第二个会带来更昂贵的更新。

随着时间的推移,这两种解决方案都保留了它们的有用性,但随着索引中包含更多过时的行,性能会慢慢恶化。

© www.soinside.com 2019 - 2024. All rights reserved.