Indexsuche mit ODER-Bedingung im Vergleich zu separaten SELECTs viel langsamer

8

Basierend auf diesen Fragen und den gegebenen Antworten:

SQL 2008 Server - Leistungsverlust, möglicherweise verbunden mit einer sehr großen Tabelle

Eine große Tabelle mit historischen Daten weist zu viel SQL Server 2008 Std. Zu. Speicher - Leistungsverlust für andere Datenbanken

Ich habe eine Tabelle in einer Datenbank SupervisionP wie folgt definiert:

CREATE TABLE [dbo].[PenData](
    [IDUkazatel] [smallint] NOT NULL,
    [Cas] [datetime2](0) NOT NULL,
    [Hodnota] [real] NULL,
    [HodnotaMax] [real] NULL,
    [HodnotaMin] [real] NULL,
 CONSTRAINT [PK_Data] PRIMARY KEY CLUSTERED 
(
    [IDUkazatel] ASC,
    [Cas] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

ALTER TABLE [dbo].[PenData]  WITH NOCHECK ADD  CONSTRAINT [FK_Data_Ukazatel] FOREIGN KEY([IDUkazatel])
REFERENCES [dbo].[Ukazatel] ([IDUkazatel])

ALTER TABLE [dbo].[PenData] CHECK CONSTRAINT [FK_Data_Ukazatel]

Es enthält ca. 211 Millionen Reihen.

Ich führe folgende Aussage aus:

DECLARE @t1 DATETIME;
DECLARE @t2 DATETIME;

SET @t1 = GETDATE();
SELECT min(cas) from PenData p WHERE IDUkazatel=24
SELECT min(cas) from PenData p WHERE IDUkazatel=25
SET @t2 = GETDATE();
SELECT DATEDIFF(millisecond,@t1,@t2) AS elapsed_ms;


SET @t1 = GETDATE();
SELECT min(cas) from PenData p WHERE IDUkazatel=24 OR IDUkazatel=25 
SET @t2 = GETDATE();
SELECT DATEDIFF(millisecond,@t1,@t2) AS elapsed_ms;

Das Ergebnis wird hier gezeigt:

Ausführungsplan

Das dritte SELECT lädt auch viel mehr Daten in den SQL Server-Speichercache.

Warum ist der dritte SELECT so viel langsamer (8,5 s) als die ersten beiden SELECTs (16 ms)? Wie kann ich die Leistung der dritten Auswahl mit ODER verbessern? Ich möchte den folgenden SQL-Befehl ausführen, aber es scheint mir, dass das Erstellen eines Cursors und das Ausführen separater Abfragen in diesem Fall viel schneller ist als eine einzelne Auswahl.

 SELECT MIN(cas) from PenData p WHERE IDUkazatel IN (SELECT IDUkazatel FROM  ...)

BEARBEITEN

Wie David vorgeschlagen hat, habe ich über dem fetten Pfeil schweben lassen:

FatArrow

Vojtěch Dohnal
quelle

Antworten:

11

Für die ersten beiden Abfragen muss lediglich der Clustered-Index nach dem ersten Eintrag für diesen Wert von IDUkazateldurchsucht werden. Aufgrund der Reihenfolge des Index ist diese Zeile der niedrigste Wert für cas für diesen Wert von IDUkazatel.

In der zweiten Abfrage ist diese Optimierung kein Wert und es wird wahrscheinlich versucht, in der ersten Zeile IDUkazatel=24den Index bis zur letzten Zeile abzusuchen IDUkazatel=25, um den Mindestwert für casalle diese Zeilen zu ermitteln.

Wenn Sie mit der Maus über diesen fetten Pfeil fahren, werden Sie sehen, dass er viele Zeilen liest (sicherlich alle für 24, wahrscheinlich auch alle für 25), während die dünnen Pfeile in der Planausgabe für die anderen beiden die topAktion anzeigen, die ihn nur verursacht Betrachten Sie eine Zeile.

Sie können versuchen, jede Abfrage auszuführen und dann das Minimum für die gefundenen Minima zu erhalten:

SELECT MIN(cas)
FROM   (
        SELECT cas=MIN(cas) FROM PenData p WHERE p.IDUkazatel = 24
        UNION ALL
        SELECT cas=MIN(cas) FROM PenData p WHERE p.IDUkazatel = 25
    ) AS minimums

Es scheint jedoch, dass Sie eine Tabelle mit IDUkazatelWerten anstelle einer expliziten ORKlausel haben. Der folgende Code funktioniert mit dieser Anordnung. Ersetzen Sie einfach den Tabellennamen @Tdurch den Namen der Tabelle, die IDUkazatelWerte enthält:

SELECT 
    MinCas = MIN(CA.PartialMinimum)
FROM @T AS T
CROSS APPLY 
(
    SELECT 
        PartialMinimum = MIN(PD.Cas)
    FROM dbo.PenData AS PD
    WHERE 
        PD.IDUkazatel = T.IDUkazatel
) AS CA;

In einer idealen Welt würde das SQL Server-Abfrageoptimierungsprogramm dieses Umschreiben für Sie durchführen, berücksichtigt diese Option jedoch heute nicht immer.

David Spillett
quelle
Sie können die letzte ohne abgeleitete Tabelle umschreiben SELECT TOP (1) min_cas=MIN(CAS) ... ORDER BY min_cas;(aber ich denke, der Plan wird der gleiche sein wie Ihr.)
ypercubeᵀᴹ