Как узнать кто удалил некоторые данные SQL Server

29

Вчера мой начальник получил запрос от клиента, спрашивающий, как он может узнать, кто удалил некоторые данные в своей базе данных SQL Server (это экспресс-версия, если это имеет значение).

Я думал, что это можно найти в журнале транзакций (при условии, что он не был усечен) - это правильно? И если да, то как ты на самом деле собираешься найти эту информацию?

Мэтт Вилко
источник

Ответы:

35

Я не пробовал fn_dblog в Express, но, если он доступен, следующее даст вам удаление:

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

Возьмите идентификатор транзакции для транзакций, которые вас интересуют, и определите 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]
Марк Стори-Смит
источник
Это действительно работает с SQL Express, но в моей системе отображаются только транзакции, которые произошли сегодня. Я не думал, что SQL Express имеет усечение журнала транзакций из коробки?
Мэтт Вилко
5
Если ваша база данных находится в простой модели восстановления, вы не можете делать предположения о том, как долго неактивные транзакции будут задерживаться в журнале.
Аарон Бертран
3
Журнал транзакций является фундаментальным, а не необязательным. Какова модель восстановления базы данных (простая или полная) и как настраиваются резервные копии (только полное или резервное копирование журнала + полное)?
Марк Стори-Смит
Я украл это для своего ответа здесь, хотя немного изменил рефакторинг, чтобы избежать самостоятельного присоединения fn_dblog. Недостатком является то, что он возвращает базу данных, USERNAME()а не гораздо более полезное имя для входа.
Мартин Смит
3

Если база данных находится в режиме полного восстановления или если у вас есть резервные копии журналов транзакций, вы можете попробовать прочитать их, используя сторонние программы чтения журналов.

Вы можете попробовать ApexSQL Log (премиум, но имеет бесплатную пробную версию) или SQL Log Rescue (бесплатно, но только для SQL 2000).

Тони Меланик
источник
3

как они могли узнать, кто удалил некоторые данные в своей базе данных SQL Server

Хотя на этот вопрос ответили, хотелось бы добавить, что в SQL Server включена трассировка по умолчанию, и его можно использовать для определения, кто удалил / изменил объекты.

Объектные события

События объекта включают: объект изменен, объект создан и объект удален

примечание: SQL Server по умолчанию имеет 5 файлов трассировки, по 20 МБ каждый, и неизвестно поддерживаемый метод для изменения этого. Если у вас занятая система, файлы трассировки могут слишком быстро переноситься (даже в течение нескольких часов), и вы не сможете отследить некоторые изменения.

Отличный пример можно найти: трассировка по умолчанию в SQL Server - мощь аудита производительности и безопасности

Кин Шах
источник
1

Вы можете попробовать эту процедуру, чтобы запросить файлы резервных копий журналов и найти, в каких файлах резервных копий журналов конкретное значение столбца таблицы было еще / последним.

Чтобы найти пользователя, после того, как вы найдете в какой резервной копии журнала значение, которое существовало в последний раз, вы можете восстановить базу данных до этой резервной копии журнала и затем следовать ответу Марка Стори-Смита .

Некоторые предпосылки

  • знать, какие значения из каких столбцов были удалены
  • Находятся под моделью полного восстановления и делают резервные копии журналов
  • у вас есть даты или идентификаторы в резервных копиях журналов, например, при использовании решения Олы Хелленгрен

отказ

Это решение далеко не водонепроницаемо, и для его выполнения требуется гораздо больше работы.

Он не был протестирован в крупномасштабных средах или даже в любых средах, за исключением нескольких небольших тестов. Текущий запуск был на SQL Server 2017.

Вы можете использовать приведенную ниже процедуру от Мухаммеда Имрана, которую я модифицировал для работы с содержимым резервных копий журналов вместо содержимого журнала активной базы данных.

Таким образом, технически вы не выполняете восстановление, а просто записываете содержимое журнала во временную таблицу. Это, вероятно, все еще будет медленным и очень открытым для ошибок и проблем. Но это может сработать, в теории ™.

Хранимая процедура использует недокументированную 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) в хранимую процедуру и запускаю процедуру в поисках значения1

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

Новый прогон, ищущий integer ( 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

Применяя ответ Марка Стори-Смита

Теперь мы знаем, что это произошло в третьем файле журнала, давайте восстановим до этого момента:

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]

Результат для меня (сисадмин)

UserName    TransactionStartTime
dbo 2019/08/09 17:14:10:450
dbo 2019/08/09 17:14:10:450
Рэнди Вертонген
источник