Answers:
我没有在Express上尝试过fn_dblog,但是如果可用,则以下内容将为您提供删除操作:
SELECT
*
FROM
fn_dblog(NULL, NULL)
WHERE
Operation = 'LOP_DELETE_ROWS'
获取您感兴趣的交易的交易ID,并使用以下命令标识发起交易的SID:
SELECT
[Transaction SID]
FROM
fn_dblog(NULL, NULL)
WHERE
[Transaction ID] = @TranID
AND
[Operation] = 'LOP_BEGIN_XACT'
然后从SID识别用户:
SELECT
*
FROM
sysusers
WHERE
[sid] = @SID
编辑:将所有这些一起找到指定表上的删除项:
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]
如果数据库处于完全恢复模式,或者您具有事务日志备份,则可以尝试使用第三方日志读取器读取这些备份。
您可以尝试ApexSQL Log(高级版,但有免费试用版)或SQL Log Rescue(免费,但仅限sql 2000)。
他们如何找出谁删除了SQL Server数据库中的某些数据
尽管回答了此问题,但要补充一点,SQL Server已启用了默认跟踪,并且可以用来查找谁删除/更改了对象。
对象事件
对象事件包括:更改对象,创建对象和删除对象
注意:默认情况下,SQL Server具有5个跟踪文件,每个跟踪文件20 MB,并且没有已知的支持的更改方法。如果系统繁忙,则跟踪文件的滚动速度可能会太快(甚至数小时之内),并且您可能无法捕获某些更改。
可以找到一个很好的例子:SQL Server中的默认跟踪-性能和安全审核的功能
您可以尝试执行以下过程来查询日志备份文件,并查找在哪个/哪些日志备份文件中仍存在/最后一个表的列的特定值。
要找到用户,请在哪个日志备份中找到最后一个值之后,可以还原数据库直到该日志备份,然后按照Mark Storey-Smith的回答进行操作。
前提条件
免责声明
该解决方案远非防水,需要做更多的工作。
它尚未在大规模环境或什至是几个小测试之外的任何环境中进行测试。当前运行在SQL Server 2017上。
您可以使用穆罕默德·伊姆兰(Muhammad Imran)修改的以下过程,以处理日志备份的内容,而不是实时数据库日志的内容。
这样,从技术上讲,您不执行还原,而是将日志内容转储到临时表中。它可能仍然会很慢,并且非常容易出现错误和问题。但从理论上讲,它可以工作。
该存储过程使用未记录的fn_dump_dblog
功能来读取日志文件。
测试环境
考虑这个数据库,我们在其中插入一些行,进行2次日志备份,在第三个日志备份中,我们删除所有行。
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
步骤
您可以在此处找到并下载存储过程。
我不能在这里添加它,因为它大于字符数限制,并且会使答案变得不那么清晰。
除此之外,您还应该能够运行该过程。
运行程序
例如,当我将所有日志文件(4
)添加到存储过程并运行该过程以查找value1时,
EXEC dbo.Recover_Deleted_Data_Proc @Database_Name= 'WrongDeletesDatabase',
@SchemaName_n_TableName= 'dbo.WrongDeletes',
@SearchString = 'value1',
@SearchColumn = 'val',
@LogBackupFolder ='C:\temp\Logs\'
这使我:
ID val LogFileName
1 value1 c:\temp\Logs\log3.trn
1 value1 c:\temp\Logs\log1.trn
在哪里可以找到上一次执行操作的时间value1
,请在中删除log3.trn
。
更多测试数据,添加具有不同列的表
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
更改日志文件名并再次执行该过程
EXEC dbo.Recover_Deleted_Data_Proc @Database_Name= 'WrongDeletesDatabase',
@SchemaName_n_TableName= 'dbo.WrongDeletes',
@SearchString = 'value1',
@SearchColumn = 'val',
@LogBackupFolder ='C:\temp\Logs\'
结果
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
进行一次新的搜索,2
在的val3
列中搜索整数()dbo.WrongDeletes2
EXEC dbo.Recover_Deleted_Data_Proc @Database_Name= 'WrongDeletesDatabase',
@SchemaName_n_TableName= 'dbo.WrongDeletes2',
@SearchString = '2',
@SearchColumn = 'Val3',
@LogBackupFolder ='C:\temp\Logs\'
结果
Anotherval Val3 Wow LogFileName
value2 2 c c:\temp\Logs\log2.trn
value2 2 c c:\temp\Logs\log3.trn
应用Mark Storey-Smith的答案
现在我们知道它发生在第三个日志文件中,让我们还原到那时:
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
运行他的答案中的最后一个查询
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]
对我而言的结果(sysadmin)
UserName TransactionStartTime
dbo 2019/08/09 17:14:10:450
dbo 2019/08/09 17:14:10:450