Mittwoch, 30. Juli 2014

Aufsteigende Indexschlüssel – Performance Killer

Ein reißerischer Titel, oder? Aber tatsächlich ist für die Performance einer Datenbankanwendung das Design ein entscheidender Faktor, der – wie in diesem Fall – schnell zu einem Performancekiller werden kann. Dieses Problem ist in der Community allseits unter dem Begriff “Ascending Key Problem” bekannt. Das Problem tritt in einer Anwendung auf, in der zu einer Haupttabelle [dbo].[master_table] jeweils n Datensätze in einer Detailtabelle [detail_table] gespeichert werden. Sobald neue Datensätze in der Haupt- und Detailtabelle eingetragen werde und anschließend die abhängigen Detaildaten abgefragt werden, verschlechtert sich die Performance dramatisch, wenn es sich um neue Einträge handelte. Die Zusammenhänge zeigt der nachfolgende Artikel.

Problembeschreibung

Im aktuellen Projekt wird zunächst in einer Haupttabelle [dbo].[master_table] ein neuer Datensatz eingetragen. Anschließend werden weitere Datensätze in die Detailtabelle [dbo].[detail_table] eingetragen, die den Primärschlüssel des Hauptdatensatzes als Fremdschlüssel erhalten. Die Anzahl der neuen Datensätze in der Detailtabelle kann sehr stark variieren. Von 1 Datensatz bis zu 1.000 Datensätzen kann das Volumen variieren. Insbesondere, wenn sehr große Datenmengen in der Detailtabelle eingetragen werden, benötigen Abfragen, die zuvor wenige Sekunden liefen, Minuten. Meine Aufgabe besteht darin, diese starken Abweichungen so weit wie möglich zu verhindern.

Testumgebung

Um das Problem zu verdeutlichen, werden zwei Tabellen erstellt; für die Darstellung reicht eine Simplifizierung mit wenigen Attributen.

CREATE TABLE dbo.master_table
(
    Id INT        NOT NULL  IDENTITY (1, 1),
    c1 CHAR(200)  NOT NULL  DEFAULT ('stuff for c1 in master_table'),
 
    CONSTRAINT pk_master_table_Id PRIMARY KEY CLUSTERED (Id)
);
GO

Die erste Tabelle wird die “Kopfdaten” erhalten. Der Primärschlüssel ist gleichzeitig der Clustered Key und wird durch die Funktion IDENTITY als fortlaufend gekennzeichnet.



CREATE TABLE dbo.detail_table
(
    Id         INT       NOT NULL  IDENTITY (1, 1),
    c1         CHAR(200) NOT NULL  DEFAULT ('more stuff for c1 in detail_table'),
    master_id  INT       NOT NULL,
 
    CONSTRAINT pk_detail_table_id PRIMARY KEY CLUSTERED (Id),
    CONSTRAINT fk_master_table_id FOREIGN KEY (master_id) REFERENCES dbo.master_table (id)
);
 
CREATE INDEX ix_detail_table_master_id ON dbo.detail_table (master_Id);
GO

Die zweite Tabelle erhält ebenfalls einen geclusterten Primärschlüssel und – für die referenzielle Integrität – das Attribut [master_id], das durch eine Fremdschlüsseleinschränkung sicherstellt, dass nur Schlüsselwerte der Haupttabelle vorhanden sein dürfen. Zusätzlich wird das Attribut [master_id] aus Optimierungsgründen indexiert!


Nachdem beide Tabellen erstellt worden sind, werden 1.000 Datensätze in die Tabelle [dbo].[master_table] geschrieben. Anschließend werden für jeden Eintrag der Haupttabelle jeweils 1- 10 Datensätze in die Detailtabelle eingetragen.



INSERT INTO dbo.master_table DEFAULT VALUES;
GO 1000
 
DECLARE @i   INT = 1
DECLARE @rows    INT = CAST(RAND() * 10 + 1 AS INT);
DECLARE @cntr    INT = 1;
 
WHILE @i <= 1000
BEGIN
    SET @cntr = 1;
    RAISERROR ('Inserting %i rows for id %i', 0, 1, @rows, @i) WITH NOWAIT;
 
    WHILE @cntr <= @rows
    BEGIN
        INSERT INTO dbo.detail_table (master_id) VALUES (@i);
        SET @cntr += 1;
    END
 
    SET    @i += 1;
    SET    @rows = CAST(RAND() * 10 + 1 AS INT);
END
GO

