Nachdem in den letzten Postings der "SQL Server Closeup" Serie viel Theorie an der Reihe war, werden wir heute die Ghost Data Records einmal "Live" unter die Lupe nehmen.
Hierzu benötigen wir natürlich als erstes eine Datenbank und eine Tabelle.
Das Skript sieht hierfür wie folgt aus:
USE [master] GO IF DATABASEPROPERTY('SQLServerCloseup', 'Version') > 0 BEGIN ALTER DATABASE [SQLServerCloseup] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; DROP DATABASE [SQLServerCloseup]; END; GO CREATE DATABASE SQLServerCloseup; GO USE [SQLServerCloseup]; GO CREATE TABLE [dbo].[Produkte] ( [ProduktID] [int] NOT NULL, [ProduktBezeichnung] [varchar](50) NOT NULL, CONSTRAINT [PK_Produkte] PRIMARY KEY CLUSTERED ( [ProduktID] ASC ) )
Nachdem nun eine Tabelle zum Testen existiert, können Datensätze hinzugefügt und gelöscht werden.
Damit das Ganze besser nachvollziehbar ist, verwende ich eine benannte Transaktion, die ich vorerst offen lasse:
BEGIN TRANSACTION GhostRecordTest; INSERT INTO [dbo].[Produkte] VALUES (1, 'Bleistift'), (2, 'Kugelschreiber'), (3, 'Buntstift'); DELETE FROM [dbo].[Produkte] WHERE ProduktID = 2;
Um die File ID und Page in File ID zu ermitteln, verwende ich den SQL Befehl DBCC IND [Mehr]:
DBCC IND ('SQLServerCloseup', 'Produkte', 1);
Danach kann der Inhalt der Page mittels DBCC PAGE [Mehr] angezeigt werden:
DBCC PAGE ('SQLServerCloseup', 1, 78, 1);
Die wichtigen Stellen der Ausgabe ich ich mit rot hervorgehoben:
PAGE: (1:78) ...Ausgabe an dieser Stelle gekürzt... PAGE HEADER: Page @0x12E34000 m_pageId = (1:78) m_headerVersion = 1 m_type = 1 m_typeFlagBits = 0x4 m_level = 0 m_flagBits = 0x0 ...Ausgabe an dieser Stelle gekürzt... m_xactReserved = 0 m_xdesId = (0:597) m_ghostRecCnt = 1 m_tornBits = 0 ...Ausgabe an dieser Stelle gekürzt... DATA: Slot 0, Offset 0x60, Length 24, DumpStyle BYTE Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS Record Size = 24 Memory Dump @0x6378C060 00000000: 30000800 01000000 02000001 00180042 †0..............B 00000010: 6c656973 74696674 †††††††††††††††††††leistift Slot 1, Offset 0x78, Length 29, DumpStyle BYTE Record Type = GHOST_DATA_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS Record Size = 29 Memory Dump @0x6378C078 00000000: 3c000800 02000000 02000001 001d004b †<..............K 00000010: 7567656c 73636872 65696265 72††††††††ugelschreiber Slot 2, Offset 0x95, Length 24, DumpStyle BYTE Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS Record Size = 24 Memory Dump @0x6378C095 00000000: 30000800 03000000 02000001 00180042 †0..............B 00000010: 756e7473 74696674 †††††††††††††††††††untstift OFFSET TABLE: Row - Offset 2 (0x2) - 149 (0x95) 1 (0x1) - 120 (0x78) 0 (0x0) - 96 (0x60)
Wie man sehen kann, wurde der zweite Record als Ghost Data Record markiert, sowie im Page Header der Ghost Record Count um 1 erhöht.
Mit der undokumentierten Funktion fn_dblog kann man dies auch im Transaction Log der Datenbank nachvollziehen:
DECLARE @TransactionID NVARCHAR(MAX) SELECT @TransactionID = [Transaction ID] FROM fn_dblog(null, null) WHERE [Transaction Name] = 'GhostRecordTest'; SELECT * FROM fn_dblog(null, null) WHERE [Transaction ID] = @TransactionID;
Außerhalb der aktuellen Transaktion kann man bereits die Anpassungen der zugehörigen PFS Page erkennen:
Um besser erkennen zu können, was nach dem COMMIT der Transaktion passiert, muss als nächstes die letzte LSN bestimmt werden:
SELECT MAX([Current LSN]) FROM fn_dblog(null, null);
Anschließend kann die Transaktion beendet und direkt die weiteren Transaction Log Einträge angezeigt werden:
COMMIT TRANSACTION; SELECT [Page ID], * FROM fn_dblog(null, null) WHERE [Current LSN] > '00000018:00000052:001a';
Ein wenig später ist auch der Ghost Cleanup Task [Mehr – siehe Ghost Records] gelaufen.
Wenn man sich die Page jetzt erneut anzeigen lässt, kann man erkennen, dass nur im Row Offset Array der Record Zeiger entfernt wurde:
DBCC PAGE ('SQLServerCloseup', 1, 78, 2);
Ausgabe von DBCC PAGE:
PAGE: (1:78) ...Ausgabe an dieser Stelle gekürzt... PAGE HEADER: Page @0x130FC000 m_pageId = (1:78) m_headerVersion = 1 m_type = 1 m_typeFlagBits = 0x4 m_level = 0 m_flagBits = 0x0 ...Ausgabe an dieser Stelle gekürzt... m_xactReserved = 0 m_xdesId = (0:597) m_ghostRecCnt = 0 m_tornBits = 0 ...Ausgabe an dieser Stelle gekürzt... DATA: Memory Dump @0x617BC000 617BC000: 01010400 00000001 00000000 00000800 †................ 617BC010: 00000000 00000200 1d000000 6c1fad00 †............l.. 617BC020: 4e000000 01000000 18000000 57000000 †N...........W... 617BC030: 01000000 55020000 00000000 00000000 †....U........... 617BC040: 01000000 00000000 00000000 00000000 †................ 617BC050: 00000000 00000000 00000000 00000000 †................ 617BC060: 30000800 01000000 02000001 00180042 †0..............B 617BC070: 6c656973 74696674 3c000800 02000000 †leistift<....... 617BC080: 02000001 001d004b 7567656c 73636872 †.......Kugelschr 617BC090: 65696265 72300008 00030000 00020000 †eiber0.......... 617BC0A0: 01001800 42756e74 73746966 74000021 †....Buntstift..! 617BC0B0: 21212121 21212121 21212121 21212121 †!!!!!!!!!!!!!!!! 617BC0C0: 21212121 21212121 21212121 21212121 †!!!!!!!!!!!!!!!! 617BC0D0: 21212121 21212121 21212121 21212121 †!!!!!!!!!!!!!!!! ...Ausgabe an dieser Stelle gekürzt... 617BDFE0: 21212121 21212121 21212121 21212121 †!!!!!!!!!!!!!!!! 617BDFF0: 21212121 21212121 21219500 95006000 †!!!!!!!!!!?.?.`. OFFSET TABLE: Row - Offset 1 (0x1) - 149 (0x95) 0 (0x0) - 96 (0x60)
Verwendete Quellen: SQLskills.com und Microsoft® SQL Server® 2008 Internals (ISBN-10: 0735626243)