Страницы

Поиск по вопросам

четверг, 23 января 2020 г.

Как восстановить БД MS SQL из битого ldf и full backup?

#sql #sql_server #recovery


Кратко о причинах - проблемы с хранилищем привели к потере файла БД. Все.

Проверено - файла базы mdf - нет. Лог ldf есть, модель - full.
Есть full backup. Почти свежий, от 09.2016 но имея лог от 14.10.2016, хотелось бы
свежее. 

Что попытался сделать сам: 


Создал бэкап сохранившегося лога:
Создал базу KA на другом сервере, ALTER DATABASE [KA] SET OFFLINE. удалил файлы,
залил свой ka_log.ldf
ALTER DATABASE [KA] SET ONLINE, получил свой Unable to open the physical file mdf и т.д.
сделал BACKUP LOG [ka] TO DISK = N'D:\Tail.bak' WITH NO_TRUNCATE;


Во время бэкапа было видно, что логу тоже досталось в момент падения хоста - было
сообщение BACKUP WITH CONTINUE_AFTER_ERROR успешно сформировал резервную копию поврежденной
базы данных. Дополнительные сведения об обнаруженных ошибках см. в журнале ошибок SQL
Server. 
В журнале - Backup detected log corruption in database ka. Context is Bad Middle Sector


Восстановил базу из имеющегося бк - RESTORE DATABASE [ka] FROM DISK = N'C:\2016.Bak'
WITH NORECOVERY, REPLACE
Попытался вернуть лог - RESTORE LOG [ka] FROM DISK = N'D:\Tail.bak' WITH FILE = 1,
NOUNLOAD, STATS = 1, NORECOVERY, CONTINUE_AFTER_ERROR


Результат - Произошла ошибка во время обработки журнала для базы данных "ka". Если
возможно, восстановите из резервной копии. Если резервная копия недоступна, возможно,
понадобится перестроить журнал.

Все. В этом месте я кончился. Нутром чувствую, что должен быть какой-то способ добраться
до данных, но не пойму как.

Перестроить лог не могу - я сижу в рекавери стейт. Выйти из рекавери не могу - у
меня проблемы с восстановленным журналом транзакций. 
    


Ответы

Ответ 1



Если восстановление журнала с CONTINUE_AFTER_ERROR (либо со STOP_ON_ERROR) RESTORE LOG [ka] FROM DISK = N'D:\Tail.bak' WITH FILE = 1, CONTINUE_AFTER_ERROR, NORECOVERY GO завершается с ошибкой, после которой невозможно выполнить RESTORE DATABASE [ka] WITH RECOVERY GO то можно попытаться восстановиться до какого-то LSN в журнале (максимально возможного), при котором RESTORE LOG ещё не вызывает ошибок. Для этого читаем заголовки бэкапов БД и журнала RESTORE HEADERONLY FROM DISK = N'C:\2016.Bak' RESTORE HEADERONLY FROM DISK = N'D:\Tail.bak' в которых сверяем значения столбцов FirstLSN и LastLSN (убеждаемся, что цепочка LSN не разорвана, и в журнале действительно есть дополнительная информация). Допустим, получили: C:\2016.Bak FirstLSN LastLSN ----------------- ----------------- 64000000005600195 64000000017600001 D:\Tail.bak FirstLSN LastLSN ----------------- ----------------- 64000000005600195 66000000025600001 сравниваем LSN: 64000000005600195 <= 64000000017600001 < 66000000025600001 - OK. Значение LastLSN из заголовка БД (равное 64000000017600001) переводим из десятичного представления в двоичное (см. здесь, функция fn_convertnumericlsntobinary), получаем 00000040:000000B0:0001. Теперь с помощью sys.fn_dump_dblog читаем последовательность LSN из дампа лога (можно отфильтровать только операции завершения транзакций Operation = 'LOP_COMMIT_XACT'): select [Current LSN] from sys.fn_dump_dblog( NULL, NULL, N'DISK', 1, N'D:\Tail.bak', default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default, default) where Operation = 'LOP_COMMIT_XACT'; Допустим, получили, следующий список: Current LSN ----------------------- 00000040:000000a0:0002 00000040:000000b8:0001 <-- сначала восстанавливаем журнал к этой отметке 00000040:000000c8:000b <-- потом к этой 00000040:000000d0:000a <-- ... 00000040:000000d8:000a <-- ... 00000040:000000e0:000a <-- ... 00000040:000000e8:000a <-- ... 00000040:000000f0:000b <-- ... 00000040:000000f8:0010 <-- ... 00000040:000000f8:0021 <-- и т.д, пока не встретим ошибку 00000040:000000f8:0027 <-- ERROR 00000040:00000110:0017 00000040:00000130:001b 00000040:00000130:001d 00000040:00000140:000a Заново инициализируем восстановление: RESTORE DATABASE [ka] FROM DISK = N'D:\2016.bak' WITH NORECOVERY, REPLACE GO Теперь берём из списка первое значение LSN, которое следует позднее, чем LastLSN в бэкапе БД (позднее чем 00000040:000000B0:0001) и делаем RESTORE LOG к этой отметке: RESTORE LOG [ka] FROM DISK = N'D:\Tail.bak' WITH FILE = 1, STOPATMARK = 'lsn:0x00000040:000000b8:0001', NORECOVERY GO Если операция прошла без ошибок, повторяем то же самое для следующей отметки, и т.д., пока не дойдём до отметки, восстановление к которой будет вызывать ошибку. Если отметок много, то можно применить дихотомический поиск, учитывая, однако, что при движении вперёд достаточно выполнять лишь RESTORE LOG к новой отметке, но если необходимо вернуться назад, то цепочку восстановления нужно выполнять заново (RESTORE DATABASE ... WITH REPLACE ..., затем RESTORE LOG ... к нужной отметке). После того как последняя не вызвавшая ошибку отметка определена, заново восстановим БД и журнал до этой отметки: RESTORE DATABASE [ka] FROM DISK = N'D:\2016.bak' WITH NORECOVERY, REPLACE GO RESTORE LOG [ka] FROM DISK = N'D:\Tail.bak' WITH STOPATMARK = 'lsn:0x00000040:000000f8:0021', NORECOVERY GO После чего завершаем восстановление: RESTORE DATABASE [ka] WITH RECOVERY GO

Комментариев нет:

Отправить комментарий