Zakładam, że masz wypaczone dane, że nie chcesz używać wskazówek zapytań, aby zmusić optymalizator do zrobienia, i że musisz uzyskać dobrą wydajność dla wszystkich możliwych wartości wejściowych @Id
. Możesz uzyskać gwarancję, że plan zapytań wymaga tylko kilku garści logicznych odczytów dla dowolnej możliwej wartości wejściowej, jeśli chcesz utworzyć następującą parę indeksów (lub ich odpowiednik):
CREATE INDEX GetMinSomeTimestamp ON dbo.MyTable (Id, SomeTimestamp) WHERE SomeBit = 1;
CREATE INDEX GetMaxSomeInt ON dbo.MyTable (Id, SomeInt) WHERE SomeBit = 1;
Poniżej znajdują się moje dane testowe. Umieściłem 13 M wierszy w tabeli i sprawiłem, że połowa z nich ma wartość '3A35EA17-CE7E-4637-8319-4C517B6E48CA'
dla Id
kolumny.
DROP TABLE IF EXISTS dbo.MyTable;
CREATE TABLE dbo.MyTable (
Id uniqueidentifier,
SomeTimestamp DATETIME2,
SomeInt INT,
SomeBit BIT,
FILLER VARCHAR(100)
);
INSERT INTO dbo.MyTable WITH (TABLOCK)
SELECT NEWID(), CURRENT_TIMESTAMP, 0, 1, REPLICATE('Z', 100)
FROM master..spt_values t1
CROSS JOIN master..spt_values t2;
INSERT INTO dbo.MyTable WITH (TABLOCK)
SELECT '3A35EA17-CE7E-4637-8319-4C517B6E48CA', CURRENT_TIMESTAMP, 0, 1, REPLICATE('Z', 100)
FROM master..spt_values t1
CROSS JOIN master..spt_values t2;
To zapytanie może początkowo wyglądać trochę dziwnie:
DECLARE @Id UNIQUEIDENTIFIER = '3A35EA17-CE7E-4637-8319-4C517B6E48CA'
SELECT
@Id,
st.SomeTimestamp,
si.SomeInt
FROM (
SELECT TOP (1) SomeInt, Id
FROM dbo.MyTable
WHERE Id = @Id
AND SomeBit = 1
ORDER BY SomeInt DESC
) si
CROSS JOIN (
SELECT TOP (1) SomeTimestamp, Id
FROM dbo.MyTable
WHERE Id = @Id
AND SomeBit = 1
ORDER BY SomeTimestamp ASC
) st;
Zaprojektowano go, aby skorzystać z uporządkowania indeksów i znaleźć wartość minimalną lub maksymalną za pomocą kilku logicznych odczytów. CROSS JOIN
Jest tam, aby uzyskać poprawne wyniki, gdy nie ma żadnych wierszy pasujących do @Id
wartości. Nawet jeśli odfiltruję najpopularniejszą wartość w tabeli (pasującą do 6,5 miliona wierszy), otrzymam tylko 8 logicznych odczytów:
Tabela „MyTable”. Liczba skanów 2, logiczne odczyty 8
Oto plan zapytań:
Oba indeksy szukają 0 lub 1 wierszy. Jest niezwykle wydajny, ale utworzenie dwóch indeksów może być przesadą w Twoim scenariuszu. Zamiast tego możesz rozważyć następujący indeks:
CREATE INDEX CoveringIndex ON dbo.MyTable (Id) INCLUDE (SomeTimestamp, SomeInt) WHERE SomeBit = 1;
Teraz plan zapytania dla pierwotnego zapytania (z opcjonalną MAXDOP 1
wskazówką) wygląda nieco inaczej:
Kluczowe wyszukiwania nie są już konieczne. Dzięki lepszej ścieżce dostępu, która powinna działać dobrze dla wszystkich danych wejściowych, nie musisz się martwić, że optymalizator wybierze niewłaściwy plan zapytań ze względu na wektor gęstości. Jednak to zapytanie i indeks nie będą tak wydajne jak inne, jeśli szukasz popularnej @Id
wartości.
Tabela „MyTable”. Liczba skanów 1, logiczne odczyty 33757