SQL Select benötigt zu viel Zeit für die Ausführung

9

Es ist eine einfache Auswahl aus einer temporären Tabelle, bei der eine vorhandene Tabelle auf ihrem Primärschlüssel verknüpft wird, wobei zwei Unterauswahlen unter Verwendung von Top 1 auf die verknüpfte Tabelle verweisen.

In Code:

SELECT
    TempTable.Col1,
    TempTable.Col2,
    TempTable.Col3,
    JoinedTable.Col1,
    JoinedTable.Col2,
    (
        SELECT TOP 1
            ThirdTable.Col1 -- Which is ThirdTable's Primary Key
        FROM
            ThirdTable
        WHERE
            ThirdTable.SomeColumn = JoinedTable.SomeColumn
    ) as ThirdTableColumn1,
    (
        SELECT TOP 1
            ThirdTable.Col1 -- Which is also ThirdTable's Primary Key
        FROM
            ThirdTable
        WHERE
            ThirdTable.SomeOtherColumn = JoinedTable.SomeColumn
    ) as ThirdTableColumn2,
FROM
    #TempTable as TempTable
LEFT JOIN
    JoinedTable
ON (TempTable.PKColumn1 = JoinedTable.PKColumn1 AND 
    TempTable.PKColumn2 = JoinedTable.PKColumn2)
WHERE
    JoinedTable.WhereColumn IN  (1, 3)

Dies ist eine exakte Nachbildung meiner Anfrage.

Wenn ich die beiden Unterauswahlen entferne, läuft es gut und schnell. Mit den beiden Unterauswahlen erhalte ich ungefähr 100 Datensätze pro Sekunde, was für diese Abfrage extrem langsam ist, da fast eine Million Datensätze zurückgegeben werden sollten.

Ich habe überprüft, ob jede Tabelle einen Primärschlüssel hat, das tun sie alle. Sie alle haben Indizes UND Statistiken für ihre wichtigen Spalten, wie die in den WHERE-Klauseln und die in der JOIN-Klausel. Die einzige Tabelle, in der weder ein Primärschlüssel noch ein Index definiert ist, ist die temporäre Tabelle, aber es ist auch nicht das Problem, da es sich nicht um die Tabelle handelt, die sich auf die langsamen Unterauswahlen bezieht, und wie ich bereits erwähnt habe, läuft sie ohne Unterauswahlen einwandfrei.

Ohne TOP 1diese gibt es mehr als ein Ergebnis zurück und löst einen Fehler aus.

Hilfe, jemand?

EDIT :

Der Ausführungsplan sagte mir also, dass mir ein Index fehlte. Ich habe es erstellt und einige der anderen Indizes neu erstellt. Nach einer Weile wurden sie vom Ausführungsplan verwendet, und die Abfrage wird jetzt schnell ausgeführt. Das einzige Problem ist, dass es mir nicht gelingt, dies auf einem anderen Server für dieselbe Abfrage erneut durchzuführen. Meine Lösung lautet also TIPP, welchen Index SQL Server verwenden wird.

Smur
quelle
Wow, das ist beeindruckend. Aber können Sie dies stattdessen in mehrere separate Anweisungen aufteilen? Wie wäre es stattdessen mit gespeicherten Prozeduren?
2
@Adel Diese Auswahl ist in der Tat eine Unterauswahl innerhalb einer gespeicherten Prozedur. Das Ganze ist eigentlich ziemlich groß, aber ich bin mir zu 100% sicher, dass genau dieser Teil Zeit braucht, um ausgeführt zu werden.
Die Änderung des Ausführungsplans einschließlich der automatisch ausgewählten Indizes hat höchstwahrscheinlich mit einer Änderung der Daten zu tun. Ich würde sicherstellen, dass Ihre Indizes vollständig abgedeckt sind, oder die Engine nimmt unerwartete Pfade wie einen Tabellenscan. Ich empfehle, den Ausführungsplan auf dem neuen Server (ohne Hinweise) zu überprüfen, um festzustellen, wo Abweichungen vom ursprünglichen System auftreten.
Robert Miller
Aha. Ich habe nur den Server geändert, die Datenbank ist dieselbe, mit denselben Indizes. Trotzdem scheint es nicht automatisch zu sein, meine Indizes zu verwenden. Es macht genau das, was Sie gesagt haben: einen Tabellenscan.
Smur
Klingt so, als ob der Abfrage-Opitmizer keinen der Tabellenindizes für Ihre Abfrage mag. Hat der Ausführungsplan einen fehlenden Index angezeigt?
Robert Miller

Antworten:

7

Ich denke, bei einer Abfrage von Millionen Datensätzen muss man Dinge wie vermeiden OUTER JOINS. Ich schlage vor, Sie verwenden UNION ALLanstelle von LEFT JOIN. Solange ich denke CROSS APPLY, dass dies effizienter ist als eine Unterabfrage in der select-Klausel, werde ich die von Conard Frix geschriebene Abfrage ändern, was ich für richtig halte.

