Ще трябва да извикам
REFRESH MATERIALIZED VIEW
при всяка промяна на съответните таблици, нали?
Да, PostgreSQL сам по себе си никога няма да го извика автоматично, трябва да го направите по някакъв начин.
Как трябва да направя това?
Много начини да постигнете това. Преди да дадете някои примери, имайте предвид, че REFRESH MATERIALIZED VIEW
командата блокира изгледа в режим AccessExclusive, така че докато работи, дори не можете да направите SELECT
на масата.
Въпреки че, ако сте във версия 9.4 или по-нова, можете да й дадете CONCURRENTLY
опция:
REFRESH MATERIALIZED VIEW CONCURRENTLY my_mv;
Това ще придобие ExclusiveLock и няма да блокира SELECT
заявки, но може да има по-големи допълнителни разходи (зависи от количеството променени данни, ако са променени няколко реда, тогава може да е по-бързо). Въпреки че все още не можете да стартирате две REFRESH
команди едновременно.
Опреснете ръчно
Това е вариант за разглеждане. Специално в случаи на зареждане на данни или пакетни актуализации (например система, която зарежда само тонове информация/данни след дълги периоди от време) е обичайно да има операции в края за промяна или обработка на данните, така че можете просто да включите REFRESH
операция в края му.
Насрочване на операцията ОБНОВЯВАНЕ
Първата и широко използвана опция е да използвате някаква система за планиране, за да извикате опресняването, например, можете да конфигурирате подобно в задание на cron:
*/30 * * * * psql -d your_database -c "REFRESH MATERIALIZED VIEW CONCURRENTLY my_mv"
И тогава материализираният ви изглед ще се обновява на всеки 30 минути.
Съображения
Тази опция е наистина добра, особено с CONCURRENTLY
опция, но само ако можете да приемете данните да не са 100% актуални през цялото време. Имайте предвид, че дори със или без CONCURRENTLY
, REFRESH
командата трябва да изпълни цялата заявка, така че трябва да отделите времето, необходимо за изпълнение на вътрешната заявка, преди да обмислите времето за насрочване на REFRESH
.
Опресняване със задействане
Друга възможност е да извикате REFRESH MATERIALIZED VIEW
в тригерна функция, като тази:
CREATE OR REPLACE FUNCTION tg_refresh_my_mv()
RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
REFRESH MATERIALIZED VIEW CONCURRENTLY my_mv;
RETURN NULL;
END;
$$;
След това във всяка таблица, която включва промени в изгледа, правите:
CREATE TRIGGER tg_refresh_my_mv AFTER INSERT OR UPDATE OR DELETE
ON table_name
FOR EACH STATEMENT EXECUTE PROCEDURE tg_refresh_my_mv();
Съображения
Той има някои критични клопки за производителност и едновременност:
- Всяка операция INSERT/UPDATE/DELETE ще трябва да изпълни заявката (което е възможно бавно, ако обмисляте MV);
- Дори с
CONCURRENTLY
, едноREFRESH
все още блокира друг, така че всяко INSERT/UPDATE/DELETE в участващите таблици ще бъде сериализирано.
Единствената ситуация, която мога да смятам за добра идея е, ако промените са наистина редки.
Опреснете чрез LISTEN/NOTIFY
Проблемът с предишния вариант е, че е синхронен и налага големи разходи при всяка операция. За да подобрите това, можете да използвате тригер както преди, но това извиква само NOTIFY
операция:
CREATE OR REPLACE FUNCTION tg_refresh_my_mv()
RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
NOTIFY refresh_mv, 'my_mv';
RETURN NULL;
END;
$$;
Така че тогава можете да създадете приложение, което поддържа връзка и използва LISTEN
операция за идентифициране на необходимостта от извикване на REFRESH
. Един хубав проект, който можете да използвате, за да тествате това, е pgsidekick, с този проект можете да използвате шел скрипт, за да правите LISTEN
, така че можете да планирате REFRESH
като:
pglisten --listen=refresh_mv --print0 | xargs -0 -n1 -I? psql -d your_database -c "REFRESH MATERIALIZED VIEW CONCURRENTLY ?;"
Или използвайте pglater
(също вътре в pgsidekick
), за да сте сигурни, че няма да извикате REFRESH
много често. Например, можете да използвате следния тригер, за да го направите REFRESH
, но в рамките на 1 минута (60 секунди):
CREATE OR REPLACE FUNCTION tg_refresh_my_mv()
RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
NOTIFY refresh_mv, '60 REFRESH MATERIALIZED VIEW CONCURRENLTY my_mv';
RETURN NULL;
END;
$$;
Така че няма да извика REFRESH
за по-малко от 60 секунди, а също и ако NOTIFY
много пъти за по-малко от 60 секунди, REFRESH
ще се задейства само веднъж.
Съображения
Като опция за cron, тази също е добра само ако можете да използвате малко остарели данни, но това има предимството, че REFRESH
се извиква само когато наистина е необходимо, така че имате по-малко режийни разходи, а също така данните се актуализират по-близо до когато е необходимо.
OBS:Все още не съм пробвал кодовете и примерите, така че ако някой открие грешка, печатна грешка или я опита и работи (или не), моля, уведомете ме.