Ihr Ausführungsplan
Wenn wir uns den Abfrageplan ansehen, sehen wir, dass ein Index berührt wird, um zwei Filteroperationen zu bedienen.
Ganz einfach ausgedrückt wurde aufgrund des TOP-Operators ein Zeilenziel festgelegt. Weitere Informationen und Voraussetzungen zu Reihenzielen finden Sie hier
Aus derselben Quelle:
Eine Zeilenzielstrategie bedeutet im Allgemeinen, nicht blockierende Navigationsoperationen (z. B. verschachtelte Schleifenverknüpfungen, Indexsuchen und Suchvorgänge) gegenüber blockierenden satzbasierten Operationen wie Sortieren und Hashing zu bevorzugen. Dies kann immer dann nützlich sein, wenn der Client von einem schnellen Start und einem stetigen Zeilenstrom profitieren kann (mit möglicherweise einer längeren Gesamtausführungszeit - siehe Rob Farleys Beitrag oben). Es gibt auch die offensichtlicheren und traditionelleren Verwendungszwecke, z. B. das Präsentieren von Ergebnissen seitenweise.
Die gesamte Tabelle wird mithilfe eines linken Semi-Joins mit einem festgelegten Zeilenziel in den Filtern untersucht, in der Hoffnung, die 5 Zeilen so schnell und effizient wie möglich zurückzugeben.
Dies ist nicht der Fall, was zu vielen Iterationen über die .Fulltextmatch-TVF führt.
Erholung
Basierend auf Ihrem Plan konnte ich Ihr Problem etwas nachbauen:
CREATE TABLE dbo.Person(id int not null,lastname varchar(max));
CREATE UNIQUE INDEX ui_id ON dbo.Person(id)
CREATE FULLTEXT CATALOG ft AS DEFAULT;
CREATE FULLTEXT INDEX ON dbo.Person(lastname)
KEY INDEX ui_id
WITH STOPLIST = SYSTEM;
GO
INSERT INTO dbo.Person(id,lastname)
SELECT top(12000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)),
REPLICATE(CAST('A' as nvarchar(max)),80000)+ CAST(ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) as varchar(10))
FROM master..spt_values spt1
CROSS APPLY master..spt_values spt2;
CREATE CLUSTERED INDEX cx_Id on dbo.Person(id);
Ausführen der Abfrage
SELECT TOP (5) *
FROM dbo.Person
WHERE "id" = 1 OR contains("lastName", '"B*"');
Ergebnisse in einem Abfrageplan, der mit Ihrem vergleichbar ist:
Im obigen Beispiel ist B im Volltextindex nicht vorhanden. Infolgedessen hängt es von den Parametern und Daten ab, wie effizient der Abfrageplan sein kann.
Eine bessere Erklärung hierfür finden Sie in Row Goals, Part 2: Semi Joins von Paul White
... Mit anderen Worten, bei jeder Iteration einer Anwendung können wir aufhören, Eingabe B zu betrachten, sobald die erste Übereinstimmung gefunden wurde, indem wir das Push-Down-Join-Prädikat verwenden. Dies ist genau die Art von Dingen, für die ein Zeilenziel gut ist: einen Teil eines Plans zu generieren, der so optimiert ist, dass die ersten n übereinstimmenden Zeilen schnell zurückgegeben werden (wobei hier n = 1 ist).
Ändern Sie beispielsweise das Prädikat, damit die Ergebnisse viel früher gefunden werden (zu Beginn des Scans).
select top (5) *
from dbo.Person
where "id" = 124
or contains("lastName", '"A*"');
Das where "id" = 124
wird eliminiert, da das Volltextindex-Prädikat bereits 5 Zeilen zurückgibt und das TOP()
Prädikat erfüllt .
Die Ergebnisse zeigen dies ebenfalls
id lastname
1 'AAA...'
2 'AAA...'
3 'AAA...'
4 'AAA...'
5 'AAA...'
Und die TVF-Hinrichtungen:
Neue Zeilen einfügen
INSERT INTO dbo.Person
SELECT 12001, REPLICATE(CAST('B' as nvarchar(max)),80000);
INSERT INTO dbo.Person
SELECT 12002, REPLICATE(CAST('B' as nvarchar(max)),80000);
Ausführen der Abfrage, um diese zuvor eingefügten Zeilen zu finden
SELECT TOP (2) *
from dbo.Person
where "id" = 1
or contains("lastName", '"B*"');
Dies führt wiederum zu zu vielen Iterationen über fast alle Zeilen, um den vorletzten gefundenen Wert zurückzugeben.
id lastname
1 'AAA...'
12001 'BBB...'
Lösung
Beim Entfernen des Zeilenziels mithilfe des Traceflags 4138
SELECT TOP (5) *
FROM dbo.Person
WHERE "id" = 124
OR contains("lastName", '"B*"')
OPTION(QUERYTRACEON 4138 );
Der Optimierer verwendet ein Verknüpfungsmuster, das der Implementierung von a näher UNION
kommt. In unserem Fall ist dies günstig, da die Prädikate auf ihre jeweiligen Clustered-Index-Suchvorgänge verschoben werden und nicht der zeilengesteuerte linke Semi-Verknüpfungsoperator verwendet wird.
Eine andere Möglichkeit, dies zu schreiben, ohne das oben erwähnte Traceflag zu verwenden:
SELECT top (5) *
FROM
(
SELECT *
FROM dbo.Person
WHERE "id" = 1
UNION
SELECT *
FROM dbo.Person
WHERE contains("lastName", '"B*"')
) as A;
Mit dem resultierenden Abfrageplan:
wobei die Volltextfunktion direkt angewendet wird
Als Randnotiz für op löste das Hotfix-Traceflag 4199 des Abfrageoptimierers sein Problem. Er implementierte dies, indem OPTION(QUERYTRACEON(4199))
er der Abfrage hinzufügte . Ich konnte dieses Verhalten an meinem Ende nicht reproduzieren. Dieser Hotfix enthält eine Semi-Join-Optimierung:
Ablaufverfolgungsflag: 4102 Funktion: SQL 9 - Die Abfrageleistung ist langsam, wenn der Ausführungsplan der Abfrage Semi-Join-Operatoren enthält. In der Regel werden Semi-Join-Operatoren generiert, wenn die Abfrage das Schlüsselwort IN oder das Schlüsselwort EXISTS enthält. Aktivieren Sie die Flags 4102 und 4118, um dies zu überwinden.
Quelle
Extra
Während der kostenbasierten Optimierung kann der Optimierer dem Ausführungsplan auch einen Indexspool hinzufügen, der von (oder dem physischen Gegenstück) implementiert wird.LogOp_Spool Index on fly Eager
Dies geschieht mit meinem Datensatz für, TOP(3)
aber nicht fürTOP(2)
SELECT TOP (3) *
from dbo.Physician
where "id" = 1
or contains("lastName", '"B*"')
Bei der ersten Ausführung liest und speichert ein eifriger Spool die gesamte Eingabe, bevor er die Teilmenge der Zeilen zurückgibt, die von den späteren Prädikatausführungen angefordert wird. Lesen Sie dieselbe oder eine andere Teilmenge der Zeilen aus dem Arbeitstisch und geben Sie sie zurück, ohne das untergeordnete Element jemals ausführen zu müssen wieder Knoten.
Quelle
Mit dem Suchprädikat, das auf diese Index-Eiferspule angewendet wird: