it-swarm.com.de

Overhead der Index-Eindeutigkeit

Ich habe in meinem Büro eine laufende Debatte mit verschiedenen Entwicklern über die Kosten eines Index geführt und darüber, ob die Eindeutigkeit vorteilhaft oder kostspielig ist (wahrscheinlich beides). Der Kern des Problems sind unsere konkurrierenden Ressourcen.

Hintergrund

Ich habe zuvor eine Diskussion gelesen, in der angegeben wurde, dass ein Unique -Index keine zusätzlichen Kosten für die Wartung darstellt, da eine Insert -Operation implizit prüft, wo er in den B-Baum passt und ob ein Duplikat vorhanden ist In einem nicht eindeutigen Index gefunden, hängt ein Eindeutiger an das Ende des Schlüssels an, fügt ihn aber ansonsten direkt ein. In dieser Abfolge von Ereignissen verursacht ein Unique -Index keine zusätzlichen Kosten.

Mein Mitarbeiter bekämpft diese Aussage, indem er sagt, dass Unique als zweite Operation nach der Suche nach der neuen Position im B-Baum erzwungen wird und daher teurer zu pflegen ist als ein nicht eindeutiger Index.

Im schlimmsten Fall habe ich Tabellen mit einer Identitätsspalte (von Natur aus eindeutig) gesehen, die der Clustering-Schlüssel der Tabelle ist, aber ausdrücklich als nicht eindeutig angegeben wird. Auf der anderen Seite ist meine Besessenheit von der Eindeutigkeit am schlimmsten, und alle Indizes werden als eindeutig erstellt. Wenn es nicht möglich ist, eine explizit eindeutige Beziehung zu einem Index zu definieren, füge ich die PK der Tabelle an das Ende des Index an, um die Einzigartigkeit ist garantiert.

Ich bin häufig an Codeüberprüfungen für das Entwicklerteam beteiligt und muss in der Lage sein, allgemeine Richtlinien für deren Befolgung anzugeben. Ja, jeder Index sollte ausgewertet werden. Wenn Sie jedoch fünf Server mit jeweils Tausenden von Tabellen und bis zu zwanzig Indizes für eine Tabelle haben, müssen Sie in der Lage sein, einige einfache Regeln anzuwenden, um ein bestimmtes Qualitätsniveau sicherzustellen.

Frage

Hat die Eindeutigkeit zusätzliche Kosten für das Back-End eines Insert im Vergleich zu den Kosten für die Verwaltung eines nicht eindeutigen Index? Was ist zweitens falsch daran, den Primärschlüssel einer Tabelle an das Ende eines Index anzuhängen, um die Eindeutigkeit sicherzustellen?

Beispieltabellendefinition

create table #test_index
    (
    id int not null identity(1, 1),
    dt datetime not null default(current_timestamp),
    val varchar(100) not null,
    is_deleted bit not null default(0),
    primary key nonclustered(id desc),
    unique clustered(dt desc, id desc)
    );

create index
    [nonunique_nonclustered_example]
on #test_index
    (is_deleted)
include
    (val);

create unique index
    [unique_nonclustered_example]
on #test_index
    (is_deleted, dt desc, id desc)
include
    (val);

Beispiel

Ein Beispiel dafür, warum ich den Schlüssel Unique am Ende eines Index hinzufügen würde, finden Sie in einer unserer Faktentabellen. Es gibt eine Primary Key, Die eine Identity Spalte ist. Clustered Index Ist jedoch stattdessen die Spalte für das Partitionierungsschema, gefolgt von drei Fremdschlüsseldimensionen ohne Eindeutigkeit. Die Auswahl der Leistung in dieser Tabelle ist miserabel, und ich erhalte häufig bessere Suchzeiten, wenn ich Primary Key Mit einer Schlüsselsuche verwende, anstatt Clustered Index Zu nutzen. Andere Tabellen, die einem ähnlichen Design folgen, an deren Ende jedoch Primary Key Angehängt ist, weisen eine erheblich bessere Leistung auf.

-- date_int is equivalent to convert(int, convert(varchar, current_timestamp, 112))
if not exists(select * from sys.partition_functions where [name] = N'pf_date_int')
    create partition function 
        pf_date_int (int) 
    as range right for values 
        (19000101, 20180101, 20180401, 20180701, 20181001, 20190101, 20190401, 20190701);
go

if not exists(select * from sys.partition_schemes where [name] = N'ps_date_int')
    create partition scheme 
        ps_date_int
    as partition 
        pf_date_int all 
    to 
        ([PRIMARY]);
go

if not exists(select * from sys.objects where [object_id] = OBJECT_ID(N'dbo.bad_fact_table'))
    create table dbo.bad_fact_table
        (
        id int not null, -- Identity implemented elsewhere, and CDC populates
        date_int int not null,
        dt date not null,
        group_id int not null,
        group_entity_id int not null, -- member of group
        fk_id int not null,
        -- tons of other columns
        primary key nonclustered(id, date_int),
        index [ci_bad_fact_table] clustered (date_int, group_id, group_entity_id, fk_id)
        )
    on ps_date_int(date_int);
