Я собираюсь предположить, что у вас есть искаженные данные, что вы не хотите использовать подсказки запросов, чтобы заставить оптимизатор что делать, и что вам нужно получить хорошую производительность для всех возможных входных значений @Id
. Вы можете получить план запроса, который гарантированно потребует всего несколько горстей логических чтений для любого возможного входного значения, если вы захотите создать следующую пару индексов (или их эквивалент):
CREATE INDEX GetMinSomeTimestamp ON dbo.MyTable (Id, SomeTimestamp) WHERE SomeBit = 1;
CREATE INDEX GetMaxSomeInt ON dbo.MyTable (Id, SomeInt) WHERE SomeBit = 1;
Ниже приведены мои тестовые данные. Я поместил 13 M строк в таблицу, и половина из них имеет значение '3A35EA17-CE7E-4637-8319-4C517B6E48CA'
для Id
столбца.
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;
Этот запрос на первый взгляд может показаться немного странным:
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;
Он разработан для того, чтобы воспользоваться порядком индексов, чтобы найти минимальное или максимальное значение с помощью нескольких логических операций чтения. Он CROSS JOIN
предназначен для получения правильных результатов, когда нет соответствующих строк для @Id
значения. Даже если я отфильтрую самое популярное значение в таблице (соответствует 6,5 миллионов строк), я получу только 8 логических чтений:
Таблица «MyTable». Сканирование 2, логическое чтение 8
Вот план запроса:
Оба индекса стремятся найти 0 или 1 строку. Это чрезвычайно эффективно, но создание двух индексов может оказаться излишним для вашего сценария. Вместо этого вы можете рассмотреть следующий индекс:
CREATE INDEX CoveringIndex ON dbo.MyTable (Id) INCLUDE (SomeTimestamp, SomeInt) WHERE SomeBit = 1;
Теперь план запроса для исходного запроса (с дополнительной MAXDOP 1
подсказкой) выглядит немного иначе:
Поиск ключей больше не нужен. Благодаря лучшему пути доступа, который должен хорошо работать для всех входных данных, вам не нужно беспокоиться о том, что оптимизатор выберет неправильный план запроса из-за вектора плотности. Однако этот запрос и индекс не будут такими же эффективными, как другие, если вы ищете популярное @Id
значение.
Таблица «MyTable». Сканирование 1, логическое чтение 33757