Est-il possible d'actualiser une vue matérialisée de manière incrémentielle dans PostgreSQL, c'est-à-dire uniquement pour les données nouvelles ou modifiées?
Considérez cette table et vue matérialisée:
CREATE TABLE graph (
xaxis integer NOT NULL,
value integer NOT NULL,
);
CREATE MATERIALIZED VIEW graph_avg AS
SELECT xaxis, AVG(value)
FROM graph
GROUP BY xaxis
Périodiquement, de nouvelles valeurs sont ajoutées à graph
ou une valeur existante est mise à jour. Je souhaite rafraîchir la vue graph_avg
toutes les deux heures uniquement pour les valeurs mises à jour. Cependant, dans PostgreSQL 9.3, la table entière est actualisée. Cela prend beaucoup de temps. La prochaine version 9.4 permet la mise à jour de CONCURRENT
mais actualise toujours la vue entière. Avec des centaines de millions de lignes, cela prend quelques minutes.
Quel est un bon moyen de garder une trace des valeurs mises à jour et nouvelles et de ne rafraîchir la vue que partiellement?
Vous pouvez toujours implémenter votre propre table servant de "vue matérialisée". C'est ainsi que nous le faisions avant MATERIALIZED VIEW
a été implémenté dans Postgres 9.3.
Vous pouvez créer un simple VIEW
:
CREATE VIEW graph_avg_view AS
SELECT xaxis, AVG(value) AS avg_val
FROM graph
GROUP BY xaxis;
Et matérialisez le résultat une fois ou chaque fois que vous devez recommencer:
CREATE TABLE graph_avg AS
SELECT * FROM graph_avg_view;
(Ou utilisez directement l'instruction SELECT
, sans créer de VIEW
.)
Ensuite, selon les détails non divulgués de votre cas d'utilisation, vous pouvez DELETE
/UPDATE
/INSERT
modifier manuellement.
Une instruction DML de base avec des CTE modificateurs de données pour votre table tel quel:
En supposant que personne d'autre n'essaye de - écrire à graph_avg
Simultanément (la lecture n'est pas un problème):
WITH del AS (
DELETE FROM graph_avg t
WHERE NOT EXISTS (SELECT FROM graph_avg_view WHERE xaxis = t.xaxis)
)
, upd AS (
UPDATE graph_avg t
SET avg_val = v.avg_val
FROM graph_avg_view v
WHERE t.xaxis = v.xaxis
AND t.avg_val <> v.avg_val
-- AND t.avg_val IS DISTINCT FROM v.avg_val -- alt if avg_val can be NULL
)
INSERT INTO graph_avg t -- no target list, whole row
SELECT v.*
FROM graph_avg_view v
WHERE NOT EXISTS (SELECT FROM graph_avg WHERE xaxis = v.xaxis);
timestamp
avec la fonction par défaut now()
à votre table de base. Appelons cela ts
. xaxis
ou value
.Créez une petite table pour mémoriser l'horodatage de votre dernier instantané. Appelons ça mv
:
CREATE TABLE mv (
tbl text PRIMARY KEY
, ts timestamp NOT NULL DEFAULT '-infinity'
); -- possibly more details
Créez cet index partiel à plusieurs colonnes:
CREATE INDEX graph_mv_latest ON graph (xaxis, value)
WHERE ts >= '-infinity';
Utilisez l'horodatage de l'instantané dernier comme prédicat dans vos requêtes pour actualiser l'instantané avec une utilisation parfaite de l'index.
À la fin de la transaction, supprimez l'index et recréez-le avec l'horodatage de la transaction en remplaçant l'horodatage dans le prédicat d'index (initialement '-infinity'
), Que vous enregistrez également dans votre table. Tout dans un transaction.
Notez que l'index partiel est idéal pour couvrir les opérations INSERT
et UPDATE
, mais pas DELETE
. Pour couvrir cela, vous devez considérer l'ensemble du tableau. Tout dépend des exigences exactes.
Bien qu'il ne s'agisse pas d'une mise à jour incrémentielle comme vous l'avez demandé, Postgres 9.4 fournit une nouvelle fonctionnalité mise à jour simultanée .
Pour citer le doc…
Avant PostgreSQL 9.4, l'actualisation d'une vue matérialisée signifiait verrouiller la table entière, et donc empêcher tout interrogation, et si une actualisation prenait beaucoup de temps pour acquérir le verrou exclusif (en attendant que les requêtes l'utilisant se terminent), elle à son tour suspend les requêtes suivantes. Cela peut maintenant être atténué avec le mot clé CONCURRENTLY:
postgres=# REFRESH MATERIALIZED VIEW CONCURRENTLY mv_data;
Un index unique devra cependant exister sur la vue matérialisée. Au lieu de verrouiller la vue matérialisée, il en crée une version mise à jour temporaire, compare les deux versions, puis applique INSERT et DELETE à la vue matérialisée pour appliquer la différence. Cela signifie que les requêtes peuvent toujours utiliser la vue matérialisée pendant sa mise à jour. Contrairement à sa forme non simultanée, les tuples ne sont pas figés, et il a besoin de VACUUMing en raison des SUPPRIMÉS susmentionnés qui laisseront les tuples morts derrière.
Cette mise à jour simultanée exécute toujours une nouvelle requête complète (non incrémentielle). CONCURRENTLY n'économise donc pas sur le temps de calcul global, il minimise simplement le temps pendant lequel votre vue matérialisée n'est pas disponible pendant sa mise à jour.