go

if not exists(select * from sys.objects where [object_id] = OBJECT_ID(N'dbo.better_fact_table'))
    create table dbo.better_fact_table
        (
        id int not null, -- Identity implemented elsewhere, and CDC populates
        date_int int not null,
        dt date not null,
        group_id int not null,
        group_entity_id int not null, -- member of group
        -- tons of other columns
        primary key nonclustered(id, date_int),
        index [ci_better_fact_table] clustered(date_int, group_id, group_entity_id, id)
        )
    on ps_date_int(date_int);
go
14
Solonotix

Ich bin häufig an Codeüberprüfungen für das Entwicklerteam beteiligt und muss in der Lage sein, allgemeine Richtlinien für deren Befolgung anzugeben.

Die Umgebung, in die ich derzeit involviert bin, hat 250 Server mit 2500 Datenbanken. Ich habe an Systemen mit 30.000 Datenbanken gearbeitet. Richtlinien für die Indizierung sollten sich um die Namenskonvention usw. drehen und keine "Regeln" für die Spalten sein, die in einen Index aufgenommen werden sollen - Jeder einzelne Index sollte erstellt werden um der richtige Index für diese bestimmte Geschäftsregel oder diesen Code zu sein, der die Tabelle berührt.

Hat die Eindeutigkeit zusätzliche Kosten für das Back-End eines Insert im Vergleich zu den Kosten für die Verwaltung eines nicht eindeutigen Index? Was ist zweitens falsch daran, den Primärschlüssel einer Tabelle an das Ende eines Index anzuhängen, um die Eindeutigkeit sicherzustellen?

Das Hinzufügen der Primärschlüsselspalte am Ende eines nicht eindeutigen Index, um ihn eindeutig zu machen, erscheint mir als Anti-Pattern. Wenn Geschäftsregeln vorschreiben, dass die Daten eindeutig sein sollen, fügen Sie der Spalte eine eindeutige Einschränkung hinzu. Dadurch wird automatisch ein eindeutiger Index erstellt. Wenn Sie eine Spalte für die Leistung indizieren, warum sollten Sie dem Index eine Spalte hinzufügen?

Selbst wenn Ihre Annahme, dass das Erzwingen der Eindeutigkeit keinen zusätzlichen Aufwand verursacht, richtig ist (was in bestimmten Fällen nicht ist), womit lösen Sie? den Index unnötig komplizieren?

In dem speziellen Fall, in dem der Primärschlüssel am Ende Ihres Indexschlüssels hinzugefügt wird, damit die Indexdefinition den Modifikator UNIQUE enthält, macht dies tatsächlich keinen Unterschied zur physischen Indexstruktur auf der Festplatte. Dies liegt an der Art der Struktur von B-Tree-Indexschlüsseln, da diese immer eindeutig sein müssen.

Als David Browne in einem Kommentar erwähnt:

Da jeder nicht gruppierte Index als eindeutiger Index gespeichert wird, fallen beim Einfügen in einen eindeutigen Index keine zusätzlichen Kosten an. Tatsächlich würden die einzigen zusätzlichen Kosten darin bestehen, dass ein Kandidatenschlüssel nicht als eindeutiger Index deklariert wird , was dazu führen würde, dass die gruppierten Indexschlüssel an den Index angehängt werden Schlüssel.

Nehmen Sie das folgende minimal vollständiges und überprüfbares Beispiel :

USE tempdb;

DROP TABLE IF EXISTS dbo.IndexTest;
CREATE TABLE dbo.IndexTest
(
    id int NOT NULL
        CONSTRAINT IndexTest_pk
        PRIMARY KEY
        CLUSTERED
        IDENTITY(1,1)
    , rowDate datetime NOT NULL
);

Ich werde zwei Indizes hinzufügen, die identisch sind, mit Ausnahme des Hinzufügens des Primärschlüssels am Ende der Definition des zweiten Indexschlüssels:

CREATE INDEX IndexTest_rowDate_ix01
ON dbo.IndexTest(rowDate);

CREATE UNIQUE INDEX IndexTest_rowDate_ix02
ON dbo.IndexTest(rowDate, id);

Als nächstes werden wir mehrere Zeilen zur Tabelle:

INSERT INTO dbo.IndexTest (rowDate)
VALUES (DATEADD(SECOND, 0, GETDATE()))
     , (DATEADD(SECOND, 0, GETDATE()))
     , (DATEADD(SECOND, 0, GETDATE()))
     , (DATEADD(SECOND, 1, GETDATE()))
     , (DATEADD(SECOND, 2, GETDATE()));

Wie Sie oben sehen können, enthalten drei Zeilen denselben Wert für die Spalte rowDate, und zwei Zeilen enthalten eindeutige Werte.