Jetzt: Als ich anfing, Ihre Abfrage zu ändern, bemerkte ich, dass Sie eine WHERE-Klausel haben, die besagt : JoinedTable.WhereColumn IN (1, 3). In diesem Fall wird die Bedingung falsch, wenn das Feld null ist. Warum verwenden Sie dann LEFT JOIN, während Sie nullwertige Zeilen filtern? ersetzen Sie einfach LEFT JOINmit INNER JOIN, ich garantiere , dass es schneller zu machen.

über INDEX:

Bitte beachten Sie, dass Sie beispielsweise einen Index für eine Tabelle haben

table1(a int, b nvarchar)

und Ihr Index ist:

nonclustered index ix1 on table1(a)

und du willst so etwas machen:

select a,b from table1
where a < 10

Sie haben die Spalte nicht in Ihren Index aufgenommen. bWas passiert also?

Wenn SQL-Server Ihren Index verwendet, wird es in dem Index durchsuchen, die so genannten „Index Seek“ und dann auf Haupttabelle bezieht sich auf Spalte zu erhalten b, die so genannten „Look Up“ . Dieser Vorgang kann viel länger dauern als das Scannen der Tabelle selbst: "Tabellenscan" .

Basierend auf den Statistiken, über die SQL Server verfügt, wird Ihr Index in solchen Situationen möglicherweise überhaupt nicht verwendet.

Überprüfen Sie daher zunächst Execution Plan, ob der Index überhaupt verwendet wird.

Wenn ja oder nein, ändern Sie Ihren Index so, dass er alle von Ihnen ausgewählten Spalten enthält. sag wie:

nonclustered index ix1 on table1(a) include(b)

In diesem Fall wird Look Up nicht benötigt und Ihre Abfrage wird viel schneller ausgeführt.


quelle
1
Ich kann diesen linken Join nicht in Inner Join ändern, es würde die Ergebnisse verfälschen, es ist eine Geschäftsregel: Die zweite Tabelle muss nicht unbedingt einen zugehörigen Datensatz haben. Außerdem akzeptiert die Spalte in der WHERE-Klausel keine Nullwerte.
Smur
6

Es ist die Unterauswahl in Ihrer Spaltenauswahl, die die langsame Rückkehr verursacht. Sie sollten versuchen, Ihre Unterauswahl in linken Verknüpfungen zu verwenden, oder eine abgeleitete Tabelle verwenden, wie ich unten definiert habe.

Verwenden von Linksverknüpfungen zu zwei Instanzen der dritten Tabelle

SELECT
  TempTable.Col1,
  TempTable.Col2,
  TempTable.Col3,
  JoinedTable.Col1,
  JoinedTable.Col2,
  ThirdTable.Col1 AS ThirdTableColumn1,
  ThirdTable2.Col1 AS ThirdTableColumn2
FROM #TempTable as TempTable
LEFT JOIN JoinedTable ON (TempTable.PKColumn1 = JoinedTable.PKColumn2 AND 
    TempTable.PKColumn 2 = JoinedTable.PKColumn2)
LEFT JOIN ThirdTable ON ThirdTable.SomeColumn = JoinedTable.SomeColumn
LEFT JOIN ThirdTable ThirdTable2 ON ThirdTable.SomeOtherColumn = JoinedTable.SomeColumn
WHERE
    JoinedTable.WhereColumn IN  (1, 3)

Verwenden einer abgeleiteten Tabelle

 SELECT 
      TempTable.Col1,
      TempTable.Col2,
      TempTable.Col3,
      DerivedTable.Col1,
      DerivedTable.Col2,
      DerivedTable.ThirdTableColumn1,
      DerivedTable.ThirdTableColumn2
 FROM #TempTable as TempTable
    LEFT JOIN (SELECT
                 JoinedTable.PKColumn2,
                 JoinedTable.Col1,
                 JoinedTable.Col2,
                 JoinedTable.WhereColumn,
                 ThirdTable.Col1 AS ThirdTableColumn1,
                 ThirdTable2.Col1 AS ThirdTableColumn2
               FROM JoinedTable
               LEFT JOIN ThirdTable ON ThirdTable.SomeColumn = JoinedTable.SomeColumn
               LEFT JOIN ThirdTable ThirdTable2 ON ThirdTable.SomeOtherColumn = JoinedTable.SomeColumn) 
        DerivedTable ON (TempTable.PKColumn1 = DerivedTable .PKColumn2 AND 
        TempTable.PKColumn2 = DerivedTable.PKColumn2)
    WHERE
        DerivedTable.WhereColumn IN  (1, 3)
John Hartsock
quelle
2

Versuchen Sie stattdessen ein Kreuz anzuwenden

SELECT
    TempTable.Col1,
    TempTable.Col2,
    TempTable.Col3,
    JoinedTable.Col1,
    JoinedTable.Col2,
    ThirdTableColumn1.col1,
    ThirdTableColumn2.col1

