Die im Primärschlüssel angegebene Sortierreihenfolge wird jedoch bei SELECT sortiert

15

Ich speichere Sensordaten in einer Tabelle SensorValues . Die Tabelle und der Primärschlüssel lauten wie folgt:

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])

Wenn ich jedoch den Sensorwert auswähle, der für eine bestimmte Zeit gültig ist, teilt mir der Ausführungsplan mit, dass eine Sortierung durchgeführt wird. Warum das?

Ich hätte gedacht, dass die Sortierung nicht stattfinden würde, da ich die nach der Datumsspalte sortierten Werte speichere. Oder liegt es daran, dass der Index nicht nur nach der Datumsspalte sortiert ist, sondern nicht davon ausgehen kann, dass die Ergebnismenge sortiert ist?

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

Ausführungsplan

Edit: Kann ich das stattdessen machen?

Da die Tabelle nach DeviceId, SensorId, Date sortiert ist und ich ein SELECT durchführe , das nur eine DeviceId und eine SensorId angibt , sollte der Ausgabesatz bereits nach Date DESC sortiert sein . Ich frage mich also, ob die folgende Frage in allen Fällen das gleiche Ergebnis liefern würde.

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

Gemäß @Catcall unten stimmt die Sortierreihenfolge nicht mit der Speicherreihenfolge überein. Dh wir können nicht davon ausgehen, dass die zurückgegebenen Werte bereits sortiert sind.

Edit: Ich habe diese CROSS APPLY-Lösung ausprobiert, kein Glück

@ Martin Smith schlug vor, ich würde versuchen, mein Ergebnis auf die Partitionen anzuwenden. Ich fand einen Blogeintrag ( Ausgerichtete nicht gruppierte Indizes für partitionierte Tabellen ), in dem dieses ähnliche Problem beschrieben wurde, und versuchte die etwas ähnliche Lösung für das, was Smith vorgeschlagen hatte. Leider ist die Ausführungszeit mit meiner ursprünglichen Lösung vergleichbar.

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__
quelle
OPTION MAXDOP 1 hilft nicht. Wie von @Martin Smith unten angegeben, scheint die Partitionierung die
Ursache zu sein

Antworten:

13

Für eine nicht partitionierte Tabelle erhalte ich den folgenden Plan

Plan 1

Es gibt ein einzelnes Suchprädikat für Seek Keys[1]: Prefix: DeviceId, SensorId = (3819, 53), Start: Date < 1339225010.

Dies bedeutet, dass SQL Server eine Gleichheitssuche für die ersten beiden Spalten ausführen und dann eine Bereichssuche beginnen kann, die bei 1339225010und nach Reihenfolge beginnt FORWARD(da der Index mit definiert ist [Date] DESC).

Der TOPBediener fordert nach dem Aussenden der ersten Zeile keine weiteren Zeilen mehr von der Suche an.

Wenn ich das Partitionsschema und die Funktion erstelle

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] );

Füllen Sie die Tabelle mit den folgenden Daten

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

Der Plan für SQL Server 2008 sieht folgendermaßen aus.

Plan 2

Die tatsächliche Anzahl der von der Suche ausgegebenen Zeilen beträgt 500. Der Plan zeigt Suchprädikate

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

Um dies anzuzeigen, wird der hier beschriebene Ansatz zum Überspringen des Scans verwendet

Das Abfrageoptimierungsprogramm wurde so erweitert, dass eine Such- oder Scanoperation mit einer Bedingung für PartitionID (als logische führende Spalte) und möglicherweise für andere Indexschlüsselspalten ausgeführt werden kann. Anschließend kann eine Suche der zweiten Ebene mit einer anderen Bedingung ausgeführt werden in einer oder mehreren zusätzlichen Spalten für jeden eindeutigen Wert, der die Qualifikation für die Suchoperation der ersten Ebene erfüllt.

