Підвищити ефективність запиту за допомогою IN ()


14

У мене є такий SQL-запит:

SELECT
  Event.ID,
  Event.IATA,
  Device.Name,
  EventType.Description,
  Event.Data1,
  Event.Data2
  Event.PLCTimeStamp,
  Event.EventTypeID
FROM
  Event
INNER JOIN EventType ON EventType.ID = Event.EventTypeID
INNER JOIN Device ON Device.ID = Event.DeviceID
WHERE
  Event.EventTypeID IN (3, 30, 40, 41, 42, 46, 49, 50)
  AND Event.PLCTimeStamp BETWEEN '2011-01-28' AND '2011-01-29'
  AND Event.IATA LIKE '%0005836217%'
ORDER BY Event.ID;

У мене також є індекс на Eventстолі для стовпчика TimeStamp. Я розумію, що цей індекс не використовується через IN()твердження. Отже, моє запитання: чи є спосіб зробити індекс для цього конкретного IN()оператора, щоб пришвидшити цей запит?

Я також спробував додати Event.EventTypeID IN (2, 5, 7, 8, 9, 14)як фільтр для індексу TimeStamp, але, дивлячись на план виконання, він, здається, не використовує цей індекс. Будь-які пропозиції чи розуміння цього були б вдячні.

Нижче наведено графічний план:

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

А ось посилання на файл .sqlplan .


Чи можемо ми також подивитись план виконання? :)
dezso

1
І, будь ласка, опублікуйте фактичний план виконання (не оцінено) з розширенням .sqlplan. Більшість людей просто хочуть розмістити знімок екрана графічного плану, і це набагато менш корисно.
Аарон Бертран

Гаразд Я додав план виконання, а також оновив запит SQL.
SandersKY

@SandersKY Найкраще вбудувати файл .sqlplan, щоб зберегти все, що стосується питання, на одному веб-сайті.
Trygve Laugstøl

1
@trygvis - Це часто було б неможливо через обмеження тривалості публікацій. Обмін стеками сорому не підтримує розміщення вкладених файлів, розміщених всередині країни.
Мартін Сміт

Відповіді:


18

Дано таблиці такої загальної форми:

CREATE TABLE Device 
(
    ID integer PRIMARY KEY
);

CREATE TABLE EventType
(
    ID integer PRIMARY KEY, 
    Name nvarchar(50) NOT NULL
);

CREATE TABLE [Event]
(
    ID integer PRIMARY KEY, 
    [TimeStamp] datetime NOT NULL, 
    EventTypeID integer NOT NULL REFERENCES EventType, 
    DeviceID integer NOT NULL REFERENCES Device
);

Наступний індекс корисний:

CREATE INDEX f1 
ON [Event] ([TimeStamp], EventTypeID) 
INCLUDE (DeviceID)
WHERE EventTypeID IN (2, 5, 7, 8, 9, 14);

Для запиту:

SELECT
  [Event].ID,
  [Event].[TimeStamp],
  EventType.Name,
  Device.ID
FROM
  [Event]
INNER JOIN EventType ON EventType.ID = [Event].EventTypeID
INNER JOIN Device ON Device.ID = [Event].DeviceID
WHERE
  [Event].[TimeStamp] BETWEEN '2011-01-28' AND '2011-01-29'
  AND Event.EventTypeID IN (2, 5, 7, 8, 9, 14);

Фільтр відповідає ANDвимозі пункту, перший ключ індексу дозволяє шукати [TimeStamp]відфільтрований EventTypeIDsі включаючи DeviceIDстовпець робить покриття індексу (оскільки DeviceIDце потрібно для приєднання до Deviceтаблиці).

Готовий план

Другий ключ індексу - EventTypeIDне є строго необхідним (він також може бути INCLUDEdстовпцем); Я включив його в ключі для причин , вказаних тут . Взагалі, я раджу людям хоча б INCLUDEстовпці з відфільтрованого індексу WHERE.


Виходячи з оновленого плану запитів та виконання у питанні, я погоджуюся, що більш загальний індекс, запропонований SSMS, швидше за все, тут буде кращим вибором, якщо тільки список відфільтрованих не EventTypeIDsє статичним, як Аарон також згадує у своїй відповіді:

CREATE TABLE Device 
(
    ID integer PRIMARY KEY,
    Name nvarchar(50) NOT NULL UNIQUE
);

CREATE TABLE EventType
(
    ID integer PRIMARY KEY, 
    Name nvarchar(20) NOT NULL UNIQUE,
    [Description] nvarchar(100) NOT NULL
);

CREATE TABLE [Event]
(
    ID integer PRIMARY KEY, 
    PLCTimeStamp datetime NOT NULL,
    EventTypeID integer NOT NULL REFERENCES EventType, 
    DeviceID integer NOT NULL REFERENCES Device,
    IATA varchar(50) NOT NULL,
    Data1 integer NULL,
    Data2 integer NULL,
);