Nachdem der für die Tests benötigte Datenbestand eingetragen wurde, werden die Statistiken beider Tabellen aktualisiert:



UPDATE STATISTICS dbo.master_table WITH FULLSCAN;
UPDATE STATISTICS dbo.detail_table WITH FULLSCAN;

Testszenario


Für die in dieser Konstellation auftretenden Probleme reicht eine simple Abfrage, die den Index [ix_detail_table_master_id] verwendet. Damit die Verwendung eines gespeicherten Plans ausgeschlossen werden kann, wird die Abfrage mit der Option RECOMPILE ausgeführt!



SELECT m.*,
       d.*
FROM   dbo.master_table AS m INNER JOIN dbo.detail_table AS d
       ON (m.id = d.master_id)
WHERE  m.id = 10 OPTION (RECOMPILE);

Die [Id] wurde willkürlich gewählt und ist nur repräsentativ! Für die obige Abfrage ergibt sich der folgende Ausführungsplan:


EXECUTION_PLAN_01


Im Ausführungsplan ist sehr gut zu erkennen, dass für die Auswahl der Daten aus [dbo].[detail_table] der Index [ix_detail_table_master_id] verwendet wird. Da jedoch nicht alle Attribute für die Ausgabe im Index enthalten sind, müssen die zusätzlichen Werte aus dem Clustered Index mittels Key Lookup ermittelt werden. Insgesamt ergibt sich aus der Abfrage ein vertretbares IO (grafische Darstellung mit http://www.statisticsparser.com/).


STATISTICSPARSER_01


Das oben beschriebene Verhalten ist für JEDEN [Id]-Wert identisch; für jeden Master-Datensatz gibt es zwischen 1 und 10 Detaildatensätze. Im nächsten Schritt wird ein neuer Master-Datensatz mit 1.000 Detaildatensätzen eingetragen und die Abfrage erneut mit der neuen [ID] ausgeführt:



-- Neue ID = 1001
INSERT INTO dbo.master_table DEFAULT VALUES;
GO
 
-- 1.000 Datensätze mit der zuvor erstellten ID
INSERT INTO dbo.detail_table (master_id) VALUES (1001);
GO 1000

Nachdem die neuen Daten eingetragen wurden, wird eine neue Abfrage auf die zuvor erstellte [Id] ausgeführt und dabei ebenfalls das IO gemessen:



SET STATISTICS IO, TIME ON;
GO
 
SELECT m.*,
       d.*
FROM   dbo.master_table AS m INNER JOIN dbo.detail_table AS d
       ON (m.id = d.master_id)
WHERE  m.id = 1001 OPTION (RECOMPILE);
 
SET STATISTICS IO, TIME OFF;
GO

Der Ausführungsplan für die Abfrage ist – trotz Neukompilierung – absolut identisch zum Plan der ersten Ausführung.


EXECUTION_PLAN_02


Obwohl die Datenmenge in der Tabelle [dbo].[detail_table] für die [Id] deutlich höher ist, wird der identische Plan verwendet. Trotz der Option RECOMPILE schlägt der Query Optimizer des Microsoft SQL Servers eine identische Ausführungsstrategie vor! Entsprechend sieht das generierte IO aus:


STATISTICSPARSER_02


Die Ausführungszeit ist 10 Mal höher als bei den vorherigen Abfragen und – bedingt durch die identische Ausführungsstrategie – das IO entsprechend hoch. Doch warum verwendet Microsoft SQL Server keinen effizienteren Ausführungsplan? Das Problem ist sehr schnell lokalisiert, wenn man sich die Eigenschaften der Operatoren des Ausführungsplanes etwas genauer anschaut – insbesondere die geschätzten Datensätze in der Tabelle [dbo].[detail_table]!


EXECUTION_PLAN_03


Obwohl 1.000 Datensätze mit dem Fremdschlüsselwert in der Tabelle vorhanden sind, geht Microsoft SQL Server nur von 1 (!) Datensatz aus.


Warum eine geschätzte Anzahl von 1 Datensatz?


Statistiken zur Abfrageoptimierung sind Objekte, die Informationen über die Verteilung von Werten in Spalten einer Tabelle oder indizierten Sicht enthalten. Der Query Optimizer von Microsoft SQL Server erstellt Ausführungspläne, die auf Statistiken beruhen. Bei jeder Ausführung einer Abfrage prüft Microsoft SQL Server zunächst auf Basis von Statistiken, wie viele Datensätze zu erwarten sind. Basierend auf diesen Werten wird dann ein geeigneter Ausführungsplan erstellt.


Ist die AUTO_UPDATE_STATISTICS-Option zur automatischen Aktualisierung von Statistiken aktiviert, stellt der Abfrageoptimierer fest, wann Statistiken veraltet sind und aktualisiert diese Statistiken, sobald sie von einer Abfrage verwendet werden. Statistiken sind veraltet, wenn die Datenverteilung in der Tabelle oder indizierten Sicht durch INSERT-, UPDATE-, DELETE- oder MERGE-Vorgänge geändert wurde. Das Problem ist jedoch der Schwellenwert, ab dem Microsoft SQL Server eine Aktualisierung der Statistiken vornimmt! Für das Aktualisieren von Statistiken gibt es Schwellenwerte, die wie folgt berechnet werden (http://support.microsoft.com/kb/195565):



  • Wenn die Kardinalität für eine Tabelle weniger als sechs beträgt und die Tabelle sich in der Datenbank tempdb befindet, wird nach jeweils sechs Änderungen in der Tabelle eine automatische Aktualisierung durchgeführt.
  • Wenn die Kardinalität für eine Tabelle größer als 6, jedoch kleiner oder gleich 500 ist, wird der Status nach jeweils 500 Änderungen aktualisiert.
  • Wenn die Kardinalität für eine Tabelle größer als 500 ist, wird die Statistik nach jeweils (500 + 20% der Tabelle) Änderungen aktualisiert.

Sind in der Tabelle [dbo].[detail_table] 5.500 Datensätze vorhanden, müssen 1.600 Änderungen (INSERT, UPDATE, DELETE, MERGE) vorgenommen werden, bevor die Statistiken aktualisiert werden. Es wurden jedoch nur 1.000 Datensätze hinzugefügt – eine Aktualisierung findet also – NOCH – nicht statt! Microsoft SQL Server erstellt bei Erstellung eines Index automatisch Statistiken für die betroffenen Attribute. Dieses Histogramm kann jederzeit abgefragt werden:



DBCC SHOW_STATISTICS ('dbo.detail_table', 'ix_detail_table_master_id') WITH HISTOGRAM;

DBCC_STATISTICS_01


Die Abbildung zeigt die letzten Einträge im Histogramm des Index [ix_detail_table_master_id]. Die erste Spalte repräsentiert einen dedizierten Wert, zu dem “exakte” Statistiken vorliegen, diese Liste kann man nicht individuell anpassen! Als Beispiel für die Interpretation eines Histogramms soll der Wert in Zeile 196 der Abbildung verwendet werden:


Würde nach dem Wert [Id] = 990 gesucht werden, sucht Microsoft SQL Server zunächst im Histogramm, ob ein entsprechender Wert unmittelbar vorhanden ist. Sofern der Wert im Histogramm selbst vorhanden ist, wird im Attribut [EQ_ROWS] geprüft, wie viele Datensätze vorhanden sind. Diesen Referenzwert verwendet der Query Optimizer für den geeigneten Ausführungsplan.


Wird nach dem Wert [Id] = 989 gesucht, wird dieser Wert nicht im Histogramm gefunden. Jedoch kann Microsoft SQL Server an Hand der Werte in [RANGE_HI_KEY] feststellen, dass der Wert zwischen 985 und 990 liegt. Das Attribut [DISTINCT_RANGE_ROWS] in Zeile 196 gibt Auskunft darüber, dass zwischen dem Wert 985 und dem Wert 990 insgesamt 4 unterschiedliche Werte liegen (986, 987, 988, 989). Das Attribut [RANGE_ROWS] besagt, dass diese 4 unterschiedlichen Werte insgesamt 22 Mal in der Tabelle vorkommen. Somit ergibt sich ein Durchschnitt von 5,5 Datensätzen pro Einzelwert. Die beiden nachfolgenden Abbildungen zeigen, wie diese statistischen Werte von Microsoft SQL Server für den Ausführungsplan verwendet werden.



SELECT * FROM dbo.detail_table WHERE master_id = 990 OPTION (RECOMPILE);

EXECUTION_PLAN_04


Die Abfrage nach der [Id] = 990 schätzt 10 Datensätze, die durch die Abfrage zurück geliefert werden.



SELECT * FROM dbo.detail_table WHERE master_id = 989 OPTION (RECOMPILE);

EXECUTION_PLAN_05


Die zweite Abfrage findet im Histogramm keinen entsprechenden Datensatz und muss sich an den Durchschnittswerten orientieren, die zwischen den beiden Werten im Histogramm gespeichert sind.


Für den neuen Wert [ID] = 1001 gibt es im Histogramm KEINE Informationen! Es gibt auch keine Bereichssschlüssel, die größer sind als der Wert 1.000. Im Beispiel konnte mittels Histogramm ein Wert ermittelt werden, der relativ nah an den tatsächlichen Wert lag. Für den neuen Datensatz können aus dem Histogramm keine Daten ermittelt werden – somit geht Microsoft SQL Server IMMER von einem Wert 1 aus (Dieses Vorgehen gilt auch für den neuen Cardinal Estimator von Microsoft SQL Server 2014, wenn nur ein Prädikat verwendet wird).


EXECUTION_PLAN_06


Dieses Problem kann auftauchen, wenn die Indexschlüssel fortlaufend sind und somit in den Statistiken kein adäquater Referenzwert gefunden werden kann. Um den Unterschied in den Ausführungsplänen zu sehen, werden die Statistiken für [dbo].[detail_table] aktualisiert und die Abfrage erneut ausgeführt:



UPDATE STATISTICS dbo.detail_table WITH FULLSCAN;
GO
 
SELECT m.*,
       d.*
FROM   dbo.master_table AS m INNER JOIN dbo.detail_table AS d
       ON (m.id = d.master_id)
WHERE  m.id = 1001;

EXECUTION_PLAN_09


Nur durch das Aktualisieren der Statistiken hat sich der Ausführungsplan geändert. Der Query Optimizer von Microsoft SQL Server erkennt die tatsächliche Anzahl von Datensätzen (1.000) und entscheidet sich für einen Index Scan statt eines Index Seek. Der Index Scan benötigt keinen expliziten Zugriff auf den Clustered Index und ist somit für die Ausführung effizienter als ein INDEX SEEK.


Lösungswege


Das obige Beispiel arbeitet mit kleinen Datenmengen – das tatsächliche Umfeld, in dem es um die Optimierung geht, beinhaltet ca. 500.000.000 Datensätze. Man kann sich also vorstellen, welchen Einfluss veraltete Statistiken auf die Ausführungspläne haben. Nachfolgend werden einige Lösungsvorschläge gegeben, die diese Problematik entschärfen können.


Verwendung von Variablen


Ein Lösungsansatz ist die Verwendung von Variablen, da dann nicht auf das Histogramm sondern auf die Verteilung der Daten (dem sogenannten Density Vektor) zugegriffen wird. Der Query Optimizer verwendet Dichten, um Kardinalitätsschätzungen für Abfragen zu erweitern, die mehrere Spalten aus derselben Tabelle oder indizierten Sicht zurückgeben. Die Abfrage wird wie folgt geändert und ausgeführt:



DECLARE @Id INT = 1001;
 
SELECT  m.*,
        d.*
FROM    dbo.master_table AS m INNER JOIN dbo.detail_table AS d
        ON (m.id = d.master_id)
WHERE   m.id = @Id;

EXECUTION_PLAN_07


Wie man in der Abbildung sehen kann, hilft diese Option nicht wirklich weiter. Das hängt von folgenden Ursachen ab:



  • Die Dichte der Bestandsdaten vor dem Einfügen hatte eine regelmäßige Verteilung (<= 10 Datensätze)
  • Die Statistiken wurden noch nicht aktualisiert

Um das Vorgehen von Microsoft SQL Server zu verstehen, muss man sich einen Überblick über die zur Verfügung stehenden Statistiken verschaffen. Mit dem nachfolgenden Befehl werden ALLE statistischen Informationen zum verwendeten Index ausgegeben. Die für die Abfrage wesentlichen Informationen werden in der Abbildung rot gekennzeichnet:



DBCC SHOW_STATISTICS ('dbo.detail_table', 'ix_detail_table_master_id');

DBCC_STATISTICS_02


Insgesamt sind 5.547 Datensätze in der Tabelle (respektive im Index) vorhanden. Für das Attribut [master_id] besteht eine Dichte von 0,001. Die Dichte wird berechnet aus 1 / Anzahl EINDEUTIGER Werte. Für den vorliegenden Fall gibt es 1.000 eindeutige Zahlenwerte: 1 / 1000 = 0,001. Dieser Wert wird vom Query Optimizer verwendet und mit der Anzahl aller in der Tabelle / Index vorhandenen Datensätze multipliziert. Somit ergibt sich – statistisch – ein Verhältnis von 5,547 Datensätze pro Einzelwert. Dieser Wert ist für das vorliegende Beispiel unbrauchbar; es sind 1.000 neue Datensätze hinzugefügt worden. Der Ausführungsplan kann nicht weiter optimiert werden! Microsoft SQL Server verwendet nicht das Histogramm sondern eine heuristische Vorgehensweise. Sofern eine gleichmäßige Verteilung der Daten in der Detailtabelle vorhanden wäre, wäre dieser Ansatz auf jeden Fall eine Möglichkeit, das Dilemma zu lösen!


OPTIMIZE FOR UNKOWN


Die Option “OPTIMIZE FOR UNKNOWN” weist den Abfrageoptimierer an, beim Kompilieren und Optimieren der Abfrage für alle lokalen Variablen, einschließlich der Parameter, die mit erzwungener Parametrisierung erstellt werden, statistische Daten statt der Anfangswerte zu verwenden. Die Option ist identisch mit der Verwendung von Variablen, die zur Laufzeit instanziiert werden (siehe vorheriges Beispiel). Auch diese Vorgehensweise scheidet für das Beispiel aus.


Optimierung des Index


Das Problem in der Abfrage ist der teure Key Lookup, der die zusätzlich benötigten Informationen aus dem Clustered Index beziehen muss. Um diese Key Lookups zu verhindern, reicht es aus, diese Attribute in den Index mit zu übernehmen. Für das Beispiel ist die Tabelle sehr klein gewählt worden; deswegen ist es – für das Beispiel – unproblematisch. Der Index wird wie folgt angepasst:



CREATE INDEX ix_detail_table_master_id ON dbo.detail_table (master_id) INCLUDE (c1) WITH DROP_EXISTING;

Bedingt durch den Neuaufbau des Index wurden die Statistiken neu erstellt. Es werden 1.000 weitere Datensätze hinzugefügt, um erneut veraltete Statistiken zu erhalten. Anschließend wird die Abfrage mit der neuen [Id] = 1002 abgefragt:



SELECT m.*,
       d.*
FROM   dbo.master_table AS m INNER JOIN dbo.detail_table AS d
       ON (m.id = d.master_id)
WHERE  m.id = 1002;

EXECUTION_PLAN_08


Der teure Key Lookup ist nun verschwunden, da alle Informationen vollständig aus dem Index gelesen werden können. Für das Beispiel dieses Artikels ist diese Variante sicherlich die ideale Lösung – die Realität sieht jedoch etwas anders aus. Da – codebedingt – immer ein SELECT * durch die Applikation ausgeführt wird, müssten alle Attribute der Relation Bestandteil des Index sein. Damit einher geht aber, dass beim Einfügen von neuen Datensätzen die Zeiten für das Laden der Daten verschlechtert werden. Hier muss also entschieden werden zwischen Performance bei der Abfrage oder Performance beim Laden von Daten.


UPDATE STATISTICS manuell ausführen oder als Auftrag implementieren


Eine weitere Möglichkeit besteht darin, die Statistiken – wie weiter oben bereits demonstriert – manuell zu aktualisieren. Durch eine Aktualisierung werden die neuen Werte in das Histogramm übernommen und die Statistiken aktualisiert. Dieses Verfahren ist jedoch für das vorliegende Beispiel unbrauchbar, da die Probleme unmittelbar nach dem Einfügen der neuen Datensätze auftauchen. Der Code müsste also dahingehend geändert werden, dass im Code nach dem Einfügen der neuen Daten erst ein UPDATE STATISTICS ausgeführt wird, bevor die Daten erneut abgefragt werden. Tatsächlich wird diese Option derzeit geprüft – ich persönlich halte sie für ungeeignet, da ein FULLSCAN über die Tabelle ebenfalls sehr rechenintensiv ist.


TraceFlag 2371


Wie oben beschrieben, verwendet Microsoft SQL Server für die Aktualisierung von Statistiken Schwellenwerte, die überschritten sein müssen. Bei kleinen Tabellen sind diese Einstellungen sicherlich ideal. Im aktuellen Fall mit mehreren Millionen Datensätzen wird dieser Schwellenwert von 20% klar zu einem Problem. Bei 500 Millionen Datensätzen müssten zunächst 100.000.000 Änderungen vorgenommen werden, bevor die Statistiken aktualisiert werden. Mit Microsoft SQL Server 2008 R2 SP1 hat Microsoft auf diesen Umstand reagiert und das Traceflag 2371 eingeführt. Mit diesem Traceflag wird auf den konstanten Schwellenwert von 20% verzichtet und der Aktualisierungsintervall für Statistiken wird basierend auf der Anzahl von vorhandenen Datensätzen angepasst. Weitere Details zu diesem Thema können im Blogartikel “Changes to automatic update statistics in SQL Server – traceflag 2371” von Jürgen Thomas nachgelesen werden.


TraceFlag 2389, 2390


Wer nähere Informationen zu diesem Thema nachlesen möchte, der sollte sich den Artikel “Statistics on ascending keys” von Benjamin Nevarez unbedingt durchlesen! Für mein Beispiel sei gesagt, dass das Traceflag unbrauchbar ist, wenn die Anzahl der eingefügten Datensätze zu sehr “streut”. Trotz Verwendung des TF 2389 war maximal eine geschätzte Anzahl von Datensätze von ~11 Datensätzen möglich. Sicherlich eine Verbesserung – aber keine Lösung.


Weiterhin gilt zu beachten, dass die Traceflags in Microsoft SQL Server 2014 nur dann funktionieren, wenn NICHT mit dem neuen “Cardinal Estimator” gearbeitet wird. Dazu gibt es einen kurzen Artikel von Rick Kutschera “Ascending Key in SQL 2014


Zusammenfassung


Das von mir beschriebene Szenario ist in fast allen Datenbanken anzutreffen; um es adäquat zu lösen, sind verschiedene Lösungsansätze denkbar. Entscheidend für die geeignete Lösung sind folgende Faktoren:



  • Verteilung der Datenmenge auf die einzelnen Schlüsselattribute
  • Struktur der Tabelle
  • Analyse der Workloads

Insgesamt ein nicht immer zu 100% lösbares Problem. Der Lösungsansatz, den Index zu erweitern, muss ich verwerfen. Ziel muss es sein, so schnell wie möglich Daten in die Tabellen zu schreiben, da durch die große Datenmenge andere Anwender Probleme bekommen, bereits existierende Datensätze zu bearbeiten (Speicherung wird verzögert).


Das Thema ist auf jeden Fall so interessant, dass ich dazu einen Vortrag vorbereite, den ich auf den Regionaltreffen der PASS Deutschland e. V. vortragen möchte.


Herzlichen Dank fürs Lesen!


Kommentare :

  1. Hallo Uwe,

    In diesem speziellen Beispiel Fall (nur zwei Indizes auf der Detail-Tabelle, einer notwendig für PK, einer für join) würde ich den den Clustered Key als (d.master_id, d.id) setzen. Id nehme ich nur auf um zu verhindern dass ein nutzloser unifier angelegt wird.

    Dann noch ein seperater unique Index auf d.id, der aber keine Include-Spalten braucht (wie oft sucht man einen Detail-Eintrag per id, und wenn dann meisstens eben nur einen!)

    Was hälst du von dem Ansatz?

    AntwortenLöschen
  2. Servus Markus,

    einen herzlichen Gruß in das schöne Wien. Die von Dir favorisierte Variante ist in Bezug auf das Datenvolumen auf jeden Fall die "charmantere" Lösung. Sie würde ebenfalls (wie meine Lösung) den - teuren - Keylookup verhindern. Ich habe durch meine Variante ein größeres Datenvolumen generiert und nehme damit ein größeres Datenvolumen bei DML-Transaktionen in Kauf.

    Dein Ansatz gefällt mir sehr gut - in der Realität jedoch hat diese Variante einen Haken; der Clustered Key wächst von 8 Bytes (bigint) auf 16 Bytes. Bei jedem weiteren Index erhöhe ich den alloziierten Speicher um 4 GB / Index! Selbst, wenn es nur 4 Bytes (int) wären, würde jeder Index zusätzliche 2 GB an Speicher alloziieren.

    Ich habe ja nur eine weitere Demospalte [C1] in der Definition um den KeyLookup zu demonstrieren. Die echte Tabelle hat 62 Attribute und 18 (!) Indexe... Ich werde das aber dennoch mal für unser "Problem" hier berechnen und den Impact einer Prüfung unterziehen.

    Es wird Zeit, dass wir uns endlich mal im echten Leben treffen :) Dieses Thema würde ich sehr gerne vertiefend mit Dir besprechen...

    Alles Gute aus Frankfurt am Main

    AntwortenLöschen