Улучшить производительность запроса с помощью 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 .

SandersKY
источник
Можем ли мы посмотреть на план выполнения тоже? :)
Дезсо 18.12.12
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 миллионами строк .

Пол Уайт 9
источник
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
SandersKY
источник