【问题标题】:Add datetime constraint to a PostgreSQL multi-column partial index将日期时间约束添加到 PostgreSQL 多列部分索引
【发布时间】:2013-01-22 14:05:56
【问题描述】:

我有一个名为 queries_query 的 PostgreSQL 表,它有很多列。

createduser_sid 这两个列在我的应用程序的 SQL 查询中经常一起使用,以确定给定用户在过去 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】:

    您使用now() 得到一个异常,因为该函数不是IMMUTABLE(显然)并且引用the manual

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

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

    1。使用常量日期条件的部分索引:

    CREATE INDEX queries_recent_idx ON queries_query (user_sid, created)
    WHERE created > '2013-01-07 00:00'::timestamp;
    

    假设created实际上定义为timestamp。为timestamptz 列(timestamp with time zone)提供timestamp 常量是行不通的。从timestamptimestamptz(反之亦然)的转换取决于当前时区设置,并且不是一成不变的。使用匹配数据类型的常量。了解带/不带时区的时间戳的基础知识:

    删除并重新创建该索引在流量较低的几个小时内,可能每天或每周(或任何对您来说足够好的)都有一个 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。使用now()::timestamp 转换为timestamp 或改用LOCALTIMESTAMP

    db小提琴here
    sqlfiddle


    如果您必须处理对表的并发访问,请使用DROP INDEX CONCURRENTLYCREATE 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;
    

    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 选项(索引重建)。事实上,我在几个数据库中使用了这个解决方案。第二个会导致更新成本更高。

    随着时间的推移,这两种解决方案都会保持其有用性,但随着索引中包含更多过时的行,性能会慢慢下降。

    【讨论】:

    • 谢谢!我很想使用那个 PostgreSQL 函数,但是在运行它时我实际上遇到了错误,例如:pastie.org/6098033 有什么想法吗?
    • 玩了一会儿之后,我也收到一个错误,说“格式”准备好的语句不存在。如果这很重要,我正在使用 9.2。
    • @rdegges:这不应该发生。准备好的陈述?创建一次函数,然后调用它。也许我不够清楚。更新了我的答案并添加了一个 sqlfiddle 证明它适用于 9.2。
    猜你喜欢
    • 2021-03-27
    • 2015-05-19
    • 2016-03-21
    • 2017-05-22
    • 1970-01-01
    • 2011-02-03
    • 2018-02-20
    • 2016-02-19
    • 1970-01-01
    相关资源
    最近更新 更多