Comment savoir qui a supprimé certaines données SQL Server


29

Mon patron a demandé hier à un client comment il pouvait savoir qui avait supprimé certaines données de sa base de données SQL Server (c'est l'édition express si cela compte).

Je pensais que cela pouvait être trouvé dans le journal des transactions (à condition qu'il n'ait pas été tronqué) - est-ce correct? Et si oui, comment allez-vous réellement trouver ces informations?

Réponses:


35

Je n'ai pas essayé fn_dblog sur Express mais s'il est disponible, ce qui suit vous donnera des opérations de suppression:

SELECT 
    * 
FROM 
    fn_dblog(NULL, NULL) 
WHERE 
    Operation = 'LOP_DELETE_ROWS'

Prenez l'ID de transaction pour les transactions qui vous intéressent et identifiez le SID qui a initié la transaction avec:

SELECT
    [Transaction SID]
FROM
    fn_dblog(NULL, NULL)
WHERE
    [Transaction ID] = @TranID
AND
    [Operation] = 'LOP_BEGIN_XACT'

Identifiez ensuite l'utilisateur à partir du SID:

SELECT
    *
FROM 
    sysusers
WHERE
    [sid] = @SID

Edit: Rassembler tout cela pour trouver des suppressions sur une table spécifiée:

DECLARE @TableName sysname
SET @TableName = 'dbo.Table_1'

SELECT
    u.[name] AS UserName
    , l.[Begin Time] AS TransactionStartTime
FROM
    fn_dblog(NULL, NULL) l
INNER JOIN
    (
    SELECT
        [Transaction ID]
    FROM 
        fn_dblog(NULL, NULL) 
    WHERE
        AllocUnitName LIKE @TableName + '%'
    AND
        Operation = 'LOP_DELETE_ROWS'
    ) deletes
ON  deletes.[Transaction ID] = l.[Transaction ID]
INNER JOIN
    sysusers u
ON  u.[sid] = l.[Transaction SID]

Cela fonctionne en effet avec SQL express mais sur mon système, il ne montre que les transactions qui se sont produites aujourd'hui. Je ne pensais pas que SQL Express avait une troncature de journal des transactions prête à l'emploi?
Matt Wilko

5
Si votre base de données est dans un modèle de récupération simple, vous ne pouvez pas faire d'hypothèses sur la durée des transactions inactives dans le journal.
Aaron Bertrand

3
Le journal des transactions est fondamental, plutôt que facultatif. Quel est le modèle de récupération de la base de données (simple ou complète) et comment les sauvegardes sont-elles configurées (complète uniquement ou sauvegarde de journal + complète)?
Mark Storey-Smith

J'ai volé ceci pour ma réponse ici bien que je l'ai un peu refactorisé pour éviter l'auto-jointure fn_dblog. Un inconvénient est qu'il renvoie la base de données USERNAME()plutôt que le nom de connexion beaucoup plus utile.
Martin Smith

3

Si la base de données est en mode de récupération complète ou si vous disposez de sauvegardes de journaux de transactions, vous pouvez essayer de les lire à l'aide de lecteurs de journaux tiers.

Vous pouvez essayer ApexSQL Log (premium mais a un essai gratuit) ou SQL Log Rescue (gratuit mais sql 2000 uniquement).


3

comment ils pourraient savoir qui a supprimé certaines données dans leur base de données SQL Server

Bien que cela soit répondu, je voulais ajouter que SQL Server a une trace par défaut activée et qu'elle peut être utilisée pour savoir qui a déposé / modifié les objets.

Événements d'objets

Les événements d'objet incluent: Objet modifié, Objet créé et Objet supprimé

Remarque: SQL Server possède par défaut 5 fichiers de trace, 20 Mo chacun et il n'y a aucune méthode connue prise en charge pour changer cela. Si vous avez un système occupé, les fichiers de trace peuvent rouler beaucoup trop rapidement (même en quelques heures) et vous ne pourrez peut-être pas détecter certaines des modifications.

Un excellent exemple peut être trouvé: la trace par défaut dans SQL Server - la puissance de l'audit des performances et de la sécurité


1

Vous pouvez essayer cette procédure pour interroger les fichiers de sauvegarde de journal et trouver dans quel (s) fichier (s) de sauvegarde de journal une valeur spécifique d'une colonne d'une table était encore / dernière présente.

