Jeg bliver nødt til at kalde REFRESH MATERIALIZED VIEW
på hver ændring af de involverede tabeller, ikke?
Ja, PostgreSQL i sig selv vil aldrig kalde det automatisk, du skal gøre det på en eller anden måde.
Hvordan skal jeg gøre dette?
Mange måder at opnå dette på. Inden du giver nogle eksempler, skal du huske at REFRESH MATERIALIZED VIEW
kommando blokerer visningen i AccessExclusive-tilstand, så mens den virker, kan du ikke engang gøre SELECT
på bordet.
Selvom du er i version 9.4 eller nyere, kan du give den CONCURRENTLY
mulighed:
REFRESH MATERIALIZED VIEW CONCURRENTLY my_mv;
Dette vil erhverve en ExclusiveLock og vil ikke blokere SELECT
forespørgsler, men kan have større overhead (afhænger af mængden af ændrede data, hvis få rækker er ændret, så kan det være hurtigere). Selvom du stadig ikke kan køre to REFRESH
kommandoer samtidigt.
Opdater manuelt
Det er en mulighed at overveje. Specielt i tilfælde af dataindlæsning eller batchopdateringer (f.eks. et system, der kun indlæser tonsvis af information/data efter lange perioder) er det almindeligt at have operationer til sidst for at ændre eller behandle dataene, så du kan nemt inkludere en REFRESH
operation i slutningen af det.
Planlægning af REFRESH-handlingen
Den første og udbredte mulighed er at bruge et eller andet planlægningssystem til at kalde opdateringen, for eksempel kan du konfigurere lignende i et cron-job:
*/30 * * * * psql -d your_database -c "REFRESH MATERIALIZED VIEW CONCURRENTLY my_mv"
Og så vil din materialiserede visning blive opdateret hvert 30. minut.
Overvejelser
Denne mulighed er rigtig god, især med CONCURRENTLY
mulighed, men kun hvis du kan acceptere, at data ikke er 100 % opdateret hele tiden. Husk, at selv med eller uden CONCURRENTLY
, REFRESH
kommandoen skal køre hele forespørgslen, så du skal tage den nødvendige tid til at køre den indre forespørgsel, før du overvejer tiden til at planlægge REFRESH
.
Opdater med en trigger
En anden mulighed er at kalde REFRESH MATERIALIZED VIEW
i en triggerfunktion, som denne:
CREATE OR REPLACE FUNCTION tg_refresh_my_mv()
RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
REFRESH MATERIALIZED VIEW CONCURRENTLY my_mv;
RETURN NULL;
END;
$$;
Så gør du i enhver tabel, der involverer ændringer i visningen:
CREATE TRIGGER tg_refresh_my_mv AFTER INSERT OR UPDATE OR DELETE
ON table_name
FOR EACH STATEMENT EXECUTE PROCEDURE tg_refresh_my_mv();
Overvejelser
Det har nogle kritiske faldgruber for ydeevne og samtidighed:
- Enhver INSERT/UPDATE/DELETE operation skal udføre forespørgslen (hvilket er muligt langsomt, hvis du overvejer MV);
- Selv med
CONCURRENTLY
, énREFRESH
blokerer stadig en anden, så enhver INSERT/UPDATE/DELETE på de involverede tabeller vil blive serialiseret.
Den eneste situation, jeg kan mene, er en god idé, hvis ændringerne virkelig er sjældne.
Opdater ved hjælp af LISTEN/NOTIFY
Problemet med den tidligere mulighed er, at den er synkron og pålægger en stor overhead ved hver operation. For at forbedre det kan du bruge en trigger som før, men som kun kalder en NOTIFY
operation:
CREATE OR REPLACE FUNCTION tg_refresh_my_mv()
RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
NOTIFY refresh_mv, 'my_mv';
RETURN NULL;
END;
$$;
Så kan du bygge en applikation, der holder forbindelsen og bruger LISTEN
operation for at identificere behovet for at kalde REFRESH
. Et godt projekt, som du kan bruge til at teste dette, er pgsidekick, med dette projekt kan du bruge shell-script til at gøre LISTEN
, så du kan planlægge REFRESH
som:
pglisten --listen=refresh_mv --print0 | xargs -0 -n1 -I? psql -d your_database -c "REFRESH MATERIALIZED VIEW CONCURRENTLY ?;"
Eller brug pglater
(også inde i pgsidekick
) for at sikre, at du ikke ringer til REFRESH
meget ofte. For eksempel kan du bruge følgende trigger til at gøre den til REFRESH
, men inden for 1 minut (60 sekunder):
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;
$$;
Så den kalder ikke REFRESH
på mindre end 60 sekunders mellemrum, og også hvis du NOTIFY
mange gange på mindre end 60 sekunder vises REFRESH
vil kun blive udløst én gang.
Overvejelser
Som cron-mulighed er denne også kun god, hvis du kan blotte med lidt forældede data, men dette har den fordel, at REFRESH
kaldes kun, når det virkelig er nødvendigt, så du har mindre overhead, og også dataene opdateres tættere på, når det er nødvendigt.
OBS:Jeg har ikke rigtig prøvet koderne og eksemplerne endnu, så hvis nogen finder en fejl, tastefejl eller prøver det og virker (eller ej), så lad mig det vide.