Mejore el rendimiento de la consulta con IN ()


14

Tengo la siguiente 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;

También tengo un índice en la Eventtabla para la columna TimeStamp. Tengo entendido que este índice no se usa debido a la IN()declaración. Entonces, mi pregunta es ¿hay alguna manera de hacer un índice para esta IN()declaración en particular para acelerar esta consulta?

También intenté agregar Event.EventTypeID IN (2, 5, 7, 8, 9, 14)como filtro para el índice TimeStamp, pero al mirar el plan de ejecución no parece estar usando este índice. Cualquier sugerencia o idea sobre esto sería muy apreciada.

A continuación se muestra el plan gráfico:

Plan de ejecución

Y aquí hay un enlace al archivo .sqlplan .


¿Podríamos mirar también el plan de ejecución? :)
dezso

1
Y publique el plan de ejecución real (no estimado) con la extensión .sqlplan. La mayoría de las personas solo quieren publicar una captura de pantalla del plan gráfico, y eso es mucho menos útil.
Aaron Bertrand

OK, agregué un plan de ejecución y actualicé la consulta SQL.
SandersKY

@SandersKY Es mejor incorporar el archivo .sqlplan para mantener todo lo relacionado con la pregunta en el mismo sitio.
Trygve Laugstøl

1
@trygvis: eso a menudo no sería posible debido a las limitaciones de longitud en las publicaciones. Shame stack exchange no es compatible con el alojamiento de archivos adjuntos de publicaciones internas.
Martin Smith

Respuestas:


18

Tablas dadas de la siguiente forma general:

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

El siguiente índice es útil:

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

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

El filtro cumple con el ANDrequisito de la cláusula, la primera clave del índice permite buscar [TimeStamp]el filtrado EventTypeIDsy la DeviceIDcolumna incluye la cobertura del índice (porque DeviceIDes necesario para la unión a la Devicetabla).

Plan terminado

La segunda clave del índice EventTypeIDno es estrictamente necesaria (también podría ser una INCLUDEdcolumna); Lo he incluido en la clave por los motivos aquí expuestos . En general, aconsejo a las personas que al menos INCLUDEcolumnas de una WHEREcláusula de índice filtrada .


Basado en la consulta actualizada y el plan de ejecución en la pregunta, estoy de acuerdo en que el índice más general sugerido por SSMS es probablemente la mejor opción aquí, a menos que la lista de filtrados EventTypeIDssea ​​estática como Aaron también menciona en su respuesta:

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 (declararlo único si es apropiado):

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

Información de cardinalidad del plan de ejecución (sintaxis no documentada, no usar en sistemas de producción):

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 actualizada (repetir la INlista para la EventTypetabla ayuda al optimizador en este 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;

Plan de ejecución estimado:

Segundo plan

Es probable que el plan que obtenga sea diferente porque estoy usando estadísticas adivinadas. El punto general es proporcionar al optimizador tanta información como sea posible y proporcionar un método de acceso eficiente (índice) en la [Event]tabla de 4 millones de filas .


8

La mayor parte del costo es el escaneo de índice agrupado, y a menos que esta tabla sea realmente amplia o realmente no necesite todas esas columnas en la salida, creo que SQL Server es la ruta óptima en el escenario actual sin nada más cambiado . Utiliza un escaneo de rango (etiquetado como una búsqueda de CI) para reducir el rango de filas en el que está interesado, pero debido a la salida, aún requerirá una búsqueda o un escaneo de CI incluso con el índice filtrado que creó. está dirigido a este rango, e incluso en ese caso, el escaneo de CI probablemente sea aún más barato (o al menos SQL Server lo estima como tal).

El plan de ejecución le dice que este índice sería útil:

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

Aunque dependiendo de su sesgo de datos, podría ser mejor al revés, por ejemplo:

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

Pero probaría ambos para estar seguro de cuál es mejor, si es así: la diferencia entre cualquiera de esos índices y lo que tiene ahora solo puede ser marginal (demasiadas variables para que sepamos) y debe tener en cuenta que un adicional El índice requiere mantenimiento adicional, y esto puede afectar notablemente sus operaciones DML (insertar / actualizar / eliminar). También puede considerar incluir los criterios de filtro en este índice como lo sugiere @SQLKiwi , pero solo si ese es el conjunto de valores EventTypeID que busca con frecuencia. Si ese conjunto cambia con el tiempo, el índice filtrado solo será útil para esta consulta específica.

Con un recuento de filas tan bajo, me pregunto qué tan malo podría ser el rendimiento actualmente. Esta consulta devuelve 3 filas (pero no hay ninguna indicación de cuántas filas rechazó). ¿Cuántas filas hay en la tabla?


4

Acabo de descubrir que SQL Server 2008 R2 realmente hizo una sugerencia de índice cuando ejecuté el plan de ejecución. Este índice sugerido hace que la consulta se ejecute aproximadamente un 90% más rápido.

El índice que sugirió fue el siguiente:

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
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.