Je travaille sur un projet Web qui implique un contenu modifiable par l'utilisateur, et j'aimerais pouvoir faire un suivi de version du contenu réel, qui vit dans une base de données. Fondamentalement, je veux implémenter des historiques de changements de style wiki.
En faisant des recherches de base, je vois beaucoup de documentation sur la façon de mettre à jour votre schéma de base de données (le mien est en fait déjà contrôlé), mais toutes les stratégies existantes sur la façon de suivre les modifications du contenu de votre base de données sont perdues dans l'avalanche de trucs de version de schéma, au moins dans mes recherches.
Je peux penser à quelques façons d'implémenter mon propre suivi des modifications, mais elles semblent toutes plutôt grossières:
- Enregistrez la ligne entière à chaque changement, reliez la ligne à l'ID source avec une clé primaire (ce vers quoi je me penche actuellement, c'est la plus simple). Beaucoup de petits changements pourraient cependant produire beaucoup de ballonnement.
- enregistrer avant / après / utilisateur / horodatage pour chaque modification, avec un nom de colonne pour relier la modification à la colonne appropriée.
- enregistrer avant / après / utilisateur / horodatage avec une table pour chaque colonne (entraînerait trop de tables).
- enregistrer les différences / utilisateur / horodatage pour chaque modification avec une colonne (cela signifierait que vous devrez parcourir l'intégralité de l'historique des modifications pour revenir à une certaine date).
Quelle est la meilleure approche ici? Rouler le mien semble que je réinvente probablement la (meilleure) base de code de quelqu'un d'autre.
Points bonus pour PostgreSQL.
la source
Réponses:
La technique que j'ai normalement utilisée est de sauvegarder l'enregistrement complet, avec un champ end_timestamp. Il existe une règle commerciale selon laquelle une seule ligne peut avoir un horodatage final nul, et c'est bien sûr le contenu actuellement actif.
Si vous adoptez ce système, je vous recommande fortement d'ajouter un index ou une contrainte pour appliquer la règle. C'est facile avec Oracle, car un index unique peut contenir un et un seul null. D'autres bases de données peuvent être plus problématiques. Faire appliquer la règle par la base de données gardera votre code honnête.
Vous avez tout à fait raison de dire que de nombreux petits changements créeront des ballonnements, mais vous devez les échanger contre du code et de la simplicité des rapports.
la source
Notez que si vous utilisez Microsoft SQL Server, il existe déjà une fonctionnalité appelée Change Data Capture . Vous devrez toujours écrire du code pour accéder aux révisions précédentes plus tard (CDC crée des vues spécifiques pour cela), mais au moins vous n'avez pas à modifier le schéma de vos tables, ni implémenter le suivi des modifications lui-même.
Sous le capot , ce qui se passe, c'est que:
CDC crée un tableau supplémentaire contenant les révisions,
Votre tableau d'origine est utilisé tel qu'il était auparavant, c'est-à-dire que toute mise à jour est reflétée directement dans ce tableau,
La table CDC ne stocke que les valeurs modifiées, ce qui signifie que la duplication des données est réduite au minimum.
Le fait que les modifications soient stockées dans une table différente a deux conséquences majeures:
Les sélections de la table d'origine sont aussi rapides que sans CDC. Si je me souviens bien, CDC se produit après la mise à jour, donc les mises à jour sont tout aussi rapides (bien que je ne me souvienne pas bien de la façon dont CDC gère la cohérence des données).
Certaines modifications apportées au schéma de la table d'origine entraînent la suppression du CDC. Par exemple, si vous ajoutez une colonne, CDC ne sait pas comment gérer cela. D'un autre côté, l'ajout d'un index ou d'une contrainte devrait convenir. Cela devient rapidement un problème si vous activez CDC sur une table qui est sujette à des changements fréquents. Il pourrait y avoir une solution permettant de changer le schéma sans perdre le CDC, mais je ne l'ai pas recherchée.
la source
Résolvez le problème "philosophiquement" et dans le code en premier. Et puis "négocier" avec le code et la base de données pour y arriver.
Par exemple , si vous traitez des articles génériques, un concept initial pour un article pourrait ressembler à ceci:
Et au niveau suivant le plus basique, je veux garder une liste de révisions:
Et je pourrais peut-être comprendre que le corps actuel n'est que la dernière révision. Et cela signifie deux choses: j'ai besoin que chaque révision soit datée ou numérotée:
Et ... et le corps actuel de l'article n'a pas besoin d'être distinct de la dernière révision:
Quelques détails manquent; mais cela montre que vous voulez probablement deux entités . L'un représente l'article (ou un autre type d'en-tête), et l'autre est une liste de révisions (regrouper les champs qui ont un bon sens "philosophique" à grouper). Vous n'avez pas besoin de contraintes de base de données spéciales au départ, car votre code ne se soucie d'aucune des révisions en soi - ce sont les propriétés d'un article qui connaît les révisions.
Ainsi, vous n'avez pas à vous soucier de signaler les révisions d'une manière spéciale ou de vous appuyer sur une contrainte de base de données pour marquer l'article "actuel". Il vous suffit de les horodater (même un identifiant automatique serait OK), de les rendre liés à leur article parent et de laisser l'article en charge de savoir que le "dernier" est le plus pertinent.
Et vous laissez un ORM gérer les détails les moins philosophiques - ou vous les cachez dans une classe utilitaire personnalisée si vous n'utilisez pas un ORM prêt à l'emploi.
Beaucoup plus tard, après avoir effectué des tests de résistance, vous pouvez penser à faire de cette propriété de révision lazy-load, ou avoir votre attribut Body lazy-load uniquement la révision la plus haute. Mais, dans ce cas, votre structure de données ne devrait pas avoir à changer pour tenir compte de ces optimisations.
la source
Il existe une page wiki PostgreSQL pour un déclencheur de suivi d'audit qui vous explique comment configurer un journal d'audit qui fera ce dont vous avez besoin.
Il suit les données d'origine complètes d'une modification, ainsi que la liste des nouvelles valeurs pour les mises à jour (pour les insertions et les suppressions, il n'y a qu'une seule valeur). Si vous souhaitez restaurer une ancienne version, vous pouvez récupérer la copie des données d'origine de l'enregistrement d'audit. Notez que si vos données impliquent des clés étrangères, ces enregistrements peuvent également devoir être annulés pour maintenir la cohérence.
De manière générale, si votre application de base de données passe la plupart de son temps uniquement aux données actuelles, je pense que vous feriez mieux de suivre les versions alternatives dans un tableau distinct des données actuelles. Cela gardera vos index de table actifs plus gérables.
Si les lignes que vous suivez sont très grandes et que l'espace est un problème grave, vous pouvez essayer de décomposer les modifications et de stocker des différences / correctifs minimaux, mais c'est certainement plus de travail pour couvrir tous vos types de types de données. J'ai déjà fait cela auparavant, et c'était difficile de reconstruire les anciennes versions des données en parcourant toutes les modifications en arrière, une à la fois.
la source
Eh bien, je me suis contenté de l'option la plus simple, un déclencheur qui copie l'ancienne version d'une ligne dans un journal d'historique par table.
Si je me retrouve avec trop de ballonnement dans la base de données, je peux envisager de réduire éventuellement certains des changements mineurs de l'historique, si nécessaire.
La solution s'est avérée plutôt désordonnée, car je voulais générer automatiquement les fonctions de déclenchement. Je suis SQLAlchemy, j'ai donc pu produire la table d'historique en faisant des hijinks d'héritage, ce qui était bien, mais les fonctions de déclenchement réelles ont fini par nécessiter un couplage de chaînes pour générer correctement les fonctions PostgreSQL et mapper les colonnes d'une table à un autre correctement.
Quoi qu'il en soit, tout est sur github ici .
la source