it-swarm.com.de

Beeinflussen die Spalten varchar (max), nvarchar (max) und varbinary (max) ausgewählte Abfragen?

Betrachten Sie diese Tabelle:

create table Books
(
    Id bigint not null primary key identity(1, 1),
    UniqueToken varchar(100) not null,
    [Text] nvarchar(max) not null
)

Stellen wir uns vor, wir haben über 100.000 Bücher in dieser Tabelle.

Jetzt erhalten wir 10.000 Buchdaten zum Einfügen in diese Tabelle, von denen einige doppelt vorhanden sind. Wir müssen also zuerst Duplikate filtern und dann neue Bücher einfügen.

Eine Möglichkeit, nach Duplikaten zu suchen, ist folgende:

select UniqueToken
from Books 
where UniqueToken in 
(
    'first unique token',
    'second unique token'
    -- 10,000 items here
)

Beeinflusst das Vorhandensein der Spalte Text die Leistung dieser Abfrage? Wenn ja, wie können wir es optimieren?

P.S. Ich habe die gleiche Struktur für einige andere Daten. Und es funktioniert nicht gut. Ein Freund sagte mir, dass ich meinen Tisch wie folgt in zwei Tische aufteilen sollte:

create table BookUniqueTokens 
(
    Id bigint not null primary key identity(1, 1),
    UniqueToken varchar(100)
)

create table Books
(
    Id bigint not null primary key,
    [Text] nvarchar(max)
)

Und ich muss meinen Algorithmus zur doppelten Suche nur für die erste Tabelle ausführen und dann Daten in beide einfügen. Auf diese Weise behauptete er, dass die Leistung viel besser wird, weil die Tabellen physisch getrennt sind. Er behauptete, dass [Text] Spalte wirkt sich auf jede select Abfrage in der UniqueToken Spalte aus.

5
Saeed Neamati

Beispiele

Betrachten Sie Ihre Abfrage mit 8 Filterprädikaten in Ihrer IN -Klausel für einen Datensatz von 10K-Datensätzen.

select UniqueToken
from Books 
where UniqueToken in 
(
    'Unique token 1',
    'Unique token 2',
    'Unique token 3',
    'Unique token 4',
    'Unique token 5',
    'Unique token 6',
    'Unique token 9999',
    'Unique token 5000'
    -- 10,000 items here
);

Ein Clustered-Index-Scan wird verwendet, es sind keine anderen Indizes in dieser Testtabelle vorhanden

