2009-07-06 9 views
7

In SQL Server (2005+) muss ich eine Spalte indexieren (nur exakte Übereinstimmungen), die nvarchar(2000+) ist. Was ist der skalierbarste und performanteste Weg, um das zu erreichen?SQL Server-Indexleistung - lange Spalte

In SQL Server (2005+), was den praktischen Unterschied in der Indizierung auf einer Säule mit den folgenden Typen sein würde:

  • nvarchar(2000)
  • char(40)
  • binary(16)

Z.B wäre ein Lookup gegen eine indizierte binary(16) Spalte messbar schneller als eine Suche nach einem indexierten nvarchar(2000)? Wenn ja, wie viel?

Offensichtlich ist kleiner in jeder Hinsicht immer besser, aber ich bin nicht vertraut genug, wie SQL Server seine Indizes optimiert, um zu wissen, wie es mit der Länge umgeht.

+0

tun müssen, um Sie suchen oder Eindeutigkeit zu erzwingen? –

+0

@Alex Ich muss Eindeutigkeit erzwingen, aber werde nur genaue Übereinstimmungen machen. –

+0

Ich würde Trigger verwenden. –

Antwort

6

Sie über diese aus der falschen Richtung zu denken:

  • Erstellen Indizes Sie benötigen Leistungsziele erfüllen
  • keine Indizes erstellen Sie müssen nicht

Ob ein Spalte ist ein binary(16) oder nvarchar(2000) macht da wenig Unterschied, weil Sie nicht einfach Indizes hinzufügen willy nilly.

Lassen Sie sich von der Indexwahl nicht Ihre Spaltentypen vorgeben. Wenn Sie eine nvarchar(2000) indexieren müssen, betrachten Sie einen Volltextindex oder fügen Sie einen Hashwert für die Spalte hinzu und indexieren Sie diesen.


Basierend auf Ihrem Update, würde ich wahrscheinlich entweder eine Prüfsumme Spalte oder eine berechnete Spalte mit der HashBytes() Funktion und Index, erstellen. Beachten Sie, dass eine Prüfsumme nicht mit einem kryptografischen Hashwert identisch ist und Sie daher eher Kollisionen haben. Sie können jedoch auch den gesamten Inhalt des Textes abgleichen und es wird zuerst mit dem Index gefiltert. HashBytes() ist weniger anfällig für Kollisionen, aber es ist immer noch möglich und Sie müssen die tatsächliche Spalte noch vergleichen. HashBytes ist auch teurer, den Hash für jede Abfrage und jede Änderung zu berechnen.

+0

Eigentlich ist das einer der Gründe, warum ich das frage - wäre ein kurzer binärer Hash eines großen Feldes besser zu indexieren? –

+0

Eine Hash-Spalte kann nur eine exakte Übereinstimmung suchen. Wenn Sie keine Teiltreffer (LIKE 'foo%') oder Bereiche (BETWEEN 'A' UND 'B') benötigen, können Sie Hashes verwenden. –

+1

Okay: Jetzt sehen wir uns eine andere Frage an: "Ich muss eine nvarchar (2000) -Spalte indizieren. Das Ziel ist, diese Art von Abfrage schneller auszuführen: ______. Wie soll ich das machen?" –

6

NATÜRLICH eine binäre (16) wird viel schneller sein - tun nur die schnellste Berechnungen:

  • eine SQL Server-Seite ist immer 8K
  • , wenn Sie 16 Bytes pro Eintrag, Sie können
  • mit 4000 Bytes pro Eintrag (nvarchar) 500 Einträge auf einer Seite speichern, werden Sie mit 2 Einträgen pro Seite (worst case, wenn Ihr nVARCHAR (2000) sind voll bestückt) am Ende

Wenn Sie eine Tabelle mit 100'000 Einträgen haben, müssen Sie 200 Seiten für den Index mit einem binären Schlüssel (16) haben, während Sie 50'000 Seiten für den gleichen Index mit nvarchar (2000) benötigen.

Auch nur die E/A hinzugefügt zu lesen und scannen all diesen Seiten wird jede Leistung töten Sie gehabt haben könnten ........

