我有一个包含以下列的表格:
- 一个名为
id
的整数列 - 一个名为
value
的文本列> - 一个名为
creation_date
的时间戳列>
目前,已经为id
和value
列创建了索引。
我必须在该表中搜索给定值,并希望尽可能快地进行搜索。但我真的不需要查看超过一个月的记录。所以,理想情况下,我想将它们从索引中排除。
实现此目标的最佳方法是什么:
- 执行表分区。仅在子表中搜索适当的月份。
- 创建一个仅包含最近记录的部分索引。每个月重新创建它。
- 还有别的吗?
(PS.:“最佳方案”是指最方便、快捷、易维护的方案)
最佳答案
部分索引
A partial index将是完美的,甚至是部分 multicolumn index .但是你的情况
don't need to search value in records older than one month
不稳定。部分索引的条件只能用于文字或 IMMUTABLE
函数,即常量值。您提到每个月重新创建一次
,但这与您的定义早于一个月
不一致。你看出区别了吗?
如果您只需要当前(或上一个)月份,索引重建以及查询本身就会变得相当简单!
对于此答案的其余部分,我将按照您的定义“不超过一个月” 进行操作。我以前不得不处理这样的情况。以下解决方案最适合我:
将索引条件基于固定的时间戳,并在查询中使用相同的时间戳以说服查询规划器它可以使用部分索引。这种部分将在很长一段时间内保持有用,只是随着新行的添加和旧行从您的时间范围中删除,其有效性会下降。索引将返回越来越多的误报,额外的 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 注入(inject) 和锁定问题 以及高并发负载!
这个“指导表”在您的制度中每张 table 都有一行:
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 行的小测试用例更新了 fiddle ,以证明它可以工作。
PostgreSQL 甚至会为我的示例查询执行仅索引扫描。没有比这更快的了。
关于sql - 从 PostgreSQL 中的搜索中排除过时数据的最佳方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16168504/