sql >> Databasteknik >  >> RDS >> PostgreSQL

Hur kan jag säkerställa att en materialiserad vy alltid är uppdaterad?

Jag måste anropa REFRESH MATERIALIZED VIEW på varje ändring av de berörda tabellerna, eller hur?

Ja, PostgreSQL i sig kommer aldrig att anropa det automatiskt, du måste göra det på något sätt.

Hur ska jag gå till väga?

Många sätt att uppnå detta. Innan du ger några exempel, kom ihåg att REFRESH MATERIALIZED VIEW kommandot blockerar vyn i AccessExclusive-läge, så medan det fungerar kan du inte ens göra SELECT på bordet.

Även om du är i version 9.4 eller senare kan du ge den CONCURRENTLY alternativ:

REFRESH MATERIALIZED VIEW CONCURRENTLY my_mv;

Detta kommer att få ett ExclusiveLock och blockerar inte SELECT frågor, men kan ha en större omkostnad (beror på mängden data som ändras, om få rader har ändrats kan det gå snabbare). Även om du fortfarande inte kan köra två REFRESH kommandon samtidigt.

Uppdatera manuellt

Det är ett alternativ att överväga. Speciellt i fall av dataladdning eller batchuppdateringar (t.ex. ett system som bara laddar massor av information/data efter långa tidsperioder) är det vanligt att ha operationer i slutet för att modifiera eller bearbeta data, så du kan enkelt inkludera en REFRESH operation i slutet av den.

Schemalägga REFRESH-åtgärden

Det första och ofta använda alternativet är att använda något schemaläggningssystem för att anropa uppdateringen, till exempel kan du konfigurera liknande i ett cron-jobb:

*/30 * * * * psql -d your_database -c "REFRESH MATERIALIZED VIEW CONCURRENTLY my_mv"

Och sedan kommer din materialiserade vy att uppdateras var 30:e minut.

Överväganden

Det här alternativet är riktigt bra, speciellt med CONCURRENTLY alternativet, men bara om du kan acceptera att data inte är 100 % uppdaterade hela tiden. Tänk på att även med eller utan CONCURRENTLY , REFRESH kommandot behöver köra hela frågan, så du måste ta den tid som behövs för att köra den inre frågan innan du överväger tiden för att schemalägga REFRESH .

Uppdaterar med en utlösare

Ett annat alternativ är att anropa REFRESH MATERIALIZED VIEW i en triggerfunktion, så här:

CREATE OR REPLACE FUNCTION tg_refresh_my_mv()
RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
    REFRESH MATERIALIZED VIEW CONCURRENTLY my_mv;
    RETURN NULL;
END;
$$;

Sedan, i alla tabeller som involverar ändringar av vyn, gör du:

CREATE TRIGGER tg_refresh_my_mv AFTER INSERT OR UPDATE OR DELETE
ON table_name
FOR EACH STATEMENT EXECUTE PROCEDURE tg_refresh_my_mv();

Överväganden

Den har några kritiska fallgropar för prestanda och samtidighet:

  1. Alla INSERT/UPDATE/DELETE-operationer måste köra frågan (vilket är möjligt långsamt om du överväger MV);
  2. Även med CONCURRENTLY , en REFRESH blockerar fortfarande en annan, så alla INSERT/UPDATE/DELETE på de inblandade tabellerna kommer att serialiseras.

Den enda situation som jag kan tycka som en bra idé är om förändringarna verkligen är sällsynta.

Uppdatera med LISTEN/NOTIFY

Problemet med det tidigare alternativet är att det är synkront och medför stora omkostnader vid varje operation. För att förbättra det kan du använda en trigger som tidigare, men som bara anropar 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å då kan du bygga en applikation som håller kontakten och använder LISTEN operation för att identifiera behovet av att anropa REFRESH . Ett trevligt projekt som du kan använda för att testa detta är pgsidekick, med detta projekt kan du använda skalskript för att göra LISTEN , så att du kan schemalägga REFRESH som:

pglisten --listen=refresh_mv --print0 | xargs -0 -n1 -I? psql -d your_database -c "REFRESH MATERIALIZED VIEW CONCURRENTLY ?;"

Eller använd pglater (även inuti pgsidekick ) för att se till att du inte ringer REFRESH väldigt ofta. Du kan till exempel använda följande trigger för att göra den REFRESH , men inom 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å det kommer inte att anropa REFRESH på mindre än 60 sekunders mellanrum, och även om du NOTIFY många gånger på mindre än 60 sekunder visas REFRESH utlöses endast en gång.

Överväganden

Som cron-alternativ är den här också bra bara om du kan blotta med lite inaktuell data, men detta har fördelen att REFRESH anropas endast när det verkligen behövs, så du har mindre omkostnader, och även data uppdateras närmare när det behövs.

OBS:Jag har inte riktigt provat koderna och exemplen än, så om någon hittar ett fel, stavfel eller försöker och fungerar (eller inte), vänligen meddela mig.



  1. SQL - Hitta sats som infogar specifika värden

  2. IF-THEN-ELSE-satser i postgresql

  3. Finns det något sätt att visa en WHERE-sats bara för ett fält i MySQL?

  4. TSQL:Hur konverterar man lokal tid till UTC? (SQL Server 2008)