Marc

UPDATE:
Für Bei meinen üblichen Indizes versuche ich, zusammengesetzte Indizes so gut wie möglich zu vermeiden. Die Referenzierung von anderen Tabellen wird ziemlich unordentlich (WHERE-Klauseln mit mehreren Gleichheitsvergleichen).

Überprüfen und pflegen Sie regelmäßig Ihre Indizes - wenn Sie mehr als 30% Fragmentierung haben, erstellen Sie neu - wenn Sie 5-30% Fragmentierung haben, reorganisieren Sie. Schauen Sie sich einen automatischen, gut getestet DB Index Wartungsskript bei http://sqlfool.com/2009/06/index-defrag-script-v30/

Für die Schlüssel gruppierten auf einer SQL Server-Tabelle, versuchen GUID zu vermeiden, da sie in der Natur zufällig sind und somit verursachen potenziell massive Indexfragmentierung und damit verletzt Performance. Stellen Sie außerdem sicher, dass Ihr gruppierter Schlüssel eindeutig ist. Wenn dies nicht der Fall ist, fügt SQL Server einen Vier-Byte-Unifikator hinzu. Außerdem wird der Clusterschlüssel zu jedem Eintrag in jedem nicht gruppierten Index hinzugefügt. Daher ist es im Clusterschlüssel äußerst wichtig, eine kleine, eindeutige, stabile (sich nicht ändernde) Spalte zu haben (optimalerweise wird sie immer größer) , das gibt Ihnen die besten Eigenschaften und Leistung -> INT IDENTITY ist perfekt).

+0

Was sonst noch außer Reinraumbetrachtungen? Wenn mehrere andere Spalten mit dem Index gespeichert sind, ist der Seitenvergleich also nicht ganz so drastisch. Welche anderen Unterschiede gibt es? –

3

Sie können höchstens 900 Byte pro Indexeintrag haben, sodass Ihr nvarchar (2000) nicht fliegt.Der größte Unterschied ist die Indextiefe - die Anzahl der Seiten, die von der Wurzel bis zur Blattseite durchlaufen werden. Also, wenn Sie suchen müssen, können Sie Index auf CHECKSUM, wie folgt aus:

alter table recipe add text_checksum as checksum(recipe_text) 
create index text_checksum_ind on recipe(text_checksum) 

(zB von hier Indexes on Computed Columns: Speed Up Queries, Add Business Rules) , die Sie nicht eine genaue Übereinstimmung geben, verengen nur um die Suche nach unten sehr gut.

Wenn Sie die Eindeutigkeit erzwingen müssen, müssen Sie natürlich Trigger verwenden.

Eine andere Idee besteht darin, Ihren nvarchar auf einen kleineren binären Wert zu komprimieren und darauf zu indizieren, aber können Sie garantieren, dass jeder Wert immer auf 900 Bytes oder weniger gezippt wird?

+1

+1 ausgezeichneter Punkt, ja - 900 Bytes ist das Maximum für einen Indexeintrag. –

+0

Sie benötigen einen viel größeren Hash als eine 32-Bit-Prüfsumme. CHECKSUM gibt int zurück und es wird im * besten * Fall eine Kollision von 50% Wahrscheinlichkeit nach nur 64k Datensätzen haben, eine sehr, sehr kleine Tabelle. http://rusanu.com/2009/05/29/lockres-collision-probability-magic-marker-16777215/ –

+0

Remus, mit einem größeren Hash haben Sie weniger Chance, falsch positive zu bekommen, aber Sie werden immer noch einige haben. Löst nur in diesem Fall aus. –

2

In index max length is 900 bytes anyway, so dass NVARCHAR (2000) nicht indiziert werden kann.

Ein größerer Indexschlüssel bedeutet, dass weniger Schlüssel in die Indexseiten passen, so dass ein größerer Baum, mehr Datenträger, mehr E/A, mehr Pufferzug, weniger Caching erstellt wird. Bei geclusterten Schlüsseln ist dies viel schlimmer, da der Wert des gruppierten Schlüssels als Suchwert für alle anderen nicht gruppierten Indizes verwendet wird, sodass die Größe der Indizes alle erhöht wird.

