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?
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]
Si la base de données est en mode de récupération complète ou si vous disposez de sauvegardes du journal des 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).
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'objet
Les événements d'objet incluent: Objet modifié, Objet créé et Objet supprimé
note: SQL Server par défaut a 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 des performances et des audits de sécurité
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
Clause de non-responsabilité
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 ci-dessous procédure de Muhammad Imran que j'ai modifié pour travailler avec le contenu de les sauvegardes de journaux au lieu du contenu du journal d'une base de données active.
De cette façon, vous n'effectuez techniquement pas de restauration, 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 fonction non documentée fn_dump_dblog
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 en recherchant 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 sur value1
S'est produite, la suppression dans log3.trn
.
Quelques données de test supplémentaires, en ajoutant 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
Nouvelle exécution, recherchant l'entier (2
) Dans la colonne val3
De dbo.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 Mark Storey-Smith réponse
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