Порядок сортировки указан в первичном ключе, но сортировка выполняется в SELECT

15

Я храню данные датчика в таблице SensorValues . Таблица и первичный ключ выглядит следующим образом:

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

Тем не менее, когда я выбираю значение датчика, действительное в течение определенного времени, план выполнения говорит мне, что он выполняет сортировку. Это почему?

Я бы подумал, что, поскольку я храню значения, отсортированные по столбцу Date, сортировка не произойдет. Или потому, что индекс отсортирован не только по столбцу «Дата», т. Е. Он не может предполагать, что результирующий набор отсортирован?

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

План выполнения

Редактировать: Могу ли я сделать это вместо этого?

Поскольку таблица сортируется DeviceId, SensorId, дата и я делаю SELECT , указав только один DeviceId и один SensorId , набор вывода уже должны быть отсортированы по дате DESC . Поэтому мне интересно, даст ли следующий вопрос одинаковый результат во всех случаях?

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

Согласно @Catcall ниже, порядок сортировки не совпадает с порядком хранения. Т.е. мы не можем предположить, что возвращаемые значения уже в отсортированном порядке.

Изменить: я попробовал это решение CROSS APPLY, не повезло

@Martin Smith предложил мне попробовать OUTER APPLY применить мой результат к разделам. Я нашел сообщение в блоге ( выровненные некластеризованные индексы на многораздельной таблице ), описывающее подобную проблему, и попытался найти решение, несколько похожее на предложенное Смитом. Однако, не повезло, время выполнения соответствует моему первоначальному решению.

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__
источник
ОПЦИЯ MAXDOP 1 не помогает. Как указано @Martin Smith ниже, кажется, что причиной является разделение ...
m__

Ответы:

13

Для таблицы без разделов я получаю следующий план

План 1

Существует один предикат поиска Seek Keys[1]: Prefix: DeviceId, SensorId = (3819, 53), Start: Date < 1339225010.

Это означает, что SQL Server может выполнить поиск на равенство в первых двух столбцах, а затем начать поиск диапазона, начиная с 1339225010и упорядочивая FORWARD(как определяется индекс [Date] DESC)

TOPОператор прекратит больше строк запроса от искать после того, как первый ряд испускается.

Когда я создаю схему и функцию разбиения

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

И заполните таблицу следующими данными

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

План на SQL Server 2008 выглядит следующим образом.

План 2

Фактическое количество строк, выбрасываемых из поиска, равно 500. План показывает предикаты поиска

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

Указывая, что используется подход пропуска сканирования, описанный здесь

оптимизатор запросов расширен, так что операция поиска или сканирования с одним условием может быть выполнена для PartitionID (в качестве логического ведущего столбца) и, возможно, других столбцов индекса ключа, а затем может быть выполнен поиск второго уровня с другим условием в одном или нескольких дополнительных столбцах для каждого отдельного значения, которое соответствует квалификации для операции поиска первого уровня.

Этот план представляет собой последовательный план, и поэтому для конкретного имеющегося у вас запроса кажется, что, если SQL Server обеспечит обработку разделов в порядке убывания date, исходный план с этим TOPвсе равно будет работать, и он мог бы прекратить обработку после того, как первая соответствующая строка была найдено вместо продолжения и выдачи оставшихся 499 совпадений.

На самом деле план на 2005 год выглядит так, как будто он использует такой подход

План на 2005 год

Я не уверен , если это прямо вперед , чтобы получить тот же план на 2008 или , может быть , понадобилась бы OUTER APPLYна sys.partition_range_valuesимитировать его.

Мартин Смит
источник
9

Многие люди считают, что кластерный индекс гарантирует порядок сортировки на выходе. Но это не то, что он делает; это гарантирует порядок хранения на диске.

Смотрите, например, этот пост в блоге и это более продолжительное обсуждение .

Майк Шеррилл 'Cat Recall'
источник
1
Ну, ранее ОП также сказал: «Я бы подумал, что, поскольку я храню значения, отсортированные по столбцу« Дата », сортировка не произойдет [sic]». Таким образом, по крайней мере, часть проблемы заключается в том, что неправильное представление о том, что делает кластерный индекс. Я думаю, это хорошо, чтобы все исправить.
Майк Шеррилл 'Cat Recall'
Может быть, я просто упрямый (так что, пожалуйста, прости меня ;-)). Во всяком случае, я прочитал пост в блоге Хьюго Корнелиса, и это довольно просто. Однако в его примере он использует один кластеризованный индекс и один некластеризованный, некластеризованный индекс меньше по размеру и, таким образом, используется в плане выполнения. В моем случае у меня есть только один кластеризованный индекс, может ли сервер sql по-прежнему возвращать значения в неправильном порядке (у него нет меньшего индекса для использования, а полное сканирование таблицы слишком медленное)?
m__
Я перенес это на новый вопрос (не по теме)
m__
5

Я предполагаю, что СОРТ необходим из-за параллельного плана. Я основываю это на какой-то смутной и далекой статье в блоге: но я нашел это на MSDN, что может или не может оправдать это

Итак, попробуйте с MAXDOP 1 и посмотрите, что произойдет ...

Также намекает на сообщение в блоге @sql kiwi о Simple Talk в разделе «Оператор обмена». И "DOP ​​зависимость" здесь

ГБН
источник
Хотя раньше я не удосужился настроить функцию разделения date. Теперь у меня есть и, кажется, разделение является виновником 2005 года, возможно, ведет себя лучше для этого конкретного запроса.
Мартин Смит
1

В принципе вы правы - поскольку первичный ключ имеет порядок «DeviceId, SensorId, Date», данные в ключе не отсортированы по дате, поэтому не могут быть использованы. Если бы ваш ключ был в другом порядке «Дата, Идентификатор устройства, Сенсорный идентификатор», то данные в ключе будут отсортированы по дате, поэтому их можно использовать ...


источник
Я уже пытался сменить ключ так, как ты упомянул, так что не жалей. В любом случае, попробую создать некластеризованный индекс по всем 3 столбцам и посмотрим, что это мне даст. (поиск отсутствующего индекса продолжается ... ;-))
m__