(enter image description here

Mit einer Datengröße von 216 Bytes .

Sie sollten auch beachten, dass sich die Filter OR auch bei 8 Datensätzen stapeln.

Die Lesungen, die auf dieser Tabelle passiert sind:

(enter image description here

Credits to Statistiksparser.

Wenn Sie die Spalte Text in den ausgewählten Teil Ihrer Abfrage aufnehmen, ändert sich die tatsächliche Datengröße drastisch:

select UniqueToken,Text
from Books 
where UniqueToken in 
(
    'Unique token 1',
    'Unique token 2',
    'Unique token 3',
    'Unique token 4',
    'Unique token 5',
    'Unique token 6',
    'Unique token 9999',
    'Unique token 5000'
    -- 10,000 items here
);

Wieder der Clustered Index Scan mit einem Restprädikat :

(enter image description here

Aber mit einem Datensatz von 32KB .

Da es fast 1000 lob logische Lesevorgänge gibt:

(enter image description here

Wenn wir nun die beiden fraglichen Tabellen erstellen und sie mit denselben 10.000 Datensätzen füllen

Ausführen derselben Auswahl ohne Text. Denken Sie daran, dass wir bei Verwendung der Tabelle Books 99 logische Lesevorgänge hatten.

select UniqueToken
from BookUniqueTokens 
where UniqueToken in 
(
    'Unique token 1',
    'Unique token 2',
    'Unique token 3',
    'Unique token 4',
    'Unique token 5',
    'Unique token 6',
    'Unique token 9999',
    'Unique token 5000'
    -- 10,000 items here
)

Die Lesevorgänge auf BookUniqueTokens sind niedriger, 67 statt 99.

(enter image description here

Wir können dies bis zu den Seiten in der ursprünglichen Books -Tabelle und den Seiten in der neuen Tabelle ohne Text zurückverfolgen.

Original Books Tabelle:

(enter image description here

Neue BookUniqueTokens Tabelle

(enter image description here

Alle Seiten + (2 Overhead-Seiten?) Werden also aus dem Clustered-Index gelesen.

Warum gibt es einen Unterschied und warum ist der Unterschied nicht größer? Immerhin ist der Unterschied in der Datengröße sehr groß (Lob-Daten <> Keine Lob-Daten)

Books Datenraum

(enter image description here

BooksWithText Datenraum

(enter image description here

Der Grund dafür ist ROW_OVERFLOW_DATA .

Wenn Daten größer als 8 KB werden, werden die Daten auf verschiedenen Seiten als ROW_OVERFLOW_DATA gespeichert.

Ok, wenn Lob-Daten auf verschiedenen Seiten gespeichert sind, warum sind die Seitengrößen dieser beiden Clustered-Indizes nicht gleich?

Aufgrund des 24-Byte-Zeigers, der dem Clustered-Index hinzugefügt wurde, um jede dieser Seiten zu verfolgen. Schließlich muss der SQL Server wissen, wo er die Lob-Daten finden kann.

Quelle


Um Ihre Fragen zu beantworten

Er behauptete, dass die Spalte [Text] jede ausgewählte Abfrage in der UniqueToken-Spalte beeinflusst.

Und

Beeinträchtigt das Vorhandensein einer Textspalte die Leistung dieser Abfrage? Wenn ja, wie können wir es optimieren?

Wenn es sich bei den gespeicherten Daten tatsächlich um Lob-Daten handelt und die in der Antwort angegebene Abfrage verwendet wird

Aufgrund der 24-Byte-Zeiger entsteht ein gewisser Overhead.

Abhängig davon, dass die Ausführungen/min nicht verrückt hoch sind, würde ich sagen, dass dies selbst bei 100.000 Datensätzen vernachlässigbar ist.

Denken Sie daran, dass dieser Overhead nur auftritt, wenn ein Index verwendet wird, der Text enthält, z. B. der Clustered-Index.

Aber was ist, wenn der Clustered-Index-Scan verwendet wird und die Lob-Daten 8 KB nicht überschreiten?

Wenn die Daten 8 KB nicht überschreiten und Sie keinen Index für UniqueToken haben, kann der Overhead größer sein. auch wenn Sie die Spalte Text nicht auswählen.

Logisches Lesen von 10.000 Datensätzen, wenn der Text nur 137 Zeichen lang ist (alle Datensätze):

Tabelle 'Books2'. Scananzahl 1, logische Lesevorgänge 419

Aufgrund all dieser zusätzlichen Daten befinden sich diese auf den gruppierten Indexseiten.

Wiederum wird dieses Problem durch einen Index für UniqueToken (ohne die Spalte Text) behoben.

Wie von @David Browne - Microsoft hervorgehoben, können Sie die Daten auch außerhalb der Zeile speichern, um diesen Overhead nicht zum Clustered-Index hinzuzufügen, wenn Sie diese Textspalte nicht auswählen.

Wenn Sie möchten, dass der Text außerhalb der Zeile gespeichert wird, können Sie dies auch erzwingen, ohne eine separate Tabelle zu verwenden. Setzen Sie einfach die Option 'Typen großer Werte außerhalb der Zeile' mit sp_tableoption. docs.Microsoft.com/en-us/sql/relational-databases

TL; DR

Basierend auf der angegebenen Abfrage sollte die Indizierung von UniqueToken ohne Einbeziehung von TEXT Ihre Probleme beheben. Zusätzlich würde ich anstelle der Anweisung IN eine temporäre Tabelle oder einen Tabellentyp verwenden, um die Filterung durchzuführen.

BEARBEITEN:

ja, es gibt einen nicht gruppierten Index für UniqueToken

Ihre Beispielabfrage berührt nicht die Spalte Text, und basierend auf der Abfrage sollte dies ein Deckungsindex sein.

Wenn wir dies an den drei zuvor verwendeten Tabellen testen (UniqueToken + Lob-Daten, Nur UniqueToken, UniqueToken + 137 Char-Daten in der Spalte nvarchar (max))

CREATE INDEX [IX_Books_UniqueToken] ON Books(UniqueToken);
CREATE INDEX [IX_BookUniqueTokens_UniqueToken]  ON BookUniqueTokens(UniqueToken);
CREATE INDEX [IX_Books2_UniqueToken] ON Books2(UniqueToken);

Die Lesevorgänge bleiben für diese drei Tabellen gleich, da der nicht gruppierte Index verwendet wird.

Table 'Books'. Scan count 8, logical reads 16, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

Table 'BookUniqueTokens'. Scan count 8, logical reads 16, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

Table 'Books2'. Scan count 8, logical reads 16, physical reads 5, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

Zusätzliche Details

von @David Browne - Microsoft

Wenn Sie möchten, dass der Text außerhalb der Zeile gespeichert wird, können Sie dies auch erzwingen, ohne eine separate Tabelle zu verwenden. Setzen Sie einfach die Option 'Typen großer Werte außerhalb der Zeile' mit sp_tableoption. docs.Microsoft.com/en-us/sql/relational-databases/

Denken Sie daran, dass Sie Ihre Indizes neu erstellen müssen, damit dies auf bereits ausgefüllte Daten wirksam wird.

Von @Erik Darling

Auf

Filterung nach Lob-Daten ist zum Kotzen.

Ihre Speicherzuweisungen gehen möglicherweise über das Dach, wenn Sie größere Datentypen verwenden, was sich auf die Leistung auswirkt.

6
Randi Vertongen

Technisch gesehen verringert alles, was mehr Platz auf einer Datenseite beansprucht, sodass die Daten mehr Datenseiten erfordern, die Leistung, selbst wenn sie so gering ist, dass sie nicht einfach gemessen werden kann. Mehr Datenseiten bedeuten jedoch mehr Vorgänge zum Lesen von mehr Seiten und mehr Speicher zum Speichern von mehr Datenseiten usw.

Wenn Sie also einen Heap oder Index scannen, kann das Vorhandensein einer NVARCHAR(MAX) - Spalte die Leistung beeinträchtigen , auch wenn Sie diese nicht auswählen . Wenn Sie beispielsweise 5000 bis 7000 Bytes pro Zeile haben, wird dieses in dem in der Frage gezeigten Schema in der Zeile gespeichert, sodass mehr Datenseiten erforderlich sind. 8100 Bytes (ungefähr) oder mehr garantieren jedoch, dass die Daten außerhalb der Zeile mit nur einem Zeiger auf die LOB-Seite (n) gespeichert werden, sodass dies nicht so schlecht wäre.

Aber in Ihrem Fall sollte es nicht annähernd so wichtig sein (oder überhaupt nicht), wenn Sie ein NVARCHAR(MAX) haben, da Sie erwähnt haben, dass UniqueToken einen nicht gruppierten Index hat. Spalte mit 5000-7000 Bytes (verursacht 1 Seite pro Zeile), da die Abfrage den Index betrachten sollte, der nur die Spalten Id und UniqueToken enthält. Und die Operation sollte eine Suche anstelle eines Scans durchführen, damit nicht alle Datenseiten im Index gelesen werden.

Letzte Überlegung: Es sei denn, Sie haben wirklich alte Hardware (dh nein RAM und/oder andere Prozesse, die die Festplatte/CPU/RAM belasten. In diesem Fall wären die meisten Abfragen betroffen, nicht nur diese). dann sind 100.000 Zeilen nicht viele Zeilen. Tatsächlich sind es nicht einmal viele Zeilen. 1 Million Zeilen wären nicht einmal viele Zeilen, um hier einen großen Unterschied zu machen.

Unter der Annahme, dass Ihre Abfrage tatsächlich den nicht gruppierten Index verwendet, sollten wir meines Erachtens irgendwo neben der Spalte NVARCHAR(MAX) nach dem Problem suchen. Dies bedeutet nicht, dass manchmal das Aufteilen einer Tabelle in zwei Tabellen nicht die beste Wahl ist. Es ist nur zweifelhaft, ob dies angesichts der bereitgestellten Informationen hier hilfreich ist .

Die drei Stellen, an denen ich nach Verbesserungen suchen würde, sind:

  1. Explizite Schemanamen : Dies ist geringfügig, aber stellt schemabasierten Objekten immer ihren Schemanamen voran. Sie sollten also dbo.Books Statt nur Books verwenden. Dies hilft nicht nur in Fällen, in denen mehrere Schemas verwendet werden und unterschiedliche Benutzer unterschiedliche Standardschemata haben, sondern reduziert auch einige Sperren, die auftreten, wenn das Schema nicht explizit angegeben wird und SQL Server einige Stellen darauf überprüfen muss.

  2. Die IN -Liste : Diese sind praktisch, aber nicht für ihre Skalierbarkeit bekannt. IN Listen werden für jedes Element in der Liste zu einer OR Bedingung erweitert. Bedeutung:

    where UniqueToken in 
    (
        'first unique token',
        'second unique token'
        -- 10,000 items here
    )
    

    wird:

    where UniqueToken = 'first unique token'
    OR UniqueToken = 'second unique token'
    -- 10,000 items here (9,998 more OR conditions)
    

    Wenn Sie der Liste weitere Elemente hinzufügen, erhalten Sie mehr OR Bedingungen.

    Anstatt eine IN-Liste dynamisch zu erstellen, erstellen Sie eine lokale temporäre Tabelle und erstellen Sie die Liste der INSERT -Anweisungen. Schließen Sie außerdem alle in eine Transaktion ein, um den Transaktionsaufwand zu vermeiden, der sonst pro INSERT auftreten würde (wodurch 10.000 Transaktionen auf 1 reduziert werden):

    CREATE TABLE #UniqueTokens
    (
      UniqueToken VARCHAR(100) NOT NULL
                  COLLATE Latin1_General_100_BIN2
                  PRIMARY KEY
    );
    
    BEGIN TRAN;
    
    ..dynamically generated INSERT INTO #UniqueTokens (UniqueToken) VALUES ('...');
    
    COMMIT TRAN;
    

    Nachdem Sie diese Liste geladen haben, können Sie sie wie folgt verwenden, um denselben Satz doppelter Token zu erhalten:

    SELECT bk.[UniqueToken]
    FROM   dbo.Books bk
    INNER JOIN #UniqueTokens tmp
            ON tmp.[UniqueToken] = bk.[UniqueToken];
    

    Oder wenn Sie wissen möchten, welche der 10.000 neuen Einträge Sie laden können, möchten Sie wirklich die Liste der nicht - doppelten Token, damit Sie dies können füge diese ein, richtig? In diesem Fall würden Sie Folgendes tun:

    SELECT tmp.[UniqueToken]
    FROM   #UniqueTokens tmp
    WHERE  NOT EXISTS(SELECT *
                      FROM   dbo.Books bk
                      WHERE  bk.[UniqueToken] = tmp.[UniqueToken]);
    
  3. String-Vergleich : Wenn kein spezifischer Bedarf für Vergleiche ohne Berücksichtigung der Groß- und Kleinschreibung und/oder ohne Berücksichtigung des Akzents in Bezug auf UniqueToken besteht und davon ausgegangen wird, dass die Datenbank, in der Sie diese Tabelle erstellt haben (das verwendet nicht die COLLATE -Klausel für die Spalte [UniqueToken]) hat keine binäre Standardkollatierung, dann können Sie die Leistung des Abgleichs von UniqueToken -Werten durch Verwendung eines binären Vergleichs verbessern . Nicht-binäre Vergleiche müssen für jeden Wert einen Sortierschlüssel erstellen. Dieser Sortierschlüssel basiert auf sprachlichen Regeln für eine bestimmte Kultur (dh Latin1_General, French, Hebrew, Syriac usw.). Das ist eine Menge zusätzlicher Verarbeitung, wenn die Werte einfach genau gleich sein müssen. Gehen Sie also wie folgt vor:

    1. Löschen Sie den nicht gruppierten Index auf UniqueToken
    2. Ändern Sie die Spalte UniqueToken in VARCHAR(100) NOT NULL COLLATE Latin1_General_100_BIN2 (genau wie in der oben gezeigten temporären Tabelle).
    3. Erstellen Sie den nicht gruppierten Index für UniqueToken neu
1
Solomon Rutzky