Wie ist WENN NICHT EXISTIERT SELECT THEN INSERT schneller als der UNIQUE-Index?

7

Wie ist in SQL Server ...

Sp:

CREATE PROCEDURE insertToTable
    @field1 VARCHAR(256), @field2 varchar(256), @field3 varchar(256)
AS
BEGIN
    SET NOCOUNT ON

    IF NOT EXISTS (SELECT * FROM my_table WHERE field1 = @field1)
      INSERT INTO my_table
        (field1, field2, field3)
      VALUES (@field1, @field2, @field3);
    ELSE
      THROW 50000, 'xxxxxx', 1;
    END
GO

Tabelle:

CREATE TABLE my_table (
    field1 VARCHAR(256) NOT NULL,
    field2 VARCHAR(256) NOT NULL,
    field3 VARCHAR(256) NOT NULL
);
CREATE INDEX idx_field1 ON my_table(field1);

das oben schneller als das unten?

Sp:

CREATE PROCEDURE insertToTable
    @field1 VARCHAR(256), @field2 varchar(256), @field3 varchar(256)
AS
BEGIN
    SET NOCOUNT ON

    INSERT INTO my_table
        (field1, field2, field3)
    VALUES (@field1, @field2, @field3);
GO

Tabelle:

CREATE TABLE my_table (
    field1 VARCHAR(256) NOT NULL,
    field2 VARCHAR(256) NOT NULL,
    field3 VARCHAR(256) NOT NULL
);
CREATE UNIQUE INDEX idx_field1 ON my_table(field1);

Beispieleingabe:

Feld1: F56yCgZ9AEm9aFpTyjwhERtqNeglYEow

Feld2: BD84CE2A514316164B7448C804B178AD8F6F597E8EC6F25F4D6E36287259C65F67E7206E82A4F8EFD2389C0821C0C70E8278DC5F166D220356B5A15A091A

Feld 3: A18E9049117A77E6A4D41C6CA3FFDEA65D842BF1F57705405B4E66969531D93D

Die Eingabe wird im laufenden Betrieb von der Webanwendung und unter Verwendung vorbereiteter Anweisungen generiert. Ich verwende Jmeter, um Anfragen an meine Web-App zu generieren.

Mit dem UNIQUEIndex verschlechtert sich die Leistung der Einfügungen nach 100.000 Einfügungen und wird schlechter.

Mit dem NON UNIQUEIndex und einer manuellen Überprüfung mit IF NOT EXISTS SELECTist die Leistung auch bei Millionen eingefügter Datensätze konstant.

Die Werte sind so eindeutig, dass niemals ein Duplikat generiert wird. Auch nach einigen Millionen eingefügten Werten.

user547
quelle
Möglicherweise müssen Sie keine exklusiven Sperren erhalten und die Transaktion nicht zurücksetzen.
eckes
1
Ich habe Schwierigkeiten, hier das ganze Bild zu bekommen. Können Sie den vollständigen SQL-Code veröffentlichen? Für beide Fälle ...
Tibor Karaszi
1
Wie messen Sie die Geschwindigkeit? Verwenden Sie SET STATISTICS TIME ON? Die schnellere Abfrage läuft möglicherweise parallel (mit mehr CPU-Zeit) und die langsamere Abfrage wird möglicherweise mit einem Thread ausgeführt (weniger CPU-Zeit).
Pacreely
1
Bitte veröffentlichen Sie ein vollständiges SQL-Skript, das das Phänomen demonstriert, nach dem Sie fragen
Martin Smith
1
Daher habe ich ein Skript erstellt und es für eine neu erstellte SQL Server 2017-Datenbank ausgeführt, deren Protokoll- und Datendatei jeweils eine Größe von 4 GB haben. Nicht eindeutiger Index und hausgemachte (und nicht threadsichere) eindeutige Prüfung dauerten 231,101 Sekunden. Eine mit unque Index dauerte 194,940 Sekunden. Skript verwendet pastebin.com/nCB7QFt3 . Skriptausgabe pastebin.com/eHDfdHhc
Martin Smith

Antworten:

5

ENDGÜLTIGES UPDATE:

Es ist das INSERT, das die Dinge wirklich verlangsamt.

Wenn ein eindeutiger Index vorhanden ist, muss SQL bei jedem neuen Datensatz, den Sie hinzufügen, prüfen, ob der Wert bereits vorhanden ist. Wenn die Tabelle wächst, nimmt die Anzahl der Querverweise zu. Ein nicht eindeutiger Index erfordert keine Querverweise, sodass die Leistung konstant ist.

Eindeutige Indizes sind für SELECT-Anweisungen normalerweise schneller, dies ist jedoch mit Kosten verbunden, wenn eine Tabelle aktualisiert wird.

Im Folgenden wird erläutert, warum SELECT in einem eindeutigen Index manchmal langsamer sein kann

Ich habe Ihre Situation teilweise bis zu einem Punkt neu erstellt, an dem es meiner Meinung nach auf die Kombination von Parameter-Sniffing und SQL-Präferenzen mit dem NON-UNIQUE-Index auf einem HEAP zurückzuführen ist.

Richten Sie 2 Testtabellen ein, eine davon ist ein Heap (genau wie Ihre Tabelle).

CREATE TABLE dbo.TEST1(ID VARCHAR(255) NOT NULL,TXT1 VARCHAR(255) NOT NULL,TXT2 VARCHAR(255) NOT NULL)
CREATE TABLE dbo.TEST2(ID VARCHAR(255) NOT NULL,TXT1 VARCHAR(255) NOT NULL,TXT2 VARCHAR(255) NOT NULL)
GO
INSERT INTO dbo.TEST1 VALUES(NEWID(),NEWID(),NEWID())
GO 30000

