Поиск повреждённого объекта по номеру повреждённой страницы в MS SQL Server 2005
На днях одна из баз данных MS SQL Server перешла в Suspect, в журнале было сообщение об ошибке:
Msg 7105, Level 22, State 9, Line 14
Database ID 6, page (1:386499), slot 0 for LOB data type node does not exist. This is usually caused by transactions that can read uncommitted data on a data page. Run DBCC CHECKTABLE.
Базу перевели в Emergency и попытались выполнить DBCC CHECKDB, но выполнение сразу же прерывалось:
Msg 8921, Level 16, State 1, Line 13
Check terminated. A failure was detected while collecting facts. Possibly tempdb out of space or a system table is inconsistent. Check previous errors.
Msg 7105, Level 22, State 9, Line 13
Database ID 6, page (1:386499), slot 0 for LOB data type node does not exist. This is usually caused by transactions that can read uncommitted data on a data page. Run DBCC CHECKTABLE.
С аналогичной же ошибкой прерывалось выполнение команды DBCC CHECKALLOC. Осложнялось всё тем, что SQL Server был версии 9.0.1399, т.е. RTM, без каких-либо обновлений.
Попытки использовать хинт TABLOCK и явным образом повысить уровень изоляции транзакций, ни к чему не привели (места на дисках с tempdb было достаточно и DBCC CHECKALLOC с WITH ESTIMATEONLY завершался с той же ошибкой). Накатывать SP на сервер с повреждённой БД крайне не хотелось, а с каким конкретно объектом проблема, было абсолютно непонятно. Кроме того, создавалось впечатление, что сообщение DBCC CHECKDB имеет мало общего с реальностью, поскольку в msdb.dbo.suspect_pages была одна запись, но номер страницы отличался от того, который выводил DBCC CHECKDB.
Для того, что последовать инструкциям DBCC CHECKDB и выполнить DBCC CHECKTABLE, нужно было узнать таблицу. И после долгих поисков, одна инструкция нашлась.
Мы использовали алгоритм ниже для определения object_id обеих страниц — из DBCC CHECKDB и suspect_pages. Проблема оказалась в странице из suspect_pages
Первое, что нужно сделать, это выполнить (в контексте повреждённой базы данных) — это DBCC PAGE (database_id, file_id, page_id, printopt):
DBCC TRACEON (3604);
DBCC PAGE(5, 1, 3242342, 0)
DBCC TRACEOFF (3604);
либо:
DBCC PAGE(5, 1, 3242342, 0) WITH TABLERESULTS.
Если вам повезло (или вы играетесь на живой базе), в результате вы увидите поле Metadata: ObjectId и, собственно нужный object_id:
Однако, если вам, как и нам, не повезло, вы увидите следующее:
Metadata: = Unavailable in offline DB
Если метаданные недоступны, ещё не всё потеряно, в этом случае, нам нужно поле m_objId (AllocUnitId.idObj). Если m_objId = 255, беда закрывайте статью и ищите что-то другое (пытайтесь заскриптовать всё что можно и утащить данные, выполнять DBCC CHECKDB с «восстановительными» параметрами вслепую и т.д.).
На скриншоте видно, что у меня m_objId = 9931, т.е. можно продолжать.
Теперь нужно выполнить небольшие вычисления, чтобы вычислить Allocation Unit ID (подробнее про Allocation Units можно прочитать здесь):
Allocation Unit ID = m_objid * 65536 + (2^56)
В нашем случае:
Allocation Unit ID = 9931×65536 + (2^56) = 72057594688765952
Итак, зная Allocation Unit ID, можно посмотреть что у нас в системном представлении sys.allocation_units:
SELECT * FROM sys.allocation_units
WHERE allocation_unit_id = 72057594688765952
И там, в случае, если type = 1 или 3 (IN_ROW_DATA, ROW_OVERFLOW_DATA), столбец container_id = sys.partitions.hobt_id («Heap-Or-B-Tree ID»), т.е. можно выполнить запрос:
SELECT * FROM sys.partitions WHERE hobt_id = 72057594661437440
И вот тут уже есть корректный object_id и index_id. Теперь можно посмотреть что там у нас в sys.objects и sys.indexes, да и просто выполнить:
SELECT OBJECT_NAME(object_id)
К счастью, и в реальной ситуации, и здесь поверждённым оказался некластерный индекс, после перестройки которого всё пришло в норму (на самом деле нет, но это уже другая история).
Ссылки:
How to use DBCC PAGE
Troubleshooting and Fixing SQL Server Page Level Corruption
What Are Allocation Units?
Finding a table name from a page ID
sys.allocation_units