Порядок сортування, вказаний у первинному ключі, але сортування виконується на 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])

Тим не менш, коли я вибираю значення датчика, дійсне певний час, план виконання говорить мені, що це робить своєрідне. Чому так?

Я б подумав, що оскільки я зберігаю значення, відсортовані за стовпцем Дата, сортування не відбуватиметься. Або це тому, що індекс не сортується виключно за стовпцем Дата, тобто він не може припустити, що набір результатів відсортований?

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

План виконання

Редагувати: Чи можна це зробити замість цього?

Оскільки в таблиці відсортовано DeviceId, SensorId, Date та I SELECT із зазначенням лише одного DeviceId та одного SensorId , набір вихідних даних вже повинен бути відсортований за датою DESC . Тож мені цікаво, чи дасть наступне запитання однаковий результат у всіх випадках?

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

Відповідно до @Catcall нижче, порядок сортування не такий, як порядок зберігання. Тобто ми не можемо припустити, що повернені значення вже впорядкованому порядку.

Редагувати: Я спробував це CROSS APPLY рішення, не пощастило

@Martin Smith запропонував спробувати ВИКОРИСТИТИ свій результат проти розділів. Я знайшов допис у блозі ( Вирівнювання некластеризованих індексів на розділеній таблиці ), що описує цю подібну проблему, і спробував дещо подібне рішення до того, що запропонував Сміт. Однак, не пощастило, час виконання дорівнює моєму оригінальному рішенню.

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

OPTION MAXDOP 1 не допомагає. Як зазначено нижче @Martin Smith, здається, що розділення - це те, що викликає це ...
m__

Відповіді:


13

Для нерозділеної таблиці я отримую наступний план

План 1

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

Це означає, що SQL Server може виконувати пошук рівності на перших двох стовпцях, а потім розпочати пошук діапазону, починаючи з 1339225010і впорядкованого FORWARD(як індекс визначено з [Date] DESC)

The 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

Багато людей вважають, що кластерний індекс гарантує своєрідний порядок випуску. Але це не те, що робить; це гарантує замовлення на зберігання на диску.

Дивіться, наприклад, цю публікацію в блозі та цю більш тривалу дискусію .


1
Ну, раніше, також ОП сказав: "Я би подумав, що оскільки я зберігаю значення, відсортовані за стовпцем" Дата ", сортування не відбуватиметься [sic]". Так, принаймні, частина проблеми полягає в тому, що помилкове уявлення про те, що робить кластерний індекс. Я думаю, що добре все-таки виправити це.
Майк Шеррілл 'Відкликання котів'

Можливо, я просто впертий (тож пробачте мене ;-)). Так чи інакше, я прочитав пост блогу Юго Корнеліса, і це досить прямо. Однак у своєму прикладі він використовує один кластерний індекс та один некластеризований індекс, некластеризований індекс має менші розміри і тому використовується у плані виконання. У моєму випадку у мене є лише один кластерний індекс, чи може сервер sql ще повертати значення в неправильному порядку (у нього немає меншого індексу для використання, а сканування повних таблиць занадто повільно)?
m__

Я перемістив це на нове запитання (поза темою)
m__

5

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

Отже, спробуйте з MAXDOP 1 і подивіться, що станеться ...

Також я натякав на публікацію в блозі @sql kiwi на Simple Talk у розділі "Оператор обміну". І "залежність від DOP" тут


Хоча я раніше не намагався налаштувати функцію розділів date. Тепер я, і, здається, що розділення є винуватцем з 2005 року, можливо, поводиться краще для цього конкретного запиту.
Мартін Сміт

1

В основному ви праві - оскільки первинний ключ знаходиться в порядку "DeviceId, SensorId, Date", дані в ключі не сортуються за датою, тому їх не можна використовувати. Якщо ваш ключ був в іншому порядку «Дата, DeviceId, SensorId», то дані в ключі будуть відсортовані за датою, так можна було б використовувати ...


Я вже намагався змінити ключ так, як ви згадали, так що не шкодуйте. У будь-якому разі спробую створити некластеризований індекс у всіх 3 стовпцях і побачити, що це дає мені. (пошуки зниклого індексу продовжуються ... ;-))
m__
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.