INSERT INTO dbo.TEST2
SELECT * FROM dbo.TEST1
GO

CREATE CLUSTERED INDEX cidx ON dbo.TEST1 (ID)
CREATE INDEX idx_nu ON dbo.TEST1 (ID)
CREATE UNIQUE INDEX idx_u ON dbo.TEST1 (ID)

CREATE INDEX idx_nu ON dbo.TEST2 (ID)
CREATE UNIQUE INDEX idx_u ON dbo.TEST2 (ID)

Überprüfen Sie den Footprint der Indizes. Auf dem HEAP hat der UNIQUE-Index einen geringeren Footprint als der NON-UNIQUE-Index. (Möglicherweise enthalten die Seiten des NON-UNIQUE-Index zusätzliche - möglicherweise nützliche - Informationen.) (Hinweis: Nachdem Sie den obigen Code mehrmals ausgeführt haben, unterscheidet sich die Seitenzahl nicht, wahrscheinlich aufgrund von Caching. Ändern Sie den "GO 30000", um das Problem zu beheben Problem.)

SELECT
    s.name AS SchemaName,
    t.name AS TableName,
    i.name AS IndexName,
    p.row_count,
    SUM (p.used_page_count) as used_pages_count,
    SUM (CASE
            WHEN (i.index_id < 2) THEN (in_row_data_page_count + lob_used_page_count + row_overflow_used_page_count)
            ELSE lob_used_page_count + row_overflow_used_page_count
        END) as pages
FROM 
    sys.dm_db_partition_stats  AS p 
        JOIN sys.tables AS t 
            ON 
            p.object_id = t.object_id
        JOIN sys.indexes AS i 
            ON 
            i.[object_id] = t.[object_id] 
            AND 
            p.index_id = i.index_id
        JOIN sys.schemas AS s 
            ON
            t.schema_id = s.schema_id
WHERE
    t.name IN ('TEST1','TEST2')
GROUP BY 
    s.name
    ,t.name
    ,i.name
    ,p.row_count

Fragen Sie nun die Tabellen mit Literalen und Variablen ab.

--SCAN of the UNIQUE index
DECLARE @account_id VARCHAR(255) = (SELECT TOP 1 ID FROM dbo.TEST2 WHERE ID like '%A%') 

--Parameter Sniffing kicks in --The optimiser doesn't know the value of @account_id

--SEEK of the CLUSTERED index 
DECLARE @ID1 VARCHAR(255)  = (SELECT TOP 1 ID FROM dbo.TEST1 WHERE ID = @account_id)

--SEEK of the NON UNIQUE index
DECLARE @ID2 VARCHAR(255)  = (SELECT TOP 1 ID FROM dbo.TEST2 WHERE ID = @account_id)

Aus irgendeinem Grund bevorzugt SQL den NON UNIQUE-Index für einen HEAP, wenn SEEK-Operationen ausgeführt werden.

Hier ist, was ich denke, los ist. Wenn der nicht eindeutige Index mehr Seiten enthält, enthält das entsprechende Histogramm in den STATS mehr SCHRITTE. Führen Sie den folgenden Code aus.

DBCC SHOW_STATISTICS ( 'TEST2' , 'idx_nu' )
DBCC SHOW_STATISTICS ( 'TEST2' , 'idx_u' )

Die zusätzlichen SCHRITTE erstellen eine detailliertere Ansicht des zugrunde liegenden Index, sodass der Optimierer (der weiß, dass EQ_ROWS immer 1 ist) eine bessere Kardinalitätsschätzung aus dem nicht eindeutigen Index erhält.

friedlich
quelle
Warum sollte man sich also mit dem EINZIGARTIGEN Index beschäftigen, wenn SELECT, WENN NICHT EXISTIERT, einen besseren Job macht?
user547
@ user547 Ein eindeutiger Index sollte schneller sein, wenn Select-Anweisungen für Tabellen ausgeführt werden. Dies ist jedoch mit Kosten verbunden, wenn Aktualisierungen durchgeführt werden. Dies hängt von der Art der Aktivität ab, der Ihr Tisch hauptsächlich ausgesetzt ist. Wenn möglich, würde ich den eindeutigen Index während der EINSÄTZE deaktivieren und den Index dann neu erstellen, wenn die Einfügungen abgeschlossen sind. Mein ausgewähltes Beispiel von oben ist ein sehr seltener Fall, den ich gerade interessant fand. ALTER INDEX idx ON dbo.Mytable DISABLE; ALTER INDEX idx ON dbo.Mytable REBUILD;
Pacreely
Ah kann nicht deaktivieren. Es ist eine "Echtzeit" App. Das heißt, "Benutzer" können sich nach Bedarf registrieren. Sie kennen sich wie eine normale Anmeldeseite aus.
user547
1
Ihr Argument ist nicht wirklich sinnvoll, da sie manuell die gleichen "Querverweise" wie der Index ausführen. Wenn dies jedoch als Teil einer Einfügung erfolgt, sollte dies trivial sein, da die richtige Stelle im Index gefunden werden muss, um die Zeile trotzdem einzufügen.
Martin Smith
1
Sie haben keinen Repro geliefert, den wir tatsächlich ausführen können, der zeigt, dass dieses Phänomen überhaupt existiert. Es könnte ein Fehler mit ihrer Benchmarking-Methode sein
Martin Smith