在PostgreSQL中从搜索中排除过时数据的最佳方法

时间:2013-04-23 11:43:58

标签: sql postgresql indexing plpgsql ddl

我有一个包含以下列的表:

  • 名为id
  • 的整数列
  • 名为value
  • 的文本列
  • 名为creation_date
  • 的时间戳列

目前,已为idvalue列创建了索引。

我必须在此表中搜索给定值,并希望尽可能快地进行搜索。但我真的不需要查看超过一个月的记录。所以,理想情况下我想将它们从索引中排除。

实现这一目标的最佳方式是什么:

  1. 执行表分区。只搜索相应月份的子表格。
  2. 创建仅包含最近记录的部分索引。每个月重新创建一次。
  3. 别的什么?
  4. (PS:“最佳解决方案”意味着最方便,快速且易于维护的解决方案)

1 个答案:

答案 0 :(得分:3)

部分索引

partial index对此完美,甚至是部分multicolumn index。但是你的情况

  

不需要在超过一个月的记录中搜索值

不稳定。部分索引的条件只能用于文字或IMMUTABLE函数,即常量值。您提到了Recreate it every month,但这与您的定义older than one month不符。你看到差异吧?

如果您只需要当前(或最后)月份,索引重新创建以及查询本身会变得更加简单!

对于本答复的其余部分,我的定义“不超过一个月”。我以前不得不处理这样的情况。以下解决方案最适合我:

将索引条件基于固定时间戳并在查询中使用相同的时间戳来说服查询计划程序它可以使用部分索引。这种部分将在很长一段时间内保持有用,只有在添加新行并且旧行超出时间范围时,其有效性才会降低。该索引将返回越来越多的误报,其中一个额外的WHERE子句必须从您的查询中消除。重新创建索引以更新其条件。

鉴于您的测试表:

CREATE TABLE mytbl (
   value text
  ,creation_date timestamp
);

创建一个非常简单的IMMUTABLE SQL函数:

CREATE OR REPLACE FUNCTION f_mytbl_start_ts()
  RETURNS timestamp AS
$func$
SELECT '2013-01-01 0:0'::timestamp
$func$ LANGUAGE sql IMMUTABLE;

在部分索引的条件下使用该函数:

CREATE INDEX mytbl_start_ts_idx ON mytbl(value, creation_date)
WHERE (creation_date >= f_mytbl_start_ts());

value排在第一位。 this related answer on dba.SE中的解释 @Igor在评论中的输入让我改进了答案。部分多列索引应该更快地排除部分索引中的误报 - 这是索引条件的本质,它总是越来越过时(但仍然很多比没有它。)

查询

像这样的查询将使用索引并且应该非常快:

SELECT value
FROM   mytbl
WHERE  creation_date >= f_mytbl_start_ts()            -- !
AND    creation_date >= (now() - interval '1 month')
AND    value = 'foo';

看似冗余的WHERE子句:creation_date >= f_mytbl_start_ts()的唯一目的是使查询规划器使用部分索引。

您可以手动删除并重新创建功能和索引。

完全自动化

或者你可以在一个更大的方案中自动化它,可能有很多类似的表:

免责声明:这是高级内容。您需要知道自己在做什么,并考虑用户权限,可能的 SQL注入锁定问题,并且负载很重!

这个“指导表”在你的政权中每桌收到一行:

CREATE TABLE idx_control (
   tbl text primary key  -- plain, legal table names!
  ,start_ts timestamp
);

我会将所有这些元对象放在单独的架构中

对于我们的例子:

INSERT INTO idx_control(tbl, value)
VALUES ('mytbl', '2013-1-1 0:0');

“转向表”提供了额外的好处,您可以在中央位置概览所有此类表格及其各自的设置,并且可以同步更新部分或全部表格。

每当您在此表中更改start_ts时,以下触发器就会启动并完成其余操作:

触发功能:

CREATE OR REPLACE FUNCTION trg_idx_control_upaft()
  RETURNS trigger AS
$func$
DECLARE
   _idx  text := NEW.tbl || 'start_ts_idx';
   _func text := 'f_' || NEW.tbl || '_start_ts';
BEGIN

-- Drop old idx
EXECUTE format('DROP INDEX IF EXISTS %I', _idx);

-- Create / change function; Keep placeholder with -infinity for NULL timestamp
EXECUTE format('
CREATE OR REPLACE FUNCTION %I()
  RETURNS timestamp AS
$x$
SELECT %L::timestamp
$x$ LANGUAGE SQL IMMUTABLE', _func, COALESCE(NEW.start_ts, '-infinity'));

-- New Index; NULL timestamp removes idx condition:    
IF NEW.start_ts IS NULL THEN 
   EXECUTE format('
   CREATE INDEX  %I ON %I (value, creation_date)', _idx, NEW.tbl);
ELSE
   EXECUTE format('
   CREATE INDEX  %I ON %I (value, creation_date)
   WHERE  creation_date >= %I()', _idx, NEW.tbl, _func);
END IF;

RETURN NULL;

END
$func$ LANGUAGE plpgsql;

触发:

CREATE TRIGGER upaft
AFTER UPDATE ON idx_control
FOR EACH ROW
WHEN (OLD.start_ts IS DISTINCT FROM NEW.start_ts)
EXECUTE PROCEDURE trg_idx_control_upaft();

现在,转向表上的一个简单的UPDATE可以校准索引和函数:

UPDATE idx_control
SET    start_ts = '2013-03-22 0:0'
WHERE  tbl = 'mytbl';

您可以运行cron作业或手动调用 使用索引的查询不会更改。

-> SQLfiddle
我用一个10k行的小测试用例更新了小提琴,以证明它有效。 PostgreSQL甚至会为我的示例查询执行仅索引扫描。不会比这更快。