Bei diesem Plan handelt es sich um einen seriellen Plan. Wenn SQL Server also sicherstellt, dass die Partitionen in absteigender Reihenfolge verarbeitet werden date, TOPfunktioniert der ursprüngliche Plan weiterhin und die Verarbeitung wird möglicherweise abgebrochen, nachdem die erste übereinstimmende Zeile vorhanden war gefunden, anstatt die verbleibenden 499 Übereinstimmungen fortzusetzen und auszugeben.

In der Tat sieht der Plan für 2005 so aus

Planen Sie für 2005

Ich bin mir nicht sicher, ob es ohne Weiteres möglich ist, den gleichen Plan für 2008 zu erstellen, oder ob für die Simulation ein OUTER APPLYOn erforderlich wäre sys.partition_range_values.

Martin Smith
quelle
9

Viele Leute glauben, dass ein Clustered-Index eine Sortierreihenfolge bei der Ausgabe garantiert . Aber das ist nicht was es tut; es garantiert eine Speicherreihenfolge auf der Festplatte.

Siehe zum Beispiel diesen Blog-Beitrag und diese längere Diskussion .

Mike Sherrill 'Cat Recall'
quelle
1
Nun, früher sagte das OP auch: "Ich hätte gedacht, dass die Sortierung nicht stattfinden würde, da ich die Werte sortiert nach der Datumsspalte speichere." Zumindest ein Teil des Problems ist das Missverständnis darüber, was ein Clustered-Index tut. Ich finde es trotzdem gut, das zu klären.
Mike Sherrill "Cat Recall"
Vielleicht bin ich nur stur (also verzeih mir bitte ;-)). Jedenfalls habe ich den Blogbeitrag von Hugo Kornelis gelesen und er ist ziemlich einfach. In seinem Beispiel verwendet er jedoch einen gruppierten und einen nicht gruppierten Index. Der nicht gruppierte Index ist kleiner und wird daher im Ausführungsplan verwendet. In meinem Fall habe ich nur einen Clustered-Index. Kann SQL Server die Werte immer noch in falscher Reihenfolge zurückgeben (es ist kein kleinerer Index zu verwenden und vollständige Tabellensuchen sind viel zu langsam)?
m__
Ich habe dies zu einer neuen Frage (außerhalb des Themas)
verschoben
5

Ich spekuliere, dass die SORT wegen des parallelen Plans benötigt wird. Ich stütze mich auf einen trüben und fernen Blog-Artikel, aber ich fand diesen auf MSDN, der dies möglicherweise rechtfertigt oder nicht

Versuchen Sie es mit MAXDOP 1 und sehen Sie, was passiert ...

Auch angedeutet in @sql Kiwis Blog-Post auf Simple Talk unter "Exchange Operator" denke ich. Und "DOP-Abhängigkeit" hier

gbn
quelle
Obwohl ich mich vorher nicht darum gekümmert hatte, eine Partitionsfunktion einzurichten date. Jetzt habe ich und scheint Partitionierung ist der Schuldige mit 2005 möglicherweise besseres Verhalten für diese bestimmte Abfrage.
Martin Smith
1

Grundsätzlich haben Sie Recht - da sich der Primärschlüssel in der Reihenfolge "DeviceId, SensorId, Date" befindet, sind die Daten im Schlüssel nicht nach Datum sortiert und können daher nicht verwendet werden. Wenn Sie Ihren Schlüssel in einer anderen Reihenfolge war „Datum, DeviceId, SensorId“, dann werden die Daten in dem Schlüssel würde nach Datum sortiert werden, so verwendet werden könnte ...


quelle
Ich habe bereits versucht, den Schlüssel so zu ändern, wie Sie es erwähnt haben. Wie auch immer, ich werde versuchen, den nicht gruppierten Index für alle 3 Spalten zu erstellen und zu sehen, was mir das bringt. (die Suche nach dem fehlenden Index geht weiter ... ;-))
m__