Comment les suppressions doivent-elles être gérées dans la base de données?

44

J'aimerais implémenter une fonctionnalité "Annulation de la suppression" dans une application Web, de sorte qu'un utilisateur puisse changer d'avis et récupérer un enregistrement supprimé. Réflexions sur la façon de mettre en œuvre cela? Certaines options que j'ai envisagées consistent en fait à supprimer l'enregistrement en question et à stocker les modifications dans une table d'audit distincte, ou à ne pas supprimer l'enregistrement et à utiliser une colonne booléenne "supprimé" pour le marquer comme supprimé. Cette dernière solution nécessiterait une logique d'application supplémentaire pour ignorer les enregistrements "supprimés" dans des circonstances normales, mais faciliterait beaucoup la mise en œuvre de la récupération des enregistrements du côté de l'application.

Abie
la source
J'ai oublié de mentionner que dans le second cas, les enregistrements signalés devraient être supprimés ou déplacés après un laps de temps raisonnable.
Abie
Quelle base de données utilisez-vous?
Evan Carroll
La table temporelle est la meilleure solution pour SQL Server 2016 et versions ultérieures.
Sameer le

Réponses:

37

Oui, je choisirais certainement la deuxième option, mais j'ajouterais un champ de plus un champ de date.

Donc vous ajoutez:

delete       boolean
delete_date  timestamp

Cela vous laisserait un temps pour l'action de suppression.

Si le temps est inférieur à une heure, on peut récupérer.

Pour vraiment supprimer l’entrée supprimée, créez une procédure stockée qui nettoiera chaque entrée avec delete défini sur true et une heure supérieure à une heure.

L'heure n'est qu'un exemple.

Spredzy
la source
Vous pouvez également avoir un autre indicateur - cleanedou quelque chose - qui indique que les données associées à cet enregistrement ont été supprimées de manière appropriée et complète. L'enregistrement peut être non supprimé sauf si cleanedvrai, auquel cas il est irrécupérable.
Gaurav
14
C'est l'approche commune. J'utilise habituellement un champ deleted_atcontenant à la fois la sémantique du deletebooléen et l' delete_datehorodatage. Si deleted_atest - NULLgérer le cas deleteest FALSEet delete_dateest NULL, deleted_atcontenant une poignée d'horodatage le cas deleteest TRUEet delete_datecontient un horodatage, vous permet d' économiser un temps, le stockage et la logique application.
Julien le
1
J'aime le champ booléen et la date. Selon la manière dont vous implémentez la logique de suppression, vous pouvez même disposer d'une table distincte contenant la date et la clé unique de l'enregistrement "supprimé". Les procédures stockées rendent cela facile. Cela prend l'espace supplémentaire requis par ligne jusqu'à 1 bit contre 8+. Vous pourrez également signaler les suppressions par jour sans toucher à la table source.
AndrewSQL
Remarque: supprimer est un mot réservé dans MySQL.
Jason Rikard
N'oubliez pas qu'un index filtré sur votre deletedchamp peut améliorer considérablement les performances lorsque vous interrogez des lignes non supprimées
Ross Presser
21

Dans nos applications , nous ne sommes pas vraiment quoi que ce soit à un effacement des utilisateurs demandent de toute façon (nos clients sont dans des environnements réglementés où tout peut supprimer potentiellement conduire à des problèmes juridiques).

Nous conservons les anciennes versions dans une table d'audit distincte (donc pour la table some_table où se trouve également une table appelée some_table_audit), qui est identique à la différence qu'un identificateur de version supplémentaire (un horodatage si votre base de données de sauvegarde est suffisamment granulaire, un numéro de version entier). ou UUID qui est une clé étrangère d’une table d’audit général, etc.) et met à jour la table d’audit automatiquement par déclencheur (nous n’avons donc pas besoin de mettre tout le code qui met à jour les enregistrements au courant des exigences de l’audit).

