SQL: Kontrolowane uszkodzenie bazy danych

15-cze-2017

W tym artykule możesz zobaczyć w jaki sposób można uszkodzić bazę danych, więc zgodnie z europejskimi zwyczajami:

Uwaga! Wykonanie poniższych czynności może uszkodzić bazę danych! 🙂

Zacznij od utworzenia bazy danych:

CREATE DATABASE CorruptDB
GO
USE CorruptDB
GO
CREATE TABLE TestData (ID INT, Text1 CHAR(10))
GO
CREATE CLUSTERED INDEX IX_TestData_Clustered ON TestData(ID)
GO
INSERT INTO TestData VALUES(1,'AAAAAAAAAA')
INSERT INTO TestData VALUES(2,'DDDDDDDDDD')
INSERT INTO TestData VALUES(3,'GGGGGGGGGG')
INSERT INTO TestData VALUES(4,'JJJJJJJJJJ')
INSERT INTO TestData VALUES(5,'MMMMMMMMMM')
GO

Teraz na wszelki wypadek można wykonać kopię bazy, dzięki temu będzie można szybko rozpoczynać zabawę na nowo

BACKUP DATABASE corruptDB TO DISK='c:\temp\corruptdb.bak'

Poznajmy adresację stron:

--DBCC IND ( {dbname}, {table_name},{index_id} )
--index_id like indid value from sysindexes
DBCC IND ('CorruptDB','TestData',1)

Polecenie zwraca informację o indeksie clustrowanym . Pierwszy rekord opisuje stronę zwaną IAM (pierwsza strona tabeli), a kolejna to już strona z danymi:

 

 

Jeśli chcemy psuć dane, to dobrym miejscem jest w tym przypadku strona 344 (dziecko strony 245)

Zaczynamy od wyświetlenia zawartości strony 344. Do tego służy komenda DBCC PAGE, ale aby wyświetlała wyniki konieczne jest włączenie trace flagi 3604:

DBCC TRACEON (3604);
DBCC PAGE ('CorruptDB', 1, 344, 3);

zwróć uwagę na wynik:

Slot 0 Offset 0x60 Length 21
Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP Record Size = 21
Memory Dump @0x000000E806F7A060
0000000000000000: 10001200 01000000 41414141 41414141 41410300 ........AAAAAAAAAA..
0000000000000014: 00 .
Slot 0 Column 0 Offset 0x0 Length 4 Length (physical) 0
UNIQUIFIER = 0
Slot 0 Column 1 Offset 0x4 Length 4 Length (physical) 4
ID = 1
Slot 0 Column 2 Offset 0x8 Length 10 Length (physical) 10
Text1 = AAAAAAAAAA
Slot 0 Offset 0x0 Length 0 Length (physical) 0
KeyHashValue = (de42f79bc795) 
Slot 1 Offset 0x75 Length 21
Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP Record Size = 21
Memory Dump @0x000000E806F7A075
0000000000000000: 10001200 02000000 44444444 44444444 44440300 ........DDDDDDDDDD..
0000000000000014: 00 .
Slot 1 Column 0 Offset 0x0 Length 4 Length (physical) 0
UNIQUIFIER = 0
Slot 1 Column 1 Offset 0x4 Length 4 Length (physical) 4

Zaznaczone kolorem fragmenty oznaczają:

  • Slot (rekord), a w nim m.innymi offset, czyli przesunięcie od początku strony, gdzie znajduje się ta informacja, memory dump z tekstową prezentacją po prawej stronie
  • Widać też wartości w kolumnach

Można przystępować do psucia. Służy do tego ukryta funkcja DBCC PAGEWRITE. Tak można uzyskać minimalny help do tej funkcji:

DBCC TRACEON (2588);
DBCC HELP ('PAGE');

Składnia funkcji jest następująca:

dbcc WRITEPAGE ({’dbname’ | dbid}, fileid, pageid, offset, length, data [, directORbufferpool])

  • dbname – nazwa bazy danych
  • fileid – numer pliku bazy danych
  • pageid – numer strony wyrażony jako liczba dziesiętna
  • length – długość danych, jakie będą zapisane na stronie (od 1 do 8)
  • data – dane do zapisania, hex
  • directORbufferpool – 1 oznacza zapis z pominięciem bufora (checkpoint + zapis na dysk bez przeliczenia checksum) lub 0 – modyfikacja strony w buforze (z przeliczeniem sumy kontrolnej podczas zapisywania strony na dysku)

w naszym przypadku:

ALTER DATABASE corruptdb SET SINGLE_USER
GO
DBCC WRITEPAGE('CorruptDB', 1, 344, 106, 1, 0x58, 1)
GO

Oto jak została wyznaczona liczba 106 – jest to pozycja trzeciej literki 'A’ w pierwszym rekordzie:

106 = offset (ox60) + pozycja_trzeciej_literki_a_w_rekordzie (10) = 96 + 10

Jeśli wykonasz teraz jakikolwiek zapytanie do tabeli (która cała mieści się na jednej uszkodzonej stronie), to wynikiem będzie:

Msg 824, Level 24, State 2, Line 5
SQL Server detected a logical consistency-based I/O error:
incorrect checksum (expected: 0xdbdd841d; actual: 0x5bdd8411).
It occurred during a read of page (1:344) in database ID 9 at offset 0x000000002b0000 in file
’C:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\DATA\CorruptDB.mdf’.
Additional messages in the SQL Server error log or system event log may provide more detail.
This is a severe error condition that threatens database integrity and must be corrected immediately.
Complete a full database consistency check (DBCC CHECKDB).
This error can be caused by many factors; for more information, see SQL Server Books Online.

Baza jest uszkodzona!

Gdyby w poprzednim kroku zepsuć bazę wykonując polecenie z „0” jako ostatni argument:

ALTER DATABASE corruptdb SET SINGLE_USER
GO
DBCC WRITEPAGE('CorruptDB', 1, 344, 106, 1, 0x58, 0)
GO

to po wszystkim nadal można korzystać z danych:

 

 

 

 

 

Nawet polecenie:

DBCC CHECKDB (’Corruptdb’) WITH NO_INFOMSGS

nie zwraca żadnych błędów. Dlaczego? Bo zapis został wykonany w obszarze danych znakowych, o opcja „0” w ostatnim argumencie WRITEPAGE powodowała wyliczenie sumy kontrolnej przed zapisem, więc…. błędu nie ma i baza nie będzie uszkodzona, dokonaliśmy po prostu bardzo specyficznego update na rekordzie.

Skorzystałem z:

Paul Randal:

https://www.sqlskills.com/blogs/paul/dbcc-writepage/

DBCC WRITEPAGE: an introduction

https://www.sqlskills.com/blogs/paul/category/dbcc/

Armando Prato: https://www.mssqltips.com/sqlservertip/1578/using-dbcc-page-to-examine-sql-server-table-and-index-data/

Derek Colley https://www.mssqltips.com/sqlservertip/2871/troubleshooting-and-fixing-sql-server-page-level-corruption/

Brent Ozar: https://www.brentozar.com/archive/2014/05/dbcc-checkdb-faq/

Andrew Jackson: http://dbathings.com/dbcc-page-command-analyze-sql-database-objects/

Komentarze są wyłączone

Autor: Rafał Kraik