web-dev-qa-db-fra.com

Actualiser automatiquement une vue matérialisée à l'aide d'une règle ou notifier

J'ai une vue matérialisée sur une base de données PostgreSQL 9.3 qui change rarement (environ deux fois par jour). Mais quand cela se produit, j'aimerais mettre à jour rapidement ses données.

Voici ce à quoi je pensais jusqu'à présent:

Il y a une vue matérialisée mat_view qui tire ses données des tables table1 et table2 en utilisant une déclaration de jointure.

Chaque fois que quelque chose dans table1 ou table2 changements, j’ai déjà un déclencheur qui met à jour une petite table de configuration config composée de

table_name | mat_view_name | need_update
-----------+---------------+------------
table1     | mat_view      | TRUE/FALSE
table2     | mat_view      | TRUE/FALSE

Donc, si quelque chose dans table1 changements (il y a un déclencheur sur UPDATE et sur DELETE pour chaque instruction), le champ need_update dans la première ligne est défini sur TRUE. C'est la même chose pour table2 et la deuxième rangée.

Évidemment, si need_update est VRAI, la vue matérialisée doit être actualisée.

UPDATE : Puisque les vues matérialisées ne supportent pas les règles (comme @pozs mentionné dans un commentaire ci-dessous), j'irais encore plus loin. Je créerais une vue factice v_mat_view avec la définition "SELECT * FROM mat_view ". Lorsque l'utilisateur effectue une opération SELECT sur cette vue, je dois créer une règle ON SELECT qui effectue les opérations suivantes:

  • vérifier si mat_view devrait être mis à jour (SELECT 1 FROM config WHERE mat_view_name='mat_view' AND need_update=TRUE)
  • réinitialiser le need_update drapeau avec UPDATE config SET need_update=FALSE where mat_view_name='mat_view'
  • REFRESH MATERIALIZED VIEW mat_view
  • et enfin faire l'instruction SELECT d'origine mais avec mat_view comme cible.

PDATE2: J'ai essayé de créer les étapes ci-dessus:

Créez une fonction qui gère les quatre points mentionnés ci-dessus:

CREATE OR REPLACE FUNCTION mat_view_selector()
RETURNS SETOF mat_view AS $body$
BEGIN
  -- here is checking whether to refresh the mat_view
  -- then return the select:
  RETURN QUERY SELECT * FROM mat_view;
END;
$body$ LANGUAGE plpgsql;

Créer la vue v_mat_view qui sélectionne vraiment à partir de la fonction mat_view_selector:

CREATE TABLE v_mat_view AS SELECT * from mat_view LIMIT 1;
DELETE FROM v_mat_view;

CREATE RULE "_RETURN" AS
    ON SELECT TO v_mat_view
    DO INSTEAD 
        SELECT * FROM mat_view_selector();
    -- this also converts the empty table 'v_mat_view' into a view.

Le résultat est insatisfaisant:

# explain analyze select field1 from v_mat_view where field2 = 44;
QUERY PLAN
Function Scan on mat_view_selector (cost=0.25..12.75 rows=5 width=4)
(actual time=15.457..18.048 rows=1 loops=1)
Filter: (field2 = 44)
Rows Removed by Filter: 20021
Total runtime: 31.753 ms

par rapport à la sélection dans la mat_view elle-même:

# explain analyze select field1 from mat_view where field2 = 44;
QUERY PLAN
Index Scan using mat_view_field2 on mat_view (cost=0.29..8.30 rows=1 width=4)
  (actual time=0.015..0.016 rows=1 loops=1)
Index Cond: (field2 = 44)
Total runtime: 0.036 ms

Donc, en gros, ça marche, mais la performance peut être un problème.

Quelqu'un a de meilleures idées? Sinon, je devrais l'implémenter d'une manière ou d'une autre dans la logique de l'application ou pire: exécuter un simple cronjob qui s'exécute toutes les minutes environ. :-(

59
mawimawi

PostgreSQL 9.4 a ajouté REFRESH CONCURRENTLY en vues matérialisées.

C'est peut-être ce que vous recherchez lorsque vous décrivez la tentative d'installation d'une mise à jour asynchrone de la vue matérialisée.

Les utilisateurs qui sélectionnent dans la vue matérialisée verront des données incorrectes jusqu'à la fin de l'actualisation, mais dans de nombreux scénarios utilisant une vue matérialisée, il s'agit d'un compromis acceptable.

Utilisez un déclencheur de niveau instruction qui surveille les modifications apportées dans les tables sous-jacentes, puis actualise la vue matérialisée simultanément.

25
Jeff Widman

Vous devez actualiser la vue dans les déclencheurs après insertion/mise à jour/suppression/tronquage pour chaque instruction sur table1 et table2.

create or replace function refresh_mat_view()
returns trigger language plpgsql
as $$
begin
    refresh materialized view mat_view;
    return null;
end $$;

create trigger refresh_mat_view
after insert or update or delete or truncate
on table1 for each statement 
execute procedure refresh_mat_view();

create trigger refresh_mat_view
after insert or update or delete or truncate
on table2 for each statement 
execute procedure refresh_mat_view();

De cette façon, votre vue matérialisée est toujours à jour. Cette solution simple peut être difficile à accepter avec des insertions/mises à jour fréquentes et des sélections sporadiques. Dans votre cas (change rarement environ deux fois par jour), il correspond parfaitement à vos besoins.


Pour réaliser actualisation différée d'une vue matérialisée, vous devez disposer de l'une des fonctionnalités suivantes:

  • déclencheur asynchrone
  • déclencher avant de sélectionner
  • statuer sur select before

Postgres n'en a aucune, il semble donc qu'il n'y a pas de solution claire postgres.

En prenant cela en compte, je considérerais une fonction wrapper pour selects sur mat_view, par ex.

CREATE OR REPLACE FUNCTION select_from_mat_view(where_clause text)
RETURNS SETOF mat_view AS $body$
BEGIN
  -- here is checking whether to refresh the mat_view
  -- then return the select:
  RETURN QUERY EXECUTE FORMAT ('SELECT * FROM mat_view %s', where_clause);
END;
$body$ LANGUAGE plpgsql;

Si cela est acceptable dans la pratique, cela dépend de détails que je ne connais pas.

103
klin