Pour trouver l'utilisateur, après avoir trouvé dans quelle sauvegarde de journal la dernière valeur existait, vous pouvez restaurer une base de données jusqu'à cette sauvegarde de journal, puis suivre la réponse de Mark Storey-Smith .

Quelques prérequis

  • savoir quelles valeurs de quelles colonnes ont été supprimées
  • Sont sous le modèle de récupération complète et prennent des sauvegardes de journaux
  • vous avez des dates ou des identifiants dans vos sauvegardes de journaux, comme lors de l'utilisation de la solution d'Ola Hallengren

Avertissement

Cette solution est loin d'être étanche, et beaucoup de travail reste à faire.

Il n'a pas été testé sur des environnements à grande échelle, ni même sur des environnements autres que quelques petits tests. L'exécution actuelle était sur SQL Server 2017.

Vous pouvez utiliser la procédure ci-dessous de Muhammad Imran que j'ai modifiée pour travailler avec le contenu des sauvegardes de journal au lieu du contenu du journal d'une base de données en direct.

De cette façon, vous ne faites techniquement pas des restaurations, mais plutôt le vidage du contenu du journal dans une table temporaire. Il sera probablement encore lent et très ouvert aux bugs et problèmes. Mais cela pourrait fonctionner, en théorie ™.

La procédure stockée utilise la fn_dump_dblogfonction non documentée pour lire les fichiers journaux.


Environnement de test

Considérez cette base de données, où nous insérons quelques lignes, effectuons 2 sauvegardes de journal et lors de la troisième sauvegarde de journal, nous supprimons toutes les lignes.

CREATE DATABASE WrongDeletesDatabase
GO
USE WrongDeletesDatabase
GO
BACKUP DATABASE WrongDeletesDatabase TO DISK ='c:\temp\Full.bak'

ALTER DATABASE WrongDeletesDatabase SET RECOVERY FULL
GO

CREATE TABLE dbo.WrongDeletes(ID INT, val varchar(255))

INSERT INTO dbo.WrongDeletes(ID,val)
VALUES (1,'value1')
GO
BACKUP LOG WrongDeletesDatabase TO DISK = 'c:\temp\Logs\log1.trn'
GO
INSERT INTO dbo.WrongDeletes(ID,val)
VALUES (2,'value2')
GO
BACKUP LOG WrongDeletesDatabase TO DISK = 'c:\temp\Logs\log2.trn'
GO
DELETE FROM dbo.WrongDeletes
GO
BACKUP LOG WrongDeletesDatabase TO DISK = 'c:\temp\Logs\log3.trn'
GO
INSERT INTO dbo.WrongDeletes(ID,val)
VALUES (3,'value3')
GO
BACKUP LOG WrongDeletesDatabase TO DISK = 'c:\temp\Logs\log4.trn'
GO

La procédure

Vous pouvez trouver et télécharger la procédure stockée ici .

Je ne pourrais pas l'ajouter ici car il est plus grand que la limite de caractères, et rendrait cette réponse encore moins claire qu'elle ne l'est.

En dehors de cela, vous devriez pouvoir exécuter la procédure.

Exécution de la procédure

Un exemple de cela, lorsque j'ajoute tous mes fichiers journaux ( 4) à la procédure stockée et exécute la procédure à la recherche de value1

EXEC dbo.Recover_Deleted_Data_Proc  @Database_Name= 'WrongDeletesDatabase',
                                    @SchemaName_n_TableName= 'dbo.WrongDeletes', 
                                    @SearchString = 'value1', 
                                    @SearchColumn = 'val',
                                    @LogBackupFolder ='C:\temp\Logs\'

Cela me fait:

ID  val LogFileName
1   value1  c:\temp\Logs\log3.trn
1   value1  c:\temp\Logs\log1.trn

Où nous pouvons trouver la dernière fois qu'une opération a value1eu lieu, la supprimer log3.trn.

Quelques données de test supplémentaires, ajout d'un tableau avec différentes colonnes

CREATE TABLE dbo.WrongDeletes2(Wow varchar(255), Anotherval varchar(255),Val3 int)