FROM
    #TempTable as TempTable
LEFT JOIN
    JoinedTable
ON (TempTable.PKColumn1 = JoinedTable.PKColumn2 AND 
    TempTable.PKColumn 2 = JoinedTablePKColumn2)

CROSS APPLY
(
        SELECT TOP 1
            ThirdTable.Col1 -- Which is ThirdTable's Primary Key
        FROM
            ThirdTable
        WHERE
            ThirdTable.SomeColumn = JoinedTable.SomeColumn
    ) as ThirdTableColumn1
CROSS APPLY    (
        SELECT TOP 1
            ThirdTable.Col1 -- Which is also ThirdTable's Primary Key
        FROM
            ThirdTable
        WHERE
            ThirdTable.SomeOtherColumn = JoinedTable.SomeColumn
    ) as ThirdTableColumn2,
WHERE
    JoinedTable.WhereColumn IN  (1, 3)

Sie können auch CTEs und row_number oder eine Inline-Abfrage mit MIN verwenden

Conrad Frix
quelle
2

Verschieben Sie die JOIN-Bits aus dem Hauptteil der Klausel und setzen Sie sie als Unterauswahl. Wenn Sie es in den Abschnitt WHERE and JOIN verschieben, müssen Sie nicht immer wieder TOP 1 AUSWÄHLEN, was meiner Meinung nach der Grund für die Langsamkeit ist. Wenn Sie dies überprüfen möchten, überprüfen Sie den Ausführungsplan.


quelle
2

Die ThirdTableReferenzen (Unterauswahl in Ihrem Beispiel) benötigen dieselbe Indexaufmerksamkeit wie jeder andere Teil einer Abfrage.

Unabhängig davon, ob Sie Unterauswahl verwenden:

(
    SELECT TOP 1
        ThirdTable.Col1 -- Which is ThirdTable's Primary Key
    FROM
        ThirdTable
    WHERE
        ThirdTable.SomeColumn = JoinedTable.SomeColumn
) as ThirdTableColumn1,
(
    SELECT TOP 1
        ThirdTable.Col1 -- Which is also ThirdTable's Primary Key
    FROM
        ThirdTable
    WHERE
        ThirdTable.SomeOtherColumn = JoinedTable.SomeColumn
) as ThirdTableColumn2,

LINKE VERBINDUNGEN (wie von John Hartsock vorgeschlagen):

LEFT JOIN ThirdTable ON ThirdTable.SomeColumn = JoinedTable.SomeColumn
LEFT JOIN ThirdTable ThirdTable2 ON ThirdTable.SomeOtherColumn = JoinedTable.SomeColumn

CROSS APPLY (wie von Conrad Frix vorgeschlagen):

CROSS APPLY
(
        SELECT TOP 1
            ThirdTable.Col1 -- Which is ThirdTable's Primary Key
        FROM
            ThirdTable
        WHERE
            ThirdTable.SomeColumn = JoinedTable.SomeColumn
    ) as ThirdTableColumn1
CROSS APPLY    (
        SELECT TOP 1
            ThirdTable.Col1 -- Which is also ThirdTable's Primary Key
        FROM
            ThirdTable
        WHERE
            ThirdTable.SomeOtherColumn = JoinedTable.SomeColumn
    ) as ThirdTableColumn2

Sie müssen sicherstellen covering indexes, dass für ThirdTable.SomeColumnund definiert sind ThirdTable.SomeOtherColumnund die Indizes eindeutig sind. Dies bedeutet, dass Sie die ThirdTableReferenzen weiter qualifizieren müssen , um die Auswahl mehrerer Zeilen zu vermeiden und die Leistung zu verbessern. Die Wahl sub selects, LEFT JOINoder CROSS APPLYnicht wirklich eine Rolle , bis Sie die Selektivität für verbessern ThirdTable.SomeColumnund ThirdTable.SomeOtherColumnum mehr Spalten einschließlich einzigartige Selektivität zu gewährleisten. Bis dahin gehe ich davon aus, dass Ihre Leistung weiterhin darunter leiden wird.

Das covering indexThema wird von Maziar Taheri gut vorgestellt; Obwohl ich seine Arbeit nicht wiederhole, betone ich die Notwendigkeit, die Verwendung von Deckungsindizes zu Herzen zu nehmen.

Kurz gesagt: Verbessern Sie die Selektivität für die ThirdTable.SomeColumnund ThirdTable.SomeOtherColumnAbfragen (oder Verknüpfungen), indem Sie verwandte Tabellenspalten hinzufügen, um eine eindeutige Zeilenübereinstimmung sicherzustellen. Wenn dies nicht möglich ist, treten weiterhin Leistungsprobleme auf, da der Motor damit beschäftigt ist, Reihen einzuziehen, die anschließend weggeworfen werden. Dies wirkt sich auf Ihre E / A, CPU und letztendlich auf den Ausführungsplan aus.

Robert Miller
quelle