L'index recherche beaucoup plus lentement avec la condition OR par rapport aux SELECT séparés

8

Sur la base de ces questions et des réponses données:

SQL 2008 Server - perte de performances éventuellement liée à une très grande table

Un grand tableau avec des données historiques alloue trop de SQL Server 2008 Std. mémoire - perte de performances pour d'autres bases de données

J'ai une table dans une base de données SupervisionP définie comme ceci:

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]

Il contient environ 211 millions de lignes.

Je lance la déclaration suivante:

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;

Le résultat est affiché ici:

Plan d'exécution

Le troisième SELECT charge également beaucoup plus de données dans le cache mémoire de SQL Server.

Pourquoi le troisième SELECT est-il tellement plus lent (8,5 s) que les deux premiers SELECT (16 ms)? Comment puis-je améliorer les performances de la troisième sélection avec OR? Je veux exécuter la commande SQL suivante, mais il me semble que la création d'un curseur et l'exécution de requêtes distinctes sont beaucoup plus rapides qu'une simple sélection dans ce cas.

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

ÉDITER

Comme David l'a suggéré, j'ai survolé la grosse flèche:

FatArrow

Vojtěch Dohnal
la source

Réponses:

11

Pour les deux premières requêtes, tout ce qu'il a à faire est de scanner dans l'index clusterisé la première entrée pour cette valeur de IDUkazatel- en raison de l'ordre de l'index, cette ligne sera la valeur la plus basse pour cas pour cette valeur de IDUkazatel.

Dans la deuxième requête, cette optimisation n'est pas une valeur et elle recherche probablement la première ligne pour IDUkazatel=24ensuite parcourir l'index jusqu'à la dernière ligne avec IDUkazatel=25pour trouver la valeur minimale de castoutes ces lignes.

Si vous survolez cette grosse flèche, vous verrez qu'elle lit de nombreuses lignes (certainement toutes celles pour 24, probablement toutes celles pour 25 aussi), tandis que les flèches fines dans la sortie du plan pour les deux autres montrent l' topaction qui la fait uniquement considérez une rangée.

Vous pouvez essayer d'exécuter chaque requête, puis obtenir le minimum pour les minimums trouvés:

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

Cela dit, il semble que vous ayez une table avec des IDUkazatelvaleurs plutôt qu'une ORclause explicite . Le code ci-dessous fonctionnera avec cet arrangement, remplacez simplement @Tle nom de la table par le nom de la table contenant les IDUkazatelvaleurs:

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;

Dans un monde idéal, l'optimiseur de requêtes SQL Server effectuerait cette réécriture pour vous, mais il ne considère pas toujours cette option aujourd'hui.

David Spillett
la source
Vous pouvez réécrire le dernier sans table dérivée SELECT TOP (1) min_cas=MIN(CAS) ... ORDER BY min_cas;(mais je suppose que le plan sera le même que le vôtre.)
ypercubeᵀᴹ