Langsame Abfrage, wenn 'enthält' und '=' zusammen in der where-Klausel stehen


8

Die folgende Abfrage dauert ca. 10 Sekunden, bis eine Tabelle mit 12.000 Datensätzen abgeschlossen ist

select top (5) *
from "Physician"
where "id" = 1 or contains("lastName", '"a*"')

Aber wenn ich die where-Klausel in entweder ändere

where "id" = 1

oder

where contains("lastName", '"a*"')

Es wird sofort zurückkehren.

Beide Spalten sind indiziert und die Spalte lastName ist auch im Volltext indiziert.

CREATE TABLE Physician
(
   id         int identity    NOT NULL,
   firstName  nvarchar(100)   NOT NULL,
   lastName   nvarchar(100)   NOT NULL
);

ALTER TABLE Physician
  ADD CONSTRAINT Physician_PK
  PRIMARY KEY CLUSTERED (id);

CREATE NONCLUSTERED INDEX Physician_IX2
   ON Physician (firstName ASC);

CREATE NONCLUSTERED INDEX Physician_IX3
   ON Physician (lastName ASC);

CREATE FULLTEXT INDEX
    ON "Physician" ("firstName" LANGUAGE 0x0, "lastName" LANGUAGE 0x0)
    KEY INDEX "Physician_PK"
    ON "the_catalog"
    WITH stoplist = off;

Hier ist der Ausführungsplan

Was könnte das Problem sein?


Ich habe gerade die Tabellendefinition hinzugefügt
Hooman Valibeigi

Antworten:


11

Ihr Ausführungsplan

Wenn wir uns den Abfrageplan ansehen, sehen wir, dass ein Index berührt wird, um zwei Filteroperationen zu bedienen.

Geben Sie hier die Bildbeschreibung ein

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.

Geben Sie hier die Bildbeschreibung ein


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:

Geben Sie hier die Bildbeschreibung ein

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*"');

Geben Sie hier die Bildbeschreibung ein

Das where "id" = 124wird 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:

Geben Sie hier die Bildbeschreibung ein

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.

Geben Sie hier die Bildbeschreibung ein

Geben Sie hier die Bildbeschreibung ein

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 UNIONkommt. 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.

Geben Sie hier die Bildbeschreibung ein

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:

Geben Sie hier die Bildbeschreibung ein

wobei die Volltextfunktion direkt angewendet wird

Geben Sie hier die Bildbeschreibung ein

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*"')  

Geben Sie hier die Bildbeschreibung ein

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:

Geben Sie hier die Bildbeschreibung ein


Könnten Sie möglicherweise die Verwendung der Trace Flags erklären? Es ist ein bisschen unklar aus Ihrem Code, was sie tun
George.Palacios

1
@ George.Palacios Ja, ich habe ein bisschen durcheinander gebracht: ^). Werde mich für das Feedback bedanken!
Randi Vertongen

Keines Ihrer vorgeschlagenen QUERYTRACEON-Flags (4138, 3604, 8607, 8612) hat funktioniert, aber QUERYTRACEON 4199 behebt das Problem !!!!
Hooman Valibeigi

Beachten Sie, dass die Abfrage auch ohne den TOP-Operator langsam ist
Hooman Valibeigi

@HoomanValibeigi hast du die Gewerkschaftslösung unten ausprobiert?
Randi Vertongen
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.