【问题标题】:Best way to exclude outdated data from a search in PostgreSQL从 PostgreSQL 中的搜索中排除过时数据的最佳方法
【发布时间】:2013-04-23 11:43:58
【问题描述】:

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

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

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

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

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

  1. 执行表分区。仅在子表中搜索适当的月份。
  2. 创建仅包括最近记录的部分索引。每个月重新创建它。
  3. 还有别的吗?

(PS.:“最好的解决方案”是指最方便、最快捷、最容易维护的解决方案)

【问题讨论】:

  • 可能是creation_date, value 上的复合索引?这将是最简单的(但不是最快的)。
  • 为什么要将 creation_date 包含在索引中?我寻找价值,而不是创建日期。
  • creation_date 在复合索引中可以被 postgresql 用来排除超过一个月的记录。索引不仅用于您搜索的字段。它们对于过滤器和连接字段也很有用。
  • @IgorRomanchenko:如果你把它做成一个复合索引(对于中小型表来说已经足够好了),那就让它在(value, creation_date) 上。经验法则是:平等第一——范围最后More in this related anser on dba.SE. 但 Q 专门要求 search as fast as I can
  • @ErwinBrandstetter 是的,我把复合索引字段的字段顺序写错了。至于问题 - 它要求the most convenient and simple and fastest(问题的最后一句话)。 + 我已经提到,复合索引不是最快的方法。

标签: sql postgresql indexing plpgsql ddl


【解决方案1】:

部分索引

partial index 非常适合,甚至是部分multicolumn index。但是你的情况

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

不稳定。部分索引的条件只能用于文字或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中的解释。
cmets 中@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 甚至会为我的示例查询执行仅索引扫描。不会比这更快。

【讨论】:

  • 您是否将此类部分索引的查询时间和索引大小与复合索引时间和大小进行了比较?
  • 对于具有大量过时行的大表(这是常见情况),部分索引应该快得多,因为索引只是整个表上可比较的复合索引的一小部分。对于小型表,(value, creation_date)(列的序列是相关的)上的复合索引几乎一样快。是的,我进行了广泛的测试。请注意,我修正了答案和小提琴中的一些错别字。
  • @ErwinBrandstetter 有完全相同的问题:您提出的解决方案现在在 postgresql 9.5 或任何新的工具/策略中仍然是最有效的吗?
  • @Mathieu:这在 pg 9.5 中仍然很好。但也请考虑新的BRIN indexes,它可能提供更简单的替代方案。
猜你喜欢
  • 2011-04-06
  • 1970-01-01
  • 1970-01-01
  • 2010-11-13
  • 1970-01-01
  • 1970-01-01
  • 2010-10-08
  • 2019-06-11
  • 2015-09-07
相关资源
最近更新 更多