web-dev-qa-db-fra.com

Index cherche beaucoup plus lentement avec OR condition comparé à des sélectionneuses séparées

Basé sur ces questions et les réponses données:

SQL 2008 Server - Perte de performance éventuellement connectée avec une très grande table

grande table avec données historiques alloue trop de SQL Server 2008 std. Mémoire - Perte de performance 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 des rangées CCA 211 milion.

Je cours après 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 montré ici:

Execution plan

Le troisième sélection est également chargé beaucoup plus de données dans le cache de mémoire SQL Server.

Pourquoi le troisième sélection est-il tellement plus lent (8,5 s) alors les deux premiers sélectionneurs (16 ms)? Comment puis-je améliorer les performances du troisième sélection avec ou? Je veux courir après la commande SQL, mais il me semble que la création de curseur et de faire fonctionner des requêtes distinctes est beaucoup plus rapide qu'une seule sélection dans ce cas.

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

[~ # ~] Edit [~ # ~ #]

Comme David suggérait que j'ai survolé la flèche de la graisse:

FatArrow

8
Vojtěch Dohnal

Pour les deux premières requêtes, tout ce qu'il faut faire est de numériser dans l'index en cluster sur la première entrée de cette valeur de IDUkazatel - en raison de l'ordre de l'index que la rangée sera la valeur la plus basse pour la valeur de cette valeur de IDUkazatel.

Dans la deuxième requête, cette optimisation n'est pas une valeur et elle cherche probablement à la première rangée pour IDUkazatel=24 puis scanner l'index jusqu'à la dernière ligne avec IDUkazatel=25 Pour trouver la valeur minimale de cas sur toutes ces lignes.

Si vous survolez la flèche de la graisse, vous verrez qu'il lise de nombreuses lignes (certainement toutes celles de 24, probablement toutes celles de 25), tandis que les flèches minces dans la sortie du plan pour les deux autres montrent le top Action le faisant prendre en compte seulement une ligne.

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 IDUkazatel valeurs plutôt qu'une clause explicite OR. Le code ci-dessous fonctionnera avec cet arrangement, remplacez simplement le nom de la table @T avec le nom de la table contenant IDUkazatel valeurs:

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 SQL Server Query effectuerait cette réécriture pour vous, mais elle ne considère pas toujours cette option aujourd'hui.

11
David Spillett