Letztendlich ist die am häufigsten verwendete Performance-Metrik in einer Abfrage die Anzahl der gescannten/gesuchten Seiten. Dies führt zu physischen Lesevorgängen (= I/O-Wartezeit) oder logischen Lesevorgängen (= Cache-Verschmutzung).

Abgesehen von den Platzverhältnissen machen Datentypen im Abfrageverhalten wenig bis gar keinen Unterschied. char/varchar/nchar/nvarchar haben Sortierfolgen, die bei Vergleichen berücksichtigt werden müssen, aber die Kosten für die Sortierreihenfolge der Sortierreihenfolge sind normalerweise kein entscheidender Faktor.

Und last but not least, wahrscheinlich der wichtigste Faktor, ist Ihre Anwendung Zugriffsmuster. Indexieren Sie die Spalten, die Abfragen SARGable machen, es ist absolut kein Vorteil, einen Index zu verwalten, der nicht vom Optimierer verwendet wird.

Und manchmal müssen Sie Concurrency-Probleme berücksichtigen, wie wenn Sie deadlocks caused by distinct update access path to the same record beseitigen müssen.

Update nach Post bearbeiten

Verwenden Sie eine persistente MD5-Hash-Spalte:

create table foo (
    bar nvarchar(2000) not null, 
    [hash] as hashbytes('MD5', bar) persisted not null, 
    constraint pk_hash unique ([hash])); 
go 


insert into foo (bar) values (N'Some text'); 
insert into foo (bar) values (N'Other text'); 
go 

select * from foo 
    where [hash] = hashbytes('MD5', N'Some text'); 
go 

Sie haben mit sehr vorsichtig sein, Ihre sucht, wird der Hash-wild für einen Unterschied in der Eingabe unterscheiden, dh . Wenn Sie Ascii-Parameter statt Unicode eins suchen ...

Sie werden eine decent collision chance haben, wenn Ihr Tisch groß wird.

0

Eigentlich ist es besser, Benchmark und sehen Sie selbst. Beispielsweise vergleicht das folgende Skript eine Indexsuche über eine 4-Byte-Ganzzahl mit einer Suche über ein 50-Byte-Zeichen. Es sind 3 Lesevorgänge für ein int (die Tiefe des B-Baums, der auf einer INT-Spalte aufgebaut ist) vs. 4 Lesevorgänge für ein char (die Tiefe des B-Baums, der auf einer CHAR-Spalte aufgebaut ist).

CREATE TABLE dbo.NarrowKey(n INT NOT NULL PRIMARY KEY, m INT NOT NULL) 
GO 
DECLARE @i INT; 
SET @i = 1; 
INSERT INTO dbo.NarrowKey(n,m) SELECT 1,1; 
WHILE @i<1024000 BEGIN 
    INSERT INTO dbo.NarrowKey(n,m) 
    SELECT n + @i, n + @i FROM dbo.NarrowKey; 
    SET @i = @i * 2; 
END; 
GO 
DROP TABLE dbo.WideKey 
GO 
CREATE TABLE dbo.WideKey(n CHAR(50) NOT NULL PRIMARY KEY, m INT NOT NULL) 
GO 
DECLARE @i INT; 
SET @i = 1; 
INSERT INTO dbo.WideKey(n,m) SELECT '1',1; 
WHILE @i<1024000 BEGIN 
    INSERT INTO dbo.WideKey(n,m) 
    SELECT CAST((m + @i) AS CHAR(50)), n + @i FROM dbo.WideKey; 
    SET @i = @i * 2; 
END; 
GO 
SET STATISTICS IO ON; 
SET STATISTICS TIME ON; 
GO 
SELECT * FROM dbo.NarrowKey WHERE n=123456 
SELECT * FROM dbo.WideKey WHERE n='123456' 

Index sucht 33% langsamer für einen breiteren Schlüssel ist, aber die Tabelle ist 4-mal größer:

EXEC sp_spaceused 'dbo.NarrowKey'; 
-- 32K 
EXEC sp_spaceused 'dbo.WideKey'; 
-- 136K