Je vais supposer que vous avez des données asymétriques, que vous ne voulez pas utiliser d'indices de requête pour forcer l'optimiseur quoi faire et que vous devez obtenir de bonnes performances pour toutes les valeurs d'entrée possibles de @Id
. Vous pouvez obtenir un plan de requête garanti pour ne nécessiter que quelques poignées de lectures logiques pour toute valeur d'entrée possible si vous êtes prêt à créer la paire d'index suivante (ou leur équivalent):
CREATE INDEX GetMinSomeTimestamp ON dbo.MyTable (Id, SomeTimestamp) WHERE SomeBit = 1;
CREATE INDEX GetMaxSomeInt ON dbo.MyTable (Id, SomeInt) WHERE SomeBit = 1;
Ci-dessous mes données de test. J'ai mis 13 M lignes dans le tableau et fait que la moitié d'entre elles ont une valeur de '3A35EA17-CE7E-4637-8319-4C517B6E48CA'
pour la Id
colonne.
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;
Cette requête peut sembler un peu étrange au premier abord:
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;
Il est conçu pour tirer parti de l'ordre des index pour trouver la valeur min ou max avec quelques lectures logiques. Le CROSS JOIN
est là pour obtenir des résultats corrects lorsqu'il n'y a pas de lignes correspondantes pour la @Id
valeur. Même si je filtre sur la valeur la plus populaire du tableau (correspondant à 6,5 millions de lignes), je n'obtiens que 8 lectures logiques:
Tableau 'MyTable'. Nombre de scans 2, lectures logiques 8
Voici le plan de requête:
Les deux index recherchent trouver 0 ou 1 lignes. C'est extrêmement efficace, mais la création de deux index peut être exagérée pour votre scénario. Vous pouvez plutôt considérer l'index suivant:
CREATE INDEX CoveringIndex ON dbo.MyTable (Id) INCLUDE (SomeTimestamp, SomeInt) WHERE SomeBit = 1;
Maintenant, le plan de requête pour la requête d'origine (avec un MAXDOP 1
indice facultatif ) est un peu différent:
Les recherches clés ne sont plus nécessaires. Avec un meilleur chemin d'accès qui devrait bien fonctionner pour toutes les entrées, vous ne devriez pas avoir à vous soucier que l'optimiseur choisisse le mauvais plan de requête en raison du vecteur de densité. Cependant, cette requête et cet index ne seront pas aussi efficaces que les autres si vous recherchez une @Id
valeur populaire .
Tableau 'MyTable'. Nombre de scans 1, lectures logiques 33757