Ordre de tri spécifié dans la clé primaire, mais le tri est exécuté sur SELECT

15

Je stocke des données de capteur dans une table SensorValues . La table et la clé primaire sont les suivantes:

CREATE TABLE [dbo].[SensorValues](
  [DeviceId] [int] NOT NULL,
  [SensorId] [int] NOT NULL,
  [SensorValue] [int] NOT NULL,
  [Date] [int] NOT NULL,
CONSTRAINT [PK_SensorValues] PRIMARY KEY CLUSTERED 
(
  [DeviceId] ASC,
  [SensorId] ASC,
  [Date] DESC
) WITH (
    FILLFACTOR=75,
    DATA_COMPRESSION = PAGE,
    PAD_INDEX = OFF,
    STATISTICS_NORECOMPUTE = OFF,
    SORT_IN_TEMPDB = OFF,
    IGNORE_DUP_KEY = OFF,
    ONLINE = OFF,
    ALLOW_ROW_LOCKS = ON,
    ALLOW_PAGE_LOCKS = ON)
  ON [MyPartitioningScheme]([Date])

Pourtant, lorsque je sélectionne la valeur du capteur valide pour une période spécifique, le plan d'exécution me dit qu'il fait un tri. Pourquoi donc?

J'aurais pensé que puisque je stocke les valeurs triées par la colonne Date, le tri ne se produirait pas. Ou est-ce parce que l'index n'est pas uniquement trié par la colonne Date, c'est-à-dire qu'il ne peut pas supposer que le jeu de résultats est trié?

SELECT TOP 1 SensorValue
  FROM SensorValues
  WHERE SensorId = 53
    AND DeviceId = 3819
    AND Date < 1339225010
  ORDER BY Date DESC

Plan d'exécution

Edit: Puis-je faire cela à la place?

Étant donné que la table est triée DeviceId, SensorId, Date et que je fais un SELECT spécifiant un seul DeviceId et un SensorId , l'ensemble de sortie doit déjà être trié par Date DESC . Je me demande donc si la question suivante donnerait le même résultat dans tous les cas?

SELECT TOP 1 SensorValue
  FROM SensorValues
  WHERE SensorId = 53
    AND DeviceId = 3819
    AND Date < 1339225010

Selon @Catcall ci-dessous, l'ordre de tri n'est pas le même que l'ordre de stockage. C'est-à-dire que nous ne pouvons pas supposer que les valeurs retournées sont déjà dans un ordre trié.

Edit: j'ai essayé cette solution CROSS APPLY, pas de chance

@Martin Smith m'a suggéré d'essayer D'APPLIQUER MON RÉSULTAT contre les partitions. J'ai trouvé un article de blog ( index non alignés alignés sur une table partitionnée ) décrivant ce problème similaire et j'ai essayé la solution quelque peu similaire à ce que Smith a suggéré. Cependant, pas de chance ici, le temps d'exécution est à égalité avec ma solution d'origine.

WITH Boundaries(boundary_id)
AS
(
  SELECT boundary_id
  FROM sys.partition_functions pf
  JOIN sys.partition_range_values prf ON pf.function_id = prf.function_id
  WHERE pf.name = 'PF'
  AND prf.value <= 1339225010
  UNION ALL
  SELECT max(boundary_id) + 1
  FROM sys.partition_functions pf
  JOIN sys.partition_range_values prf ON pf.function_id = prf.function_id
  WHERE pf.name = 'PF'
  AND prf.value <= 1339225010
),
Top1(SensorValue)
AS
(
  SELECT TOP 1 d.SensorValue
  FROM Boundaries b
  CROSS APPLY
  (
    SELECT TOP 1 SensorValue
      FROM SensorValues
      WHERE  SensorId = 53
        AND DeviceId = 3819
        AND "Date" < 1339225010
        AND $Partition.PF(Date) = b.boundary_id
        ORDER BY Date DESC
  ) d
  ORDER BY d.Date DESC
)
SELECT SensorValue
FROM Top1
m__
la source
L'OPTION MAXDOP 1 n'aide pas. Comme spécifié par @Martin Smith ci-dessous, il semble que le partitionnement en soit la cause ...
m__

Réponses:

13

Pour une table non partitionnée, j'obtiens le plan suivant

Plan 1

Il existe un seul prédicat de recherche Seek Keys[1]: Prefix: DeviceId, SensorId = (3819, 53), Start: Date < 1339225010.

Cela signifie que SQL Server peut effectuer une recherche d'égalité sur les deux premières colonnes, puis commencer une recherche de plage à partir de 1339225010et ordonnée FORWARD(comme l'index est défini avec [Date] DESC)

L' TOPopérateur arrêtera de demander plus de lignes à la recherche après l'émission de la première ligne.

Quand je crée le schéma de partition et la fonction

CREATE PARTITION FUNCTION PF (int)
AS RANGE LEFT FOR VALUES (1000, 1339225009 ,1339225010 , 1339225011);
GO
CREATE PARTITION SCHEME [MyPartitioningScheme]
AS PARTITION PF
ALL TO ([PRIMARY] );

