Conception de base de données pour la journalisation d'audit

151

Chaque fois que j'ai besoin de concevoir une nouvelle base de données, je passe un certain temps à réfléchir à la façon dont je dois configurer le schéma de base de données pour conserver un journal d'audit des modifications.

Certaines questions ont déjà été posées ici à ce sujet, mais je ne suis pas d'accord pour dire qu'il existe une seule meilleure approche pour tous les scénarios:

Je suis également tombé sur cet article intéressant sur la gestion d'un journal des modifications de la base de données qui tente de répertorier les avantages et les inconvénients de chaque approche. Il est très bien écrit et contient des informations intéressantes, mais cela a rendu mes décisions encore plus difficiles.

Ma question est la suivante: y a-t-il une référence que je peux utiliser, peut-être un livre ou quelque chose comme un arbre de décision auquel je peux me référer pour décider dans quelle direction dois-je aller en fonction de certaines variables d'entrée, comme:

  • La maturité du schéma de base de données
  • Comment les journaux seront interrogés
  • La probabilité qu'il soit nécessaire de recréer des enregistrements
  • Le plus important: les performances d'écriture ou de lecture
  • Nature des valeurs enregistrées (chaîne, nombres, blobs)
  • Espace de stockage disponible

Les approches que je connais sont:

1. Ajoutez des colonnes pour la date et l'utilisateur créés et modifiés

Exemple de tableau:

  • id
  • valeur_1
  • valeur_2
  • valeur_3
  • created_date
  • date modifiée
  • créé par
  • modifié par

Principaux inconvénients: Nous perdons l'historique des modifications. Impossible de revenir en arrière après la validation.

2. Insérez uniquement des tableaux

Exemple de tableau :

  • id
  • valeur_1
  • valeur_2
  • valeur_3
  • de
  • à
  • supprimé (booléen)
  • utilisateur

Principaux inconvénients: comment garder les clés étrangères à jour? Grand espace nécessaire

3. Créez une table d'historique séparée pour chaque table

Exemple de table d'historique:

  • id
  • valeur_1
  • valeur_2
  • valeur_3
  • valeur_4
  • utilisateur
  • supprimé (booléen)
  • horodatage

Principaux inconvénients: Nécessite de dupliquer toutes les tables vérifiées. Si le schéma change, il sera également nécessaire de migrer tous les journaux.

4. Créer une table d'historique consolidée pour toutes les tables

Exemple de table d'historique:

  • nom de la table
  • champ
  • utilisateur
  • nouvelle valeur
  • supprimé (booléen)
  • horodatage

Principaux inconvénients: Serai-je capable de recréer facilement les enregistrements (restauration) si nécessaire? La colonne new_value doit être une chaîne énorme pour pouvoir prendre en charge tous les types de colonnes différents.

jbochi
la source
1
et qu'en est-il de l'utilisation d'une base de données d'historique au lieu de tables?
Jowen
Peut-être que vous pourriez vérifier la conception de github.com/airblade/paper_trail
zx1986
Est-ce une mauvaise idée de consigner toutes les requêtes (obligatoires) exécutées telles quelles?
Dinushan

Réponses:

87

Une méthode utilisée par quelques plates-formes wiki consiste à séparer les données d'identification et le contenu que vous auditez. Cela ajoute de la complexité, mais vous vous retrouvez avec une piste d'audit des enregistrements complets, pas seulement des listes de champs qui ont été modifiés que vous devez ensuite mélanger pour donner à l'utilisateur une idée de ce à quoi ressemblait l'ancien enregistrement.

Ainsi, par exemple, si vous aviez une table appelée Opportunités pour suivre les offres de vente, vous créeriez en fait deux tables distinctes:

Opportunités
Opportunités_Contenu (ou quelque chose comme ça)

La table Opportunities contiendrait des informations que vous utiliseriez pour identifier de manière unique l'enregistrement et hébergerait la clé primaire que vous référeriez pour vos relations de clé étrangère. La table Opportunities_Content contiendrait tous les champs que vos utilisateurs peuvent modifier et pour lesquels vous souhaitez conserver une piste d'audit. Chaque enregistrement de la table Contenu comprendra sa propre PK et les données de date de péremption et de modification. Le tableau Opportunités comprendrait une référence à la version actuelle ainsi que des informations sur la date à laquelle l'enregistrement principal a été créé et par qui.

Voici un exemple simple:

