Während der Erstellung eines Artikels für SIMPLE-TALK kam ein sehr interessanter Aspekt in Bezug auf HEAPS in Verbindung mit non-clustered Indexen in den Fokus: „Wird beim Neuaufbau eines non-clustered Index die Position des Forwarded Record im non-clustered Index gespeichert oder die ursprüngliche Adresse?. Was ein Forwarded Record ist und welchen Einfluss ein Forwarded Record auf die Performance haben kann, beschreibt der Artikel “Forwarded Records Intern”. Der nachfolgende Artikel geht auf die Verwaltung von non-clustered Indexen in einem Heap ein.

Testumgebung

Für den Artikel wird eine Tabelle [dbo].[Customer] erstellt, die mit 1.000 Datensätzen* gefüllt wird. Die Tabelle hat folgenden – einfachen – Aufbau:

CREATE TABLE dbo.Company
(
    Id     INT           NOT NULL     IDENTITY (1, 1),
    Name   VARCHAR(200)  NOT NULL,
    Street VARCHAR(100)  NOT NULL,
    ZIP    VARCHAR(10)   NOT NULL,
    City   VARCHAR(100)  NOT NULL
);
GO

CREATE NONCLUSTERED INDEX ix_Company_ZIP ON dbo.Company (ZIP);
GO

Non-Clustered Index in einem Heap

Ein Non-Clustered Index in einem Heap folgt einer anderen Vorgehensweise als in einem Clustered Index. Während ein non-clustered Index in einem Clustered Index nur den Clusterschlüssel speichert, muss der non-clustered Index für einen Heap immer die absolute Position des Datensatzes speichern, da eine Suche über einen Schlüssel nicht möglich ist. Die nachfolgende Abbildung zeigt die unterschiedliche Indexstruktur für einen Heap und für einen Clustered Index.

INDEX_02_HEAP Die linke Abbildung zeigt den Index [ix_company_zip], wie er in einem Heap gespeichert ist. Der ZIP-Code ist der Indexschlüssel und im Attribut [HEAP RID (key)] wird der Verweis zum Datensatz im Heap gespeichert. Da ein Heap keine Ordnungsschlüssel besitzt, kann der Datensatz nur über die absolute Position angesprochen werden. Die RID ist ein Binärwert mit einer Länge von 8 Bytes, in dem die Datenseite, Dateinummer und Slot ID gespeichert sind. Die ersten vier Bytes speichern die Datenseite während die restlichen vier Bytes für Dateinummer und Slot ID verwendet werden:

0xB4 00 00 00 = Datenseite 180
0x01 00       = Dateinummer 1
0x13 00       = Slot ID 19
Vollkommen anders sieht es jedoch aus, wenn der non-clustered Index [ix_company_zip] in einem clustered Index implementiert wird. Statt einer Referenz zu einer absoluten Position in der Tabelle wird ein Verweis auf den Ordnungsschlüssel gespeichert. Kann der Datensatz [ZIP = 00142] in einem Heap direkt angewählt werden, so muss er in einem Clustered Index über den B-Tree ermittelt werden.

Ein großer Vorteil jedoch ist die Größe des Indexes. Er ist 4 Bytes kleiner, da der Datentyp INT lediglich 4 Bytes konsumiert.

INDEX_02_CLUSTER

Non-Clustered Index und Forwarded Records

Im Zusammenhang mit Forwarded Records kam die Frage auf, ob bei dem Neuaufbau eines non-clustered Index Microsoft SQL Server die “neue” Adresse des Datensatzes im non-clustered Index speichert. Diese Frage kann man ganz klar mit NEIN beantworten. Mit dem nachfolgenden Code wird zunächst die Datenseite ermittelt, auf dem sich der Datensatz befindet.

-- Auf welcher Datenseite liegt der Datensatz mit ZIP = 00142
SELECT FPLC.*,
       C.*
FROM   dbo.Company AS C
       CROSS APPLY sys.fn_PhysLocCracker(%%physloc%%) AS FPLC
ORDER BY ZIP;
GO

RECORDSET_06

Ein Blick auf die Datenseite 145 zeigt, dass kein weiterer Platz mehr zur Verfügung steht.

DBCC TRACEON (3604);
DBCC PAGE (demo_db, 1, 145, 3);

DBCC_PAGE_145

Wird der Datensatz mit der ID = 626 erweitert, so muss es unweigerlich zu einem Forwarded Record kommen. Im Beispiel Fall wird auf der Seite 145 ein FORWARDED_STUB generiert, der auf die neue Datenseite verweist, auf der sich der Datensatz als Forwarded Record befindet.

—Aktualisierung des Datensatzes mit der ID 626
UPDATE dbo.Company
SET    Street = 'Das ist eine Strasse mit einem gaaaaaaanz langen Namen'
WHERE  ID = 626;

DBCC_PAGE_145_02

Wie die Abbildung zeigt, wurde der Datensatz mit der ID = 626 auf die Datenseite 337 in Datendatei 1 in Slot 31 verschoben. Diese Position entspricht – in Binärcode umgewandelt – der RID-Position 0x51 01 00 00 01 00 1f 00. Im nächsten Schritt wird der non-clustered Index neu aufgebaut und anschließend die Indexseiten überprüft.

-- Bestehenden Index neu aufbauen
ALTER INDEX ix_Company_ZIP ON dbo.Company REBUILD;
GO

-- Auf welcher Datenseite ist der Datensatz mit ID = 00142 gespeichert?
SELECT *
FROM   dbo.Company WITH (INDEX(2))
       CROSS APPLY sys.fn_PhysLocCracker(%%physloc%%) AS FPLC

-- Ein Blick auf die Datenseite...
DBCC TRACEON (3604);
DBCC PAGE (demo_db, 1, 1920, 3);
GO

INDEX_02_HEAP

Die Abbildung zeigt, dass trotz Neuaufbau des Index die Position des Datensatzes im Heap nicht geändert wurde. Dieses Ergebnis ist verständlich, wenn man versteht, wie ein Forwarded Record funktioniert.

Ein Forwarded Record gibt den allokierten Speicherplatz nicht auf sondern hinterlässt lediglich einen Verweis auf die neue Position. Tatsächlich wird der Datensatz selbst immer noch auf der Originalseite verwaltet. Ein non-clustered Index in einem Heap speichert – wie oben beschrieben – die exakte Position des Datensatzes und nicht, wie in einem Clustered Index, einen Schlüsselwert. Somit versteht sich das Ergebnis als Konsequenz daraus, dass bei einem Rebuild des Index die Position des Datensatzes im Heap zu keiner Zeit neu positioniert wurde.

Herzlichen Dank fürs Lesen!

*Achtung: Werbung

Für das Befüllen von Testtabellen verwende ich sehr gern den SQL Data Generator von RedGate. Hiermit lassen sich in wenigen Sekunden komplexe Datenmodelle mit Testdaten füllen.