Получавате изключение, като използвате now()
защото функцията не е IMMUTABLE
(очевидно) и, цитирайки ръководството
:
Виждам два начина за използване на (много по-ефективен) частичен индекс:
1. Частичен индекс с условие, използващо константа дата:
CREATE INDEX queries_recent_idx ON queries_query (user_sid, created)
WHERE created > '2013-01-07 00:00'::timestamp;
Приемаме created
всъщност се дефинира като timestamp
. Няма да работи, ако предоставите timestamp
константа за timestamptz
колона (timestamp with time zone
). Актьорският състав от timestamp
към timestamptz
(или обратно) зависи от текущата настройка на часовата зона и не е неизменна . Използвайте константа от съответстващ тип данни. Разберете основите на времевите клейма с/без часова зона:
Пуснете и създайте отново този индекс в часове с нисък трафик, може би с 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
. Прехвърляне към timestamp
с now()::timestamp
или използвайте LOCALTIMESTAMP
вместо това.
Ако трябва да се справите с едновременен достъп към таблицата, използвайте DROP INDEX CONCURRENTLY
и 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
на масата може да е по-скъпо от пресъздаването на индекса и масата става малко по-голяма.
Бих отишла с първия опция (индекс отдих). Всъщност използвам това решение в няколко бази данни. Вторият изисква по-скъпи актуализации.
И двете решения запазват полезността си с течение на времето, производителността бавно се влошава, тъй като в индекса се включват повече остарели редове.