Cuando agrego dos columnas a mi selección, la consulta no responde. El tipo de columna es nvarchar(2000)
. Es un poco inusual.
- La versión de SQL Server es 2014.
- Solo hay un índice primario.
- Los registros completos son solo 1000 filas.
Aquí está el plan de ejecución antes (plan de presentación XML ):
Plan de ejecución después (plan de presentación XML ):
Aquí está la consulta:
select top(100)
Batch_Tasks_Queue.id,
btq.id,
Batch_Tasks_Queue.[Parameters], -- this field
btq.[Parameters] -- and this field
from
Batch_Tasks_Queue with(nolock)
inner join Batch_Tasks_Queue btq with(nolock) on Batch_Tasks_Queue.Start_Time < btq.Start_Time
and btq.Start_Time < Batch_Tasks_Queue.Finish_Time
and Batch_Tasks_Queue.id <> btq.id
and btq.Start_Time is not null
and btq.State in (3, 4)
where
Batch_Tasks_Queue.Start_Time is not null
and Batch_Tasks_Queue.State in (3, 4)
and Batch_Tasks_Queue.Operation_Type = btq.Operation_Type
and Batch_Tasks_Queue.Operation_Type not in (23, 24, 25, 26, 27, 28, 30)
order by
Batch_Tasks_Queue.Start_Time desc
El resultado total es de 17 filas. Los datos sucios (pista nolock) no son importantes.
Aquí está la estructura de la tabla:
CREATE TABLE [dbo].[Batch_Tasks_Queue](
[Id] [int] NOT NULL,
[OBJ_VERSION] [numeric](8, 0) NOT NULL,
[Operation_Type] [numeric](2, 0) NULL,
[Request_Time] [datetime] NOT NULL,
[Description] [varchar](1000) NULL,
[State] [numeric](1, 0) NOT NULL,
[Start_Time] [datetime] NULL,
[Finish_Time] [datetime] NULL,
[Parameters] [nvarchar](2000) NULL,
[Response] [nvarchar](max) NULL,
[Billing_UserId] [int] NOT NULL,
[Planned_Start_Time] [datetime] NULL,
[Input_FileId] [uniqueidentifier] NULL,
[Output_FileId] [uniqueidentifier] NULL,
[PRIORITY] [numeric](2, 0) NULL,
[EXECUTE_SEQ] [numeric](2, 0) NULL,
[View_Access] [numeric](1, 0) NULL,
[Seeing] [numeric](1, 0) NULL,
CONSTRAINT [PKBachTskQ] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [Batch_Tasks_QueueData]
) ON [Batch_Tasks_QueueData] TEXTIMAGE_ON [Batch_Tasks_QueueData]
GO
SET ANSI_PADDING OFF
GO
ALTER TABLE [dbo].[Batch_Tasks_Queue] WITH NOCHECK ADD CONSTRAINT [FK0_BtchTskQ_BlngUsr] FOREIGN KEY([Billing_UserId])
REFERENCES [dbo].[BILLING_USER] ([ID])
GO
ALTER TABLE [dbo].[Batch_Tasks_Queue] CHECK CONSTRAINT [FK0_BtchTskQ_BlngUsr]
GO
sql-server
query-performance
sql-server-2014
Hamid Fathi
fuente
fuente
Respuestas:
Resumen
Los principales problemas son:
Detalles
Los dos planes son fundamentalmente bastante similares, aunque el rendimiento puede ser muy diferente:
Plan con las columnas adicionales
Tomando el que tiene las columnas adicionales que no se completa en un tiempo razonable primero:
Las características interesantes son:
Start_Time
no es nulo,State
es 3 o 4 yOperation_Type
es uno de los valores enumerados. La tabla se escanea completamente una vez, y cada fila se prueba con los predicados mencionados. Solo las filas que pasan todas las pruebas fluyen a la Clasificación. El optimizador estima que 38,283 filas calificarán.Start_Time DESC
. Este es el orden de presentación final solicitado por la consulta.Start_Time
no es nulo yState
es 3 o 4. Se estima que esto produce 400.875 filas en cada iteración. En 94.2791 iteraciones, el número total de filas es de casi 38 millones.Operation_Type
coincide, que elStart_Time
nodo 4 es menor que elStart_Time
nodo 5, que elStart_Time
nodo 5 es menor que elFinish_Time
nodo 4 y que los dosId
valores no coinciden.La gran ineficiencia es obviamente en los pasos 6 y 7 anteriores. Escanear completamente la tabla en el nodo 5 para cada iteración es incluso un poco razonable si solo sucede 94 veces como predice el optimizador. El conjunto de comparaciones de ~ 38 millones por fila en el nodo 2 también es un gran costo.
De manera crucial, es muy probable que la estimación del objetivo de la fila de la fila 93/94 sea incorrecta, ya que depende de la distribución de valores. El optimizador asume una distribución uniforme en ausencia de información más detallada. En términos simples, esto significa que si se espera que el 1% de las filas de la tabla califiquen, el optimizador razona que para encontrar 1 fila coincidente, debe leer 100 filas.
Si ejecutó esta consulta hasta su finalización (lo que puede llevar mucho tiempo), lo más probable es que tenga que leer más de 93/94 filas de la Clasificación para finalmente producir 100 filas. En el peor de los casos, la fila número 100 se encontraría utilizando la última fila del Ordenar. Suponiendo que la estimación del optimizador en el nodo 4 es correcta, esto significa ejecutar el Escaneo en el nodo 5 38,284 veces, para un total de algo así como 15 mil millones de filas. Podría ser más si las estimaciones de escaneo también están desactivadas.
Este plan de ejecución también incluye una advertencia de índice faltante:
El optimizador lo alerta sobre el hecho de que agregar un índice a la tabla mejoraría el rendimiento.
Plan sin las columnas adicionales
Este es esencialmente el mismo plan que el anterior, con la adición de la cola de índice en el nodo 6 y el filtro en el nodo 5. Las diferencias importantes son:
Operation_Type
yStart_Time
,Id
como una columna sin clave.Operation_Type
,Start_Time
,Finish_Time
, yId
de la exploración en el nodo 4 se pasan a la rama del lado interior como referencias externas.Operation_Type
coincide con el valor de referencia exterior actual, y elStart_Time
está en el intervalo definido por losStart_Time
yFinish_Time
exteriores referencias.Id
valores del carrete de índice para determinar la desigualdad con el valor de referencia externo actual deId
.Las mejoras clave son:
Operation_Type
,Start_Time
) conId
una columna incluida permite unir los bucles anidados de índice. El índice se utiliza para buscar filas coincidentes en cada iteración en lugar de escanear toda la tabla cada vez.Como antes, el optimizador incluye una advertencia sobre un índice faltante:
Conclusión
El plan sin las columnas adicionales es más rápido porque el optimizador eligió crear un índice temporal para usted.
El plan con las columnas adicionales haría que el índice temporal sea más costoso de construir. La
[Parameters
columna] esnvarchar(2000)
, que agregaría hasta 4000 bytes a cada fila del índice. El costo adicional es suficiente para convencer al optimizador de que construir el índice temporal en cada ejecución no se pagaría por sí mismo.El optimizador advierte en ambos casos que un índice permanente sería una mejor solución. La composición ideal del índice depende de su carga de trabajo más amplia. Para esta consulta en particular, los índices sugeridos son un punto de partida razonable, pero debe comprender los beneficios y costos involucrados.
Recomendación
Una amplia gama de posibles índices sería beneficiosa para esta consulta. La conclusión importante es que se necesita algún tipo de índice no agrupado. De la información proporcionada, un índice razonable en mi opinión sería:
También estaría tentado a organizar la consulta un poco mejor, y retrasaría la búsqueda de las
[Parameters]
columnas anchas en el índice agrupado hasta después de que se hayan encontrado las 100 filas superiores (utilizandoId
como clave):Cuando las
[Parameters]
columnas no son necesarias, la consulta se puede simplificar para:La
FORCESEEK
sugerencia está ahí para ayudar a garantizar que el optimizador elija un plan de bucles anidados indexados (existe una tentación basada en el costo para que el optimizador seleccione un hash o (muchos-muchos) fusionar, de lo contrario, lo que no suele funcionar bien con este tipo de consulta en la práctica. Ambos terminan con grandes residuos; muchos elementos por cubo en el caso del hash, y muchos rebobinados para la fusión).Alternativa
Si la consulta (incluidos sus valores específicos) fuera particularmente crítica para el rendimiento de lectura, consideraría dos índices filtrados:
Para la consulta que no necesita la
[Parameters]
columna, el plan estimado que utiliza los índices filtrados es:La exploración de índice devuelve automáticamente todas las filas que califican sin evaluar ningún predicado adicional. Para cada iteración de la unión de bucles anidados de índice, la búsqueda de índice realiza dos operaciones de búsqueda:
Operation_Type
yState
= 3, luego busca el rango deStart_Time
valores, predicado residual en laId
desigualdad.Operation_Type
yState
= 4, luego busca el rango deStart_Time
valores, predicado residual de laId
desigualdad.Cuando
[Parameters]
se necesita la columna, el plan de consulta simplemente agrega un máximo de 100 búsquedas simples para cada tabla:Como nota final, debe considerar usar los tipos enteros estándar integrados en lugar de
numeric
donde corresponda.fuente
Por favor cree el siguiente índice:
fuente