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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
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
1 |
BACKUP DATABASE corruptDB TO DISK='c:\temp\corruptdb.bak' |
Poznajmy adresację stron:
1 2 3 |
--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:
1 2 |
DBCC TRACEON (3604); DBCC PAGE ('CorruptDB', 1, 344, 3); |
zwróć uwagę na wynik:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<span style="color: #0000ff;">Slot 0 Offset 0x60 Length 21</span> Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP Record Size = 21 Memory Dump @0x000000E806F7A060 <span style="color: #0000ff;">0000000000000000: 10001200 01000000 41414141 41414141 41410300 ........AAAAAAAAAA..</span> <span style="color: #0000ff;">0000000000000014: 00 .</span> 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) <span style="color: #0000ff;">Slot 1 Offset 0x75 Length 21</span> Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP Record Size = 21 Memory Dump @0x000000E806F7A075 <span style="color: #0000ff;">0000000000000000: 10001200 02000000 44444444 44444444 44440300 ........DDDDDDDDDD..</span> <span style="color: #0000ff;">0000000000000014: 00 .</span> 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:
1 2 |
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:
1 2 3 4 |
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:
1 2 3 4 |
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/
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/