Als nächstes betrachten wir die physischen Seitenstrukturen für jeden Index mit dem undokumentierten Befehl DBCC PAGE:

DECLARE @dbid int = DB_ID();
DECLARE @fileid int;
DECLARE @pageid int;
DECLARE @indexid int;

SELECT @fileid = ddpa.allocated_page_file_id
    , @pageid = ddpa.allocated_page_page_id
FROM sys.indexes i 
CROSS APPLY sys.dm_db_database_page_allocations(DB_ID(), i.object_id, i.index_id, NULL, 'LIMITED') ddpa
WHERE i.name = N'IndexTest_rowDate_ix01'
    AND ddpa.is_allocated = 1
    AND ddpa.is_iam_page = 0;

PRINT N'*************************************** IndexTest_rowDate_ix01 *****************************************';
DBCC TRACEON(3604);
DBCC PAGE (@dbid, @fileid, @pageid, 1);
DBCC TRACEON(3604);
PRINT N'*************************************** IndexTest_rowDate_ix01 *****************************************';

SELECT @fileid = ddpa.allocated_page_file_id
    , @pageid = ddpa.allocated_page_page_id
FROM sys.indexes i 
CROSS APPLY sys.dm_db_database_page_allocations(DB_ID(), i.object_id, i.index_id, NULL, 'LIMITED') ddpa
WHERE i.name = N'IndexTest_rowDate_ix02'
    AND ddpa.is_allocated = 1
    AND ddpa.is_iam_page = 0;

PRINT N'*************************************** IndexTest_rowDate_ix02 *****************************************';
DBCC TRACEON(3604);
DBCC PAGE (@dbid, @fileid, @pageid, 1);
DBCC TRACEON(3604);
PRINT N'*************************************** IndexTest_rowDate_ix02 *****************************************';

Ich habe die Ausgabe mit Beyond Compare betrachtet und bis auf offensichtliche Unterschiede bei den Zuordnungsseiten-IDs usw. sind die beiden Indexstrukturen identisch.

(enter image description here

Sie können das oben Gesagte so verstehen, dass das Einfügen des Primärschlüssels in jeden Index und das Definieren als eindeutig A Good Thing ™ ist, da dies ohnehin unter dem Deckmantel geschieht. Ich würde diese Annahme nicht treffen und würde vorschlagen, einen Index nur dann als eindeutig zu definieren, wenn die natürlichen Daten im Index tatsächlich bereits eindeutig sind.

Im Interwebz gibt es mehrere hervorragende Ressourcen zu diesem Thema, darunter:

Zu Ihrer Information, das bloße Vorhandensein einer identity -Spalte garantiert keine Eindeutigkeit. Sie müssen die Spalte als Primärschlüssel oder mit einer eindeutigen Einschränkung definieren, um sicherzustellen, dass die in dieser Spalte gespeicherten Werte tatsächlich eindeutig sind. Mit der Anweisung SET IDENTITY_INSERT schema.table ON; Können Sie nicht eindeutige Werte in eine als identity definierte Spalte einfügen.

16
Max Vernon

Nur ein Add-On zu Max 'ausgezeichnete Antwort .

Wenn es darum geht, einen nicht eindeutigen Clustered-Index zu erstellen, erstellt SQL Server sowieso im Hintergrund einen so genannten Uniquifier.

Dieses Uniquifier kann in Zukunft potenzielle Probleme verursachen, wenn Ihre Plattform viele CRUD-Operationen ausführt, da dieses Uniquifier nur 4 Byte groß ist (eine grundlegende 32-Bit-Ganzzahl). Wenn Ihr System also über viele CRUD-Operationen verfügt, werden möglicherweise alle verfügbaren eindeutigen Nummern aufgebraucht, und plötzlich wird eine Fehlermeldung angezeigt, und Sie können keine Daten mehr in Ihre Tabellen einfügen (weil dies der Fall ist) Sie müssen Ihren neu eingefügten Zeilen keine eindeutigen Werte mehr zuweisen.

In diesem Fall erhalten Sie folgende Fehlermeldung:

The maximum system-generated unique value for a duplicate group 
was exceeded for index with partition ID (someID). 

Dropping and re-creating the index may resolve this;
otherwise, use another clustering key.

Fehler 666 (der obige Fehler) tritt auf, wenn uniquifier für einen einzelnen Satz nicht eindeutiger Schlüssel mehr als 2.147.483.647 Zeilen belegt.

Sie müssen also entweder ~ 2 Milliarden Zeilen für einen einzelnen Schlüsselwert haben, oder Sie müssen einen einzelnen Schlüsselwert ~ 2 Milliarden Mal geändert haben, um diesen Fehler zu sehen. Daher ist es nicht sehr wahrscheinlich, dass Sie auf diese Einschränkung stoßen.

5
Chessbrain