Et remplissez le tableau avec les données suivantes

INSERT INTO [dbo].[SensorValues]    
/*500 rows matching date and SensorId, DeviceId predicate*/
SELECT TOP (500) 3819,53,1, ROW_NUMBER() OVER (ORDER BY (SELECT 0))           
FROM master..spt_values
UNION ALL
/*700 rows matching date but not SensorId, DeviceId predicate*/
SELECT TOP (700) 3819,52,1, ROW_NUMBER() OVER (ORDER BY (SELECT 0))           
FROM master..spt_values
UNION ALL 
/*1100 rows matching SensorId, DeviceId predicate but not date */
SELECT TOP (1100) 3819,53,1, ROW_NUMBER() OVER (ORDER BY (SELECT 0)) + 1339225011      
FROM master..spt_values

Le plan sur SQL Server 2008 se présente comme suit.

Plan 2

Le nombre réel de lignes émises par la recherche est 500. Le plan montre rechercher des prédicats

Seek Keys[1]: Start: PtnId1000 <= 2, End: PtnId1000 >= 1, 
Seek Keys[2]: Prefix: DeviceId, SensorId = (3819, 53), Start: Date < 1339225010

Indiquant qu'il utilise l' approche de saut de balayage décrite ici

l'optimiseur de requête est étendu de sorte qu'une opération de recherche ou d'analyse avec une condition puisse être effectuée sur PartitionID (en tant que colonne de tête logique) et éventuellement d'autres colonnes de clé d'index, puis une recherche de deuxième niveau, avec une condition différente, peut être effectuée sur une ou plusieurs colonnes supplémentaires, pour chaque valeur distincte répondant à la qualification de l'opération de recherche de premier niveau.

Ce plan est un plan série et donc pour la requête spécifique que vous avez, il semble que si SQL Server s'est assuré qu'il a traité les partitions dans l'ordre décroissant, datele plan d'origine avec le TOPfonctionnerait toujours et pourrait arrêter le traitement après la première ligne correspondante. trouvé plutôt que de continuer et de produire les 499 matches restants.

En fait, le plan pour 2005 semble adopter cette approche

Prévoir 2005

Je ne sais pas s'il est simple d'obtenir le même plan pour 2008 ou peut-être aurait-il besoin d'un OUTER APPLYon sys.partition_range_valuespour le simuler.

Martin Smith
la source
9

Beaucoup de gens pensent qu'un index clusterisé garantit un ordre de tri sur la sortie. Mais ce n'est pas ce qu'il fait; il garantit un ordre de stockage sur disque.

Voir, par exemple, cet article de blog et cette discussion plus longue .

Mike Sherrill 'Cat Recall'
la source
1
Eh bien, plus tôt, le PO a également déclaré: "J'aurais pensé que, puisque je stocke les valeurs triées par la colonne Date, le tri ne se produirait pas [sic]." Donc, au moins une partie du problème est cette idée fausse sur ce que fait un index clusterisé. Je pense qu'il est bon de redresser la situation de toute façon.
Mike Sherrill 'Cat Recall'
Peut-être que je suis juste têtu (alors veuillez me pardonner ;-)). Quoi qu'il en soit, j'ai lu le blog d'Hugo Kornelis et c'est assez simple. Cependant, dans son exemple, il utilise un index cluster et un non cluster, l'index non cluster est de plus petite taille et est ainsi utilisé dans le plan d'exécution. Dans mon cas, je n'ai qu'un seul index clusterisé, le serveur SQL peut-il toujours renvoyer les valeurs dans le mauvais ordre (il n'a pas d'index plus petit à utiliser et les analyses de table complètes sont beaucoup trop lentes)?
m__
Je l'ai déplacé vers une nouvelle question (hors sujet)
m__
5

Je suppose que le SORT est nécessaire en raison du plan parallèle. Je base cela sur un article de blog sombre et distant: mais je l'ai trouvé sur MSDN qui peut ou non justifier cela

Alors, essayez avec MAXDOP 1 et voyez ce qui se passe ...

J'ai également fait allusion dans le blog de @sql kiwi sur Simple Talk sous "Exchange Operator" je pense. Et "dépendance DOP" ici

gbn
la source
Même si je n'avais pas pris la peine de configurer une fonction de partition dateavant. Maintenant, j'ai et semble être le partitionnement est le coupable avec 2005 se comportant peut-être mieux pour cette requête particulière.
Martin Smith
1

Fondamentalement, vous avez raison - puisque la clé primaire est dans l'ordre "DeviceId, SensorId, Date", les données de la clé ne sont pas triées par date, elles ne peuvent donc pas être utilisées. Si votre clé était dans un ordre différent "Date, DeviceId, SensorId", les données de la clé seraient triées par date, donc pourraient être utilisées ...


la source
J'avais déjà essayé de changer la clé comme vous l'avez mentionné, alors ne vous en faites pas. Quoi qu'il en soit, va essayer de créer l'index non clusterisé sur les 3 colonnes et voir ce que cela me donne. (la quête de l'index manquant continue ... ;-))
m__