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 descEl 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_Timeno es nulo,Statees 3 o 4 yOperation_Typees 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_Timeno es nulo yStatees 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_Typecoincide, que elStart_Timenodo 4 es menor que elStart_Timenodo 5, que elStart_Timenodo 5 es menor que elFinish_Timenodo 4 y que los dosIdvalores 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_TypeyStart_Time,Idcomo una columna sin clave.Operation_Type,Start_Time,Finish_Time, yIdde la exploración en el nodo 4 se pasan a la rama del lado interior como referencias externas.Operation_Typecoincide con el valor de referencia exterior actual, y elStart_Timeestá en el intervalo definido por losStart_TimeyFinish_Timeexteriores referencias.Idvalores del carrete de índice para determinar la desigualdad con el valor de referencia externo actual deId.Las mejoras clave son:
Operation_Type,Start_Time) conIduna 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
[Parameterscolumna] 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 (utilizandoIdcomo clave):Cuando las
[Parameters]columnas no son necesarias, la consulta se puede simplificar para:La
FORCESEEKsugerencia 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_TypeyState= 3, luego busca el rango deStart_Timevalores, predicado residual en laIddesigualdad.Operation_TypeyState= 4, luego busca el rango deStart_Timevalores, predicado residual de laIddesigualdad.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
numericdonde corresponda.fuente
Por favor cree el siguiente índice:
fuente