INSERT INTO dbo.WrongDeletes(ID,val)
VALUES (1,'value1')
INSERT INTO dbo.WrongDeletes2(wOw,Anotherval,Val3)
VALUES ('b','value1',1)
GO
BACKUP LOG WrongDeletesDatabase TO DISK = 'c:\temp\Logs\log1_1.trn'
GO
INSERT INTO dbo.WrongDeletes(ID,val)
VALUES (2,'value2')
INSERT INTO dbo.WrongDeletes2(wOw,Anotherval,Val3)
VALUES ('c','value2',2)
GO
BACKUP LOG WrongDeletesDatabase TO DISK = 'c:\temp\Logs\log2_1.trn'
GO
DELETE FROM dbo.WrongDeletes
DELETE FROM dbo.WrongDeletes2
GO
BACKUP LOG WrongDeletesDatabase TO DISK = 'c:\temp\Logs\log3_1.trn'
GO
INSERT INTO dbo.WrongDeletes(ID,val)
VALUES (3,'value3')
INSERT INTO dbo.WrongDeletes2(wOw,Anotherval,Val3)
VALUES ('d','value3',3)
GO
BACKUP LOG WrongDeletesDatabase TO DISK = 'c:\temp\Logs\log4_1.trn'
GO

Modification des noms des fichiers journaux et réexécution de la procédure

EXEC dbo.Recover_Deleted_Data_Proc  @Database_Name= 'WrongDeletesDatabase',
                                    @SchemaName_n_TableName= 'dbo.WrongDeletes', 
                                    @SearchString = 'value1', 
                                    @SearchColumn = 'val',
                                    @LogBackupFolder ='C:\temp\Logs\'

Résultat

ID  val LogFileName
1   value1  c:\temp\Logs\log1_1.trn
1   value1  c:\temp\Logs\log3_1.trn
1   value1  c:\temp\Logs\log3_1.trn

Une nouvelle exécution, recherchant l'entier ( 2) dans la val3colonne dedbo.WrongDeletes2

EXEC dbo.Recover_Deleted_Data_Proc  @Database_Name= 'WrongDeletesDatabase',
                                    @SchemaName_n_TableName= 'dbo.WrongDeletes2', 
                                    @SearchString = '2', 
                                    @SearchColumn = 'Val3',
                                    @LogBackupFolder ='C:\temp\Logs\'

Résultat

Anotherval  Val3    Wow LogFileName
value2  2   c   c:\temp\Logs\log2.trn
value2  2   c   c:\temp\Logs\log3.trn

Appliquer la réponse de Mark Storey-Smith

Nous savons maintenant que cela s'est produit dans le troisième fichier journal, restaurons jusqu'à ce point:

USE master
GO
ALTER DATABASE WrongDeletesDatabase SET OFFLINE WITH ROLLBACK IMMEDIATE
GO
ALTER DATABASE WrongDeletesDatabase SET ONLINE 
GO
RESTORE DATABASE WrongDeletesDatabase FROM DISK = 'c:\temp\Logs\Full.bak' WITH NORECOVERY,REPLACE
RESTORE LOG WrongDeletesDatabase FROM DISK = 'c:\temp\Logs\log1.trn' WITH NORECOVERY
RESTORE LOG WrongDeletesDatabase FROM DISK = 'c:\temp\Logs\log2.trn' WITH NORECOVERY
RESTORE LOG WrongDeletesDatabase FROM DISK = 'c:\temp\Logs\log3.trn' WITH RECOVERY
GO
USE WrongDeletesDatabase
GO

Exécution de la dernière requête dans sa réponse

SELECT
    u.[name] AS UserName
    , l.[Begin Time] AS TransactionStartTime
FROM
    fn_dblog(NULL, NULL) l
INNER JOIN
    (
    SELECT
        [Transaction ID]
    FROM 
        fn_dblog(NULL, NULL) 
    WHERE
        AllocUnitName LIKE @TableName + '%'
    AND
        Operation = 'LOP_DELETE_ROWS'
    ) deletes
ON  deletes.[Transaction ID] = l.[Transaction ID]
INNER JOIN
    sysusers u
ON  u.[sid] = l.[Transaction SID]

Résultat pour moi (sysadmin)

UserName    TransactionStartTime
dbo 2019/08/09 17:14:10:450
dbo 2019/08/09 17:14:10:450
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.