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_dblog
fonction 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 value1
eu 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 val3
colonne 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