Запропонований індекс (оголосити його унікальним, якщо це доречно):

CREATE UNIQUE INDEX uq1
ON [Event]
    (EventTypeID, PLCTimeStamp)
INCLUDE 
    (DeviceID, IATA, Data1, Data2, ID);

Інформація про кардинальність плану виконання (недокументований синтаксис, не використовується у виробничих системах):

UPDATE STATISTICS dbo.Event WITH ROWCOUNT = 4042700, PAGECOUNT = 400000;
UPDATE STATISTICS dbo.EventType WITH ROWCOUNT = 22, PAGECOUNT = 1;
UPDATE STATISTICS dbo.Device WITH ROWCOUNT = 2806, PAGECOUNT = 28;

Оновлений запит (повторення INсписку EventTypeтаблиці допомагає оптимізатору в цьому конкретному випадку):

SELECT
  Event.ID,
  Event.IATA,
  Device.Name,
  EventType.Description,
  Event.Data1,
  Event.Data2,
  Event.PLCTimeStamp,
  Event.EventTypeID
FROM
  Event
INNER JOIN EventType ON EventType.ID = Event.EventTypeID
INNER JOIN Device ON Device.ID = Event.DeviceID
WHERE
  Event.EventTypeID IN (3, 30, 40, 41, 42, 46, 49, 50)
  AND EventType.ID IN (3, 30, 40, 41, 42, 46, 49, 50)
  AND Event.PLCTimeStamp BETWEEN '2011-01-28' AND '2011-01-29'
  AND Event.IATA LIKE '%0005836217%'
ORDER BY Event.ID;

Розрахунковий план виконання:

Другий план

План, який ви отримаєте, ймовірно, буде іншим, оскільки я використовую здогадані статистичні дані. Загальний пункт - надати оптимізатору якомога більше інформації та надати ефективний метод доступу (індекс) у [Event]таблиці 4-мільйонних рядків .


8

Більша частина вартості - це кластерне сканування індексу, і якщо ця таблиця дійсно не широка, або вам не потрібні всі ці стовпці у висновку, я вважаю, що SQL Server є оптимальним шляхом у поточному сценарії, і нічого іншого не змінюється . Він використовує сканування діапазону (позначений як прагнення CI), щоб звузити діапазон рядків, які його цікавлять, але через вихідний результат все одно вимагатиме або пошуку, або сканування CI, навіть із відфільтрованим індексом, який ви створили, орієнтований на цей діапазон, і навіть у цьому випадку сканування CI, ймовірно, все ще найдешевший (або принаймні SQL Server оцінює його як такий).

План виконання говорить вам, що цей індекс буде корисним:

CREATE NONCLUSTERED INDEX ix_EventTypeID_PLCTimeStamp_WithIncludes
  ON [dbo].[Event] ([EventTypeID],[PLCTimeStamp])
  INCLUDE ([ID],[DeviceID],[Data1],[Data2],[IATA]);

Хоча залежно від перекосу ваших даних, може бути краще навпаки, наприклад:

CREATE NONCLUSTERED INDEX ix_PLCTimeStamp_EventTypeID_WithIncludes
  ON [dbo].[Event] ([PLCTimeStamp],[EventTypeID])
  INCLUDE ([ID],[DeviceID],[Data1],[Data2],[IATA]);

Але я б протестував, щоб бути впевненим, що краще, якщо будь-який - різниця між будь-яким із цих індексів і тим, що ви маєте зараз, може бути лише незначною (занадто багато змінних, щоб ми знали), і ви повинні врахувати, що додатковий індекс вимагає додаткового обслуговування, і це може помітно вплинути на ваші операції з DML (вставити / оновити / видалити). Ви також можете розглянути можливість включення критеріїв фільтрування до цього індексу, як це запропоновано @SQLKiwi , але лише якщо це набір значень EventTypeID, які ви часто шукаєте. Якщо цей набір змінюється з часом, то відфільтрований індекс буде корисний лише для цього конкретного запиту.

Маючи таке низьке число рядків, я мушу задуматися, наскільки поганою може бути наразі продуктивність? Цей запит повертає 3 рядки (але немає жодних вказівок, скільки рядків він відхилив). Скільки рядків у таблиці?


4

Я просто виявляю, що SQL Server 2008 R2 насправді робив пропозицію щодо індексу, коли я запускав план виконання. Цей запропонований індекс робить запуск запиту приблизно на 90% швидшим.

Запропонований ним індекс був таким:

CREATE NONCLUSTERED INDEX [INDEX_spBagSearch] ON [dbo].[Event] 
(
    [EventTypeID] ASC,
    [PLCTimeStamp] ASC
)
INCLUDE ( [ID],
[DeviceID],
[Data1],
[Data2],
[IATA]) WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
GO
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.