Par ici:

  • l'opération de suppression n'est qu'une simple suppression - inutile d'ajouter de code supplémentaire (vous pouvez toutefois indiquer qui a demandé quelles lignes doivent être supprimées, même si elles ne sont pas réellement supprimées)
  • les insertions et les mises à jour sont également simples
  • vous pouvez implémenter undelete ou revert en retournant simplement la ligne "normale" à une ancienne version (le déclencheur d'audit se déclenchera à nouveau de sorte que le tableau des traces d'audit reflète également ce changement)
  • vous pouvez offrir la possibilité de réviser ou de revenir à n'importe quelle version antérieure, pas seulement de restaurer la dernière
  • vous n'êtes pas obligé d'ajouter "est marqué comme supprimé?" vérifie chaque point de code faisant référence à la table en question ou la logique "mettre à jour la copie d'audit" à chaque point de code supprimant / mettant à jour les lignes (vous devez toutefois décider quoi faire des lignes supprimées dans la table d'audit: nous avons un indicateur supprimé / non pour chaque version, il n'y a donc pas de trou dans l'historique si les enregistrements sont supprimés puis non supprimés)
  • conserver les copies d'audit dans un tableau distinct signifie que vous pouvez facilement les partitionner en différents groupes de fichiers.

Si vous utilisez un horodatage à la place (ou aussi bien) d'un numéro de version entier, vous pouvez l'utiliser pour supprimer les copies les plus anciennes après un laps de temps défini, si nécessaire. Mais l’espace disque est relativement bon marché de nos jours, donc nous ne le ferions pas, sauf si nous avons des raisons de supprimer d’anciennes données (c’est-à-dire que la réglementation en matière de protection des données stipule que vous devez supprimer les données client après X mois / années).


Cette réponse remonte à quelques années et quelques éléments clés susceptibles d’affecter ce type de planification ont changé depuis. Je n'entrerai pas dans les détails, mais pour le bénéfice des lecteurs de cette page aujourd'hui:

  • SQL Server 2016 a introduit les "tables temporelles système versionnées" qui font beaucoup de ce travail pour vous, et plus encore, car un bon sucre syntaxique est fourni pour faciliter la construction et la maintenance des requêtes historiques, et coordonne un sous-ensemble de modifications de schéma entre tables de base et d'historique. Ils ne sont pas sans leurs mises en garde, mais ils sont un outil puissant pour ce genre de but. Des fonctionnalités similaires sont également disponibles dans d'autres systèmes de base de données.

  • Les modifications apportées à la législation sur la protection des données, en particulier l'introduction du RGPD, peuvent considérablement modifier le moment où les données doivent être supprimées. Vous devez peser le solde de ne pas supprimer les données qui pourraient être utiles (ou, en fait, requises par la loi) à des fins d'audit à une date ultérieure par rapport au besoin de respecter les droits des peuples (à la fois de manière générale et comme spécifiquement définis dans la législation pertinente) lorsque vous envisagez vos dessins. Cela peut être un problème avec les tables temporelles versionnées du système, car vous ne pouvez pas modifier l'historique pour purger des données personnelles sans modifications du schéma à court terme pour désactiver le suivi de l'historique pendant que vous apportez des modifications.

David Spillett
la source
Comment traitez-vous la suppression et le changement de nom de colonnes? Définir tout pour nullable?
Stijn
1
@Stijn: Il n'est pas fréquent que les structures changent et que cela ne se présente pas souvent. Les colunms ne sont généralement jamais supprimés une fois qu'ils existent en production - s'ils cessent d'être utilisés, supprimez simplement les contraintes qui les empêcheraient de passer à NULL (ou ajoutez des valeurs par défaut pour gérer les contraintes en utilisant une "valeur magique", bien que cela semble plus sale). et arrêtez de vous y référer dans un autre code. Pour les renommés: ajoutez un nouveau, arrêtez d'utiliser l'ancien et copiez les données de l'ancien au nouveau si nécessaire. Si vous renommez des colonnes, assurez-vous simplement que les mêmes modifications sont apportées aux tables de base et d'audit en même temps.
David Spillett
9

Avec une colonne booléenne supprimée, vous commencerez à avoir des problèmes si votre table commence à grossir et à devenir vraiment grosse. Je vous suggère de déplacer les colonnes supprimées une fois par semaine (plus ou moins en fonction de vos spécifications) dans une autre table. De cette façon, vous avez une belle petite table active et une grande qui contient tous les enregistrements rassemblés au fil du temps.

poelinca
la source
7

J'irais avec la table séparée. Ruby on Rails a un acts_as_versionedplugin, qui enregistre fondamentalement une ligne dans une autre table avec le suffixe _versionavant de le mettre à jour. Bien que vous n'ayez pas besoin de ce comportement exact, cela devrait également fonctionner pour votre cas (copie avant suppression).

Comme @Spredzy, je vous recommanderais également d'ajouter une delete_datecolonne pour pouvoir purger par programmation les enregistrements qui n'ont pas été restaurés après X heures / jours / peu importe.

Michael Kohl
la source
4

La solution que nous utilisons en interne dans ce domaine consiste à créer une colonne d’état avec certaines valeurs codées en dur pour certains états spécifiques de l’objet: Supprimé, Actif, Inactif, Ouvert, Fermé, Bloqué - chaque statut ayant une signification utilisée dans l’application. Du point de vue de la base de données, nous ne supprimons pas les objets, nous modifions simplement le statut et conservons l'historique pour chaque modification de la table d'objets.

Marian
la source
3

Lorsque vous dites que "cette dernière solution nécessiterait une logique d'application supplémentaire pour ignorer les enregistrements" supprimés "", la solution simple consiste à disposer d'une vue qui les filtre.

Peter Taylor
la source
Ce n'est pas juste une question de vue. Toute opération en cours sur l'ensemble devrait exclure les enregistrements "supprimés".
Abie
2

Comme ce que Spredzy a suggéré, nous utilisons un champ d’horodatage pour la suppression dans toutes nos applications. La valeur booléenne est superflue, car l'horodatage en cours de réglage indique que l'enregistrement a été supprimé. De cette façon, notre PDO ajoute toujours AND (deleted IS NULL OR deleted = 0)aux instructions select, à moins que le modèle demande explicitement que les enregistrements supprimés soient inclus.

Actuellement, nous ne collectons pas les ordures sur toutes les tables, sauf celles contenant des blobs ou des textes l'espace est trivial si les enregistrements sont bien normalisés et l'indexation du deletedchamp a un impact limité sur la vitesse de sélection.

Bryan Agee
la source
0

Vous pouvez également placer la charge sur les utilisateurs (et les développeurs) et choisir une séquence de type "Êtes-vous sûr?", "Êtes-vous vraiment sûr?" et 'Êtes-vous absolument certain?' les questions avant l'enregistrement sont supprimées. Légèrement facétieux mais à considérer.

YaHozna
la source
0

J'ai l'habitude de voir des rangées de tableaux avec des colonnes du type 'DeletedDate' et je ne les aime pas. La notion même de «supprimé» est que l'entrée n'aurait pas dû être faite en premier lieu. Pratiquement, ils ne peuvent pas être supprimés de la base de données mais je ne les veux pas avec mes données chaudes. Les lignes logiquement supprimées sont, par définition, des données froides sauf si quelqu'un veut spécifiquement voir les données supprimées.

De plus, chaque requête écrite doit les exclure spécifiquement et les index doivent également les prendre en compte.

Ce que j'aimerais voir, c'est un changement au niveau de l'architecture de la base de données et de l'application: créer un schéma appelé "supprimé". Chaque table définie par l'utilisateur a un équivalent identique dans le schéma "supprimé" avec un champ supplémentaire contenant des métadonnées - l'utilisateur qui l'a supprimée et quand. Les clés étrangères doivent être créées.

Ensuite, les suppressions deviennent des insert-deletes. Tout d'abord, la ligne à supprimer est insérée dans son équivalent de schéma «supprimé». La ligne en question dans la table principale peut alors être supprimée. Il faut cependant ajouter une logique supplémentaire quelque part sur la ligne. Les violations de clé étrangère peuvent être traitées.

Les clés étrangères doivent être manipulées correctement. Il est déconseillé de supprimer logiquement une ligne mais dont les colonnes primaire / unique ont des colonnes dans d’autres tables qui y font référence. Cela ne devrait pas arriver de toute façon. Un travail standard peut supprimer des lignes veuve (lignes dont les clés primaires ne sont référencées dans aucune autre table malgré la présence d'une clé étrangère. Il s'agit toutefois d'une logique métier.

L'avantage général est la réduction des métadonnées dans le tableau et l'amélioration des performances qu'il apporte. La colonne 'deleteDate' indique que cette ligne ne devrait pas être réellement présente mais, pour des raisons pratiques, nous la laissons là et laissons la requête SQL la gérer. Si une copie de la ligne supprimée est conservée dans un schéma "supprimé", la table principale contenant les données hot contient un pourcentage plus élevé de données hot (en supposant qu'elles soient archivées à temps) et moins de colonnes de métadonnées inutiles. Les index et les requêtes n'ont plus besoin de prendre en compte ce champ. Plus la taille des lignes est courte, plus le nombre de lignes pouvant être insérées sur une page est élevé, plus SQL Server peut fonctionner rapidement.

Le principal inconvénient est la taille de l'opération. Il y a maintenant deux opérations au lieu d'une, ainsi que la logique supplémentaire et le traitement des erreurs. Cela peut entraîner plus de verrous que la mise à jour d'une seule colonne prendrait autrement. La transaction maintient les verrous sur la table plus longtemps et deux tables sont impliquées. La suppression des données de production, du moins selon mon expérience, est chose rare. Même dans l'une des tables principales, 7,5% des presque 100 millions d'entrées ont une entrée dans la colonne 'DeletedDate'.

En réponse à la question, l'application devrait être consciente de la «suppression complète». Il suffirait simplement de faire la même chose dans l'ordre inverse: insérez la ligne du schéma "supprimé" dans la table principale, puis supprimez la ligne du "schéma supprimé". Encore une fois, une logique et une gestion des erreurs supplémentaires sont nécessaires pour éviter les erreurs, les problèmes de clés étrangères, etc.

Sean Redmond
la source