Quando adiciono duas colunas à minha seleção, a consulta não responde. O tipo de coluna é nvarchar(2000)
. É um pouco incomum.
- A versão do SQL Server é 2014.
- Existe apenas um índice primário.
- O registro inteiro tem apenas 1000 linhas.
Aqui está o plano de execução antes ( XML showplan ):
Plano de execução após ( XML showplan ):
Aqui está a 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
A contagem total de resultados é de 17 linhas. Os dados sujos (dica nolock) não são importantes.
Aqui está a estrutura da tabela:
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
fonte
fonte
Respostas:
Sumário
Os principais problemas são:
Detalhes
Os dois planos são fundamentalmente bastante semelhantes, embora o desempenho possa ser muito diferente:
Planejar com as colunas extras
Tomando o primeiro com as colunas extras que não são concluídas em um tempo razoável primeiro:
Os recursos interessantes são:
Start_Time
não é nulo,State
é 3 ou 4 eOperation_Type
é um dos valores listados. A tabela é totalmente varrida uma vez, com cada linha sendo testada com relação aos predicados mencionados. Somente as linhas que passam em todos os testes fluem para a Classificação. O otimizador estima que 38.283 linhas serão qualificadas.Start_Time DESC
. Essa é a ordem de apresentação final solicitada pela consulta.Start_Time
não é nulo eState
é 3 ou 4. Estima-se que produz 400.875 linhas em cada iteração. Mais de 94.2791 iterações, o número total de linhas é quase 38 milhões.Operation_Type
correspondência, se oStart_Time
nó 4 é menor que oStart_Time
nó 5, se oStart_Time
nó 5 é menor que oFinish_Time
nó 4 e se os doisId
valores não correspondem.A grande ineficiência está obviamente nas etapas 6 e 7 acima. A varredura completa da tabela no nó 5 para cada iteração é apenas um pouco razoável, se ocorrer apenas 94 vezes como o otimizador prevê. O conjunto de comparações de ~ 38 milhões por linha no nó 2 também é um custo alto.
Fundamentalmente, a estimativa de meta de linha de 93/94 linhas também provavelmente está errada, pois depende da distribuição de valores. O otimizador assume distribuição uniforme na ausência de informações mais detalhadas. Em termos simples, isso significa que, se se espera que 1% das linhas da tabela se qualifiquem, o otimizador raciocina que, para encontrar uma linha correspondente, ele precisa ler 100 linhas.
Se você executou essa consulta até a conclusão (o que pode levar muito tempo), provavelmente descobrirá que muito mais que 93/94 linhas precisam ser lidas na Classificação para finalmente produzir 100 linhas. Na pior das hipóteses, a 100ª linha seria encontrada usando a última linha da Classificação. Supondo que a estimativa do otimizador no nó 4 esteja correta, isso significa executar a verificação no nó 5 38.284 vezes, para um total de algo como 15 bilhões de linhas. Pode ser mais se as estimativas de digitalização também estiverem desativadas.
Este plano de execução também inclui um aviso de índice ausente:
O otimizador está alertando você para o fato de que adicionar um índice à tabela melhoraria o desempenho.
Planejar sem as colunas extras
Esse é essencialmente o mesmo plano que o anterior, com a adição do Index Spool no nó 6 e do Filtro no nó 5. As diferenças importantes são:
Operation_Type
eStart_Time
, comId
como uma coluna não-chave.Operation_Type
,Start_Time
,Finish_Time
, eId
da verificação no nó 4 são passados para o ramo do lado interno como referências externas.Operation_Type
corresponde ao valor de referência externa atual eStart_Time
está no intervalo definido pelas referências externasStart_Time
eFinish_Time
.Id
valores do Spool do Índice quanto à desigualdade em relação ao valor de referência externa atual deId
.As principais melhorias são:
Operation_Type
,Start_Time
) comId
como uma coluna incluída permite a junção de loops aninhados ao índice. O índice é usado para procurar linhas correspondentes em cada iteração, em vez de varrer a tabela inteira a cada vez.Como antes, o otimizador inclui um aviso sobre um índice ausente:
Conclusão
O plano sem as colunas extras é mais rápido porque o otimizador optou por criar um índice temporário para você.
O plano com as colunas extras tornaria o índice temporário mais caro de construir. A
[Parameters
coluna] énvarchar(2000)
, que adicionaria até 4000 bytes a cada linha do índice. O custo adicional é suficiente para convencer o otimizador de que a criação do índice temporário em cada execução não se pagaria.O otimizador avisa nos dois casos que um índice permanente seria uma solução melhor. A composição ideal do índice depende da sua carga de trabalho mais ampla. Para essa consulta específica, os índices sugeridos são um ponto de partida razoável, mas você deve entender os benefícios e custos envolvidos.
Recomendação
Uma ampla variedade de índices possíveis seria benéfica para esta consulta. O ponto importante é que é necessário algum tipo de índice não clusterizado. A partir das informações fornecidas, um índice razoável na minha opinião seria:
Eu também ficaria tentado a organizar a consulta um pouco melhor e demoraria a procurar as
[Parameters]
colunas largas no índice clusterizado até depois que as 100 principais linhas fossem encontradas (usandoId
como chave):Onde as
[Parameters]
colunas não são necessárias, a consulta pode ser simplificada para:A
FORCESEEK
dica está aqui para ajudar a garantir que o otimizador escolha um plano de loops aninhados indexados (existe uma tentação baseada em custo para o otimizador selecionar uma junção de hash ou (muitos) muitos), caso contrário, o que tende a não funcionar bem com esse tipo de Na prática, ambos acabam com grandes resíduos; muitos itens por balde no caso do hash e muitos rebobinamentos para a mesclagem).Alternativa
Se a consulta (incluindo seus valores específicos) fosse particularmente crítica para o desempenho da leitura, consideraria dois índices filtrados:
Para a consulta que não precisa da
[Parameters]
coluna, o plano estimado usando os índices filtrados é:A varredura de índice retorna automaticamente todas as linhas qualificadas sem avaliar nenhum predicado adicional. Para cada iteração da junção de loops aninhados do índice, a busca do índice executa duas operações de busca:
Operation_Type
eState
= 3, buscando a faixa deStart_Time
valores, predicado residual daId
desigualdade.Operation_Type
eState
= 4, buscando a faixa deStart_Time
valores, predicado residual daId
desigualdade.Onde a
[Parameters]
coluna é necessária, o plano de consulta simplesmente adiciona no máximo 100 pesquisas singleton para cada tabela:Como nota final, considere usar os tipos inteiros padrão incorporados em vez de
numeric
onde aplicável.fonte
Por favor, crie o seguinte índice:
fonte