Вы получаете исключение в своей попытке использовать now()
, потому что функция не является IMMUTABLE
(очевидно), и я цитирую руководство здесь :
Все функции и операторы, используемые в определении индекса, должны быть «неизменными» ...
Я вижу два способа использования (гораздо более эффективного) частичного индекса здесь:
1. Частичный индекс с условием, использующим постоянный дата:
CREATE INDEX queries_recent_idx ON queries_query (user_sid, created)
WHERE created > '2013-01-07 00:00'::timestamp;
Assuming created
is actually defined as timestamp
. It wouldn't work to provide a timestamp
constant for a timestamptz
column (timestamp with time zone
). The cast from timestamp
to timestamptz
(or vice versa) depends on the current time zone setting and is not immutable. Use a constant of matching data type. Understand the basics of timestamps with/without time zone:
Drop and recreate that index at hours with low traffic, maybe with a cron job on a daily or weekly basis (or whatever is good enough for you). Creating an index is pretty fast, especially a partial index that is comparatively small. This solution also doesn't need to add anything to the table.
Предполагая, что нет параллельного доступа к таблице, автоматическое восстановление индекса может быть выполнено с помощью такой функции:
CREATE OR REPLACE FUNCTION f_index_recreate()
RETURNS void 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$ LANGUAGE plpgsql;
Вызов:
SELECT f_index_recreate();
now()
(like you had) is the equivalent of CURRENT_TIMESTAMP
and returns timestamptz
. Cast to timestamp
with now()::timestamp
or use LOCALTIMESTAMP
instead.
Tested with Postgres 9.2 - 9.4.
SQL Fiddle.
Если вам нужно иметь дело с параллельным доступом , используйте CREATE INDEX CONCURRENTLY
. Но вы не можете перенести эту команду в функцию, потому что на документацию :
... в транзакции может быть выполнена регулярная команда CREATE INDEX
block, но 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
the column at intervals of your choosing to "retire" older rows and create an index like:
CREATE INDEX some_index_name ON queries_query (user_sid, created)
WHERE NOT archived;
Добавьте условие соответствия к вашим запросам (даже если оно кажется избыточным), чтобы позволить ему использовать индекс. Обратитесь к EXPLAIN ANALYZE
, на который заходит планировщик запросов - он должен иметь возможность использовать индекс для запросов в более новую дату. Но он не поймет более сложные условия, не соответствующие точно.
Вам не нужно отбрасывать и воссоздавать индекс, но UPDATE
на столе может быть дороже, чем индексный отдых, а таблица немного больше.
Я бы пошел с опцией first (индекс отдыха). Фактически, я использую это решение в нескольких базах данных. Вторая берет на себя более дорогостоящие обновления.
Оба решения сохраняют свою полезность с течением времени, производительность медленно ухудшается, поскольку в индекс включены более устаревшие строки.