Melhore o desempenho da consulta usando IN ()

14

Eu tenho a seguinte consulta 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;

Eu também tenho um índice na Eventtabela para a coluna TimeStamp. Meu entendimento é que esse índice não é usado por causa da IN()declaração. Portanto, minha pergunta é: existe uma maneira de criar um índice para essa IN()instrução específica para acelerar essa consulta?

Também tentei adicionar Event.EventTypeID IN (2, 5, 7, 8, 9, 14)como um filtro para o índice TimeStamp, mas, ao analisar o plano de execução, ele não parece estar usando esse índice. Qualquer sugestão ou insight sobre isso seria muito apreciada.

Abaixo está o plano gráfico:

Plano de execução

E aqui está um link para o arquivo .sqlplan .

SandersKY
fonte
Também podemos examinar o plano de execução? :)
dezso 18/12/12
1
E publique o plano de execução real (não estimado) com a extensão .sqlplan. A maioria das pessoas só deseja publicar uma captura de tela do plano gráfico, e isso é muito menos útil.
Aaron Bertrand
OK, eu adicionei um plano de execução e atualizei a consulta SQL.
SandersKY
@SandersKY É melhor alinhar o arquivo .sqlplan para manter tudo relacionado à pergunta no mesmo site.
Trygve Laugstøl
1
@trygvis - Isso geralmente não seria possível devido a limitações de tamanho nas postagens. A troca de pilha de vergonha não suporta a hospedagem de anexos de postagem internamente.
Martin Smith

Respostas:

18

Tabelas fornecidas da seguinte forma geral:

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

O seguinte índice é útil:

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

Para a consulta:

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

O filtro atende ao ANDrequisito da cláusula, a primeira chave do índice permite uma busca [TimeStamp]pelo filtro EventTypeIDse a inclusão da DeviceIDcoluna faz com que o índice cubra (porque DeviceIDé necessário para a associação à Devicetabela).

Plano finalizado

A segunda chave do índice - EventTypeIDnão é estritamente necessária (também pode ser uma INCLUDEdcoluna); Eu o incluí na chave pelos motivos expostos aqui . Em geral, aconselho as pessoas a pelo menos INCLUDEcolunas de uma WHEREcláusula de índice filtrado .


Com base no plano atualizado de consulta e execução da pergunta, concordo que o índice mais geral sugerido pelo SSMS é provavelmente a melhor opção aqui, a menos que a lista de filtros EventTypeIDsseja estática, como Aaron também menciona em sua resposta:

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

Índice sugerido (declare-o exclusivo, se apropriado):

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

Informações de cardinalidade do plano de execução (sintaxe não documentada, não use em sistemas de produção):

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;

Consulta atualizada (repetir a INlista da EventTypetabela ajuda o otimizador neste caso específico):

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;

Plano de execução estimado:

Segundo plano

O plano que você obtém provavelmente será diferente porque estou usando estatísticas adivinhadas. O ponto geral é fornecer ao otimizador o máximo de informações possível e fornecer um método de acesso eficiente (índice) na [Event]tabela de 4 milhões de linhas .

Paul White 9
fonte
8

A maior parte do custo é a verificação de índice em cluster e, a menos que essa tabela seja realmente ampla ou você não precise de todas essas colunas na saída, acredito no SQL Server que este é o caminho ideal no cenário atual sem mais nada alterado . Ele usa uma varredura de intervalo (rotulada como uma busca de IC) para restringir o intervalo de linhas em que está interessado, mas, devido à saída, ainda será necessária uma pesquisa ou uma varredura de IC, mesmo com o índice filtrado que você criou. é direcionado para esse intervalo e, mesmo nesse caso, a verificação de IC provavelmente ainda é mais barata (ou pelo menos o SQL Server calcula como tal).

O plano de execução informa que esse índice seria útil:

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

Embora, dependendo da inclinação dos dados, seja melhor o contrário, por exemplo:

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

Mas eu testaria ambos para ter certeza de qual é o melhor - a diferença entre um desses índices e o que você tem agora pode ser apenas marginal (muitas variáveis ​​para nós sabermos) e você deve levar em conta que um adicional O índice requer manutenção extra e isso pode afetar visivelmente suas operações DML (inserir / atualizar / excluir). Você também pode considerar incluir os critérios de filtro nesse índice, conforme sugerido por @SQLKiwi , mas apenas se esse for o conjunto de valores de EventTypeID que você pesquisa com freqüência. Se esse conjunto for alterado com o tempo, o índice filtrado será útil apenas para esta consulta específica.

Com uma contagem tão baixa de fileiras, tenho que me perguntar o quão ruim o desempenho poderia ser atualmente? Essa consulta retorna três linhas (mas não há indicação de quantas linhas foram rejeitadas). Quantas linhas na tabela?

Aaron Bertrand
fonte
4

Acabei de descobrir que o SQL Server 2008 R2 realmente fez uma sugestão de índice quando executei o plano de execução. Esse índice sugerido torna a consulta executada 90% mais rápida.

O índice sugerido foi o seguinte:

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
fonte