CREATE TABLE dbo.Page(  
    ID int PRIMARY KEY,  
    Name nvarchar(200) NOT NULL,  
    CreatedByName nvarchar(100) NOT NULL, 
    CurrentRevision int NOT NULL, 
    CreatedDateTime datetime NOT NULL

Et le contenu:

CREATE TABLE dbo.PageContent(
    PageID int NOT NULL,
    Revision int NOT NULL,
    Title nvarchar(200) NOT NULL,
    User nvarchar(100) NOT NULL,
    LastModified datetime NOT NULL,
    Comment nvarchar(300) NULL,
    Content nvarchar(max) NOT NULL,
    Description nvarchar(200) NULL

Je ferais probablement du PK de la table des matières une clé multi-colonnes à partir de PageID et Revision à condition que la révision soit un type d'identité. Vous utiliseriez la colonne Révision comme FK. Vous tirez ensuite l'enregistrement consolidé en vous joignant comme ceci:

SELECT * FROM Page
JOIN PageContent ON CurrentRevision = Revision AND ID = PageID

Il y a peut-être des erreurs là-haut ... ça me vient à l'esprit. Cela devrait cependant vous donner une idée d'un modèle alternatif.

Josh Anderson
la source
10
En termes de bonne approche d'audit, mais pour la production, il faudra beaucoup de temps pour développer une table d'audit séparée pour chaque table de la base de données, écrire des déclencheurs pour chaque table pour capturer les modifications et l'écrire dans la table d'audit. En outre, il est difficile de développer un rapport d'audit unique pour toutes les tables, car chaque table d'audit est de structure différente.
asim-ishaq
11
Si l'écriture et la maintenance de scripts pour chaque table sont une préoccupation pour une organisation qui a l'intention de gérer une base de données auditée, je recommanderais naturellement qu'elle embauche soit un DBA expérimenté, soit un ingénieur logiciel très flexible et très expérimenté avec une expérience adéquate dans la création de bases de données auditées. .
Hardryv
1
Est-il exact que PageContent.PageIDFK est à Page.IDet Page.CurrentRevisionFK à PageContent.Revision? Cette dépendance est-elle vraiment circulaire?
2
J'ai voté à la baisse car il ne traite pas des alternatives mentionnées. Cela donne une autre option qui est une solution très spécifique à un cas d'utilisation très spécifique. Mais je vois les mérites de la conception suggérée
acteon
1
Je peux penser à très peu de champs que je pourrais dire avec confiance ne changeront pas ainsi toutes les tables "principales" pour chaque entité finiraient par être simplement id, revision_id; plus une table de jonction, vraiment. Cela me semble un peu malodorant. Quel avantage cela a-t-il par rapport à l'approche 3 en OP (table d'historique par table auditée)?
Kenmore
14

Si vous utilisez SQL Server 2008, vous devriez probablement envisager de modifier la capture de données. Ceci est nouveau pour 2008 et pourrait vous faire économiser beaucoup de travail.

Randy Minder
la source
voici le lien vers les informations de suivi des modifications SQL 2012. msdn.microsoft.com/en-us/library/bb933994.aspx +1 pour utiliser les fonctionnalités intégrées, inutile de réinventer la roue.
Chris
4
@Chris l'avez-vous déjà utilisé vous-même? En effet, il suit tout ... mais pouvoir en tirer des informations utiles est une toute autre histoire. Je ne peux pas utiliser une roue de tracteur pour mon vélo.
Jowen
Cela aurait vraiment été génial. Mais si vous n'avez que l' édition Standard de SQL Server, comme moi, vous n'avez pas de chance: "La capture de données modifiées n'est disponible que dans les éditions Enterprise , Developer et Enterprise Evaluation ".
Brad Turek
6

Je ne connais aucune référence, mais je suis sûr que quelqu'un a écrit quelque chose.

Cependant, si le but est simplement d'avoir un enregistrement de ce qui s'est passé - l'utilisation la plus typique d'un journal d'audit - alors pourquoi ne pas simplement tout garder:

timestamp
username
ip_address
procedureName (if called from a stored procedure)
database
table
field
accesstype (insert, delete, modify)
oldvalue
newvalue

Vraisemblablement, cela est maintenu par un déclencheur.

marcher
la source
Je ne connais aucun moyen d'obtenir cela dans le serveur de base de données, mais bien sûr, cela pourrait être fait de l'extérieur assez facilement.
wallyk
5
Il me semble que c'est le même modèle de conception que la 4ème option montrée dans la question initiale.
givanse
3

Nous allons créer un petit exemple de base de données pour une application de blog. Deux tableaux sont nécessaires:

blog: stocke un ID de publication unique, le titre, le contenu et un indicateur supprimé. audit: stocke un ensemble de base de modifications historiques avec un ID d'enregistrement, l'ID de publication de blog, le type de modification (NOUVEAU, MODIFIER ou SUPPRIMER) et la date / heure de cette modification. Le SQL suivant crée bloget indexe la colonne supprimée:

CREATE TABLE `blog` (
    `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
    `title` text,
    `content` text,
    `deleted` tinyint(1) unsigned NOT NULL DEFAULT '0',
    PRIMARY KEY (`id`),
    KEY `ix_deleted` (`deleted`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='Blog posts';

Le SQL suivant crée la audittable. Toutes les colonnes sont indexées et une clé étrangère est définie pour audit.blog_id qui fait référence à blog.id. Par conséquent, lorsque nous SUPPRIMONS physiquement une entrée de blog, son historique d'audit complet est également supprimé.

CREATE TABLE `audit` (
    `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
    `blog_id` mediumint(8) unsigned NOT NULL,
    `changetype` enum('NEW','EDIT','DELETE') NOT NULL,
    `changetime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`),
    KEY `ix_blog_id` (`blog_id`),
    KEY `ix_changetype` (`changetype`),
    KEY `ix_changetime` (`changetime`),
    CONSTRAINT `FK_audit_blog_id` FOREIGN KEY (`blog_id`) REFERENCES `blog` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
ajit
la source
2

Je pense qu'il n'y a rien de tel qu'un arbre de décision. Étant donné que certains des avantages et des inconvénients (ou des exigences) ne sont pas vraiment dénombrables. Comment mesurez-vous la maturité par exemple?

Alignez donc simplement vos besoins commerciaux pour votre journalisation d'audit. Essayez de prédire comment ces exigences pourraient changer à l'avenir et de générer vos exigences techniques. Vous pouvez maintenant le comparer aux avantages et aux inconvénients et choisir la bonne / meilleure option.

Et soyez assuré que peu importe la façon dont vous décidez, il y aura toujours quelqu'un qui pensera que vous avez pris la mauvaise décision. Cependant, vous avez fait vos devoirs et vous justifiez votre décision.

Peter Schuetze
la source