Procurando ajuda para melhorar o desempenho desta consulta.
SQL Server 2008 R2 Enterprise , RAM máxima de 16 GB, CPU 40, Grau máximo de paralelismo 4.
SELECT DsJobStat.JobName AS JobName
, AJF.ApplGroup AS GroupName
, DsJobStat.JobStatus AS JobStatus
, AVG(CAST(DsJobStat.ElapsedSec AS FLOAT)) AS ElapsedSecAVG
, AVG(CAST(DsJobStat.CpuMSec AS FLOAT)) AS CpuMSecAVG
FROM DsJobStat, AJF
WHERE DsJobStat.NumericOrderNo=AJF.OrderNo
AND DsJobStat.Odate=AJF.Odate
AND DsJobStat.JobName NOT IN( SELECT [DsAvg].JobName FROM [DsAvg] )
GROUP BY DsJobStat.JobName
, AJF.ApplGroup
, DsJobStat.JobStatus
HAVING AVG(CAST(DsJobStat.ElapsedSec AS FLOAT)) <> 0;
Mensagem de execução,
(0 row(s) affected)
Table 'AJF'. Scan count 11, logical reads 45, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'DsAvg'. Scan count 2, logical reads 1926, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'DsJobStat'. Scan count 1, logical reads 3831235, physical reads 85, read-ahead reads 3724396, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
(1 row(s) affected)
SQL Server Execution Times:
CPU time = 67268 ms, elapsed time = 90206 ms.
Estrutura das tabelas:
-- 212271023 rows
CREATE TABLE [dbo].[DsJobStat](
[OrderID] [nvarchar](8) NOT NULL,
[JobNo] [int] NOT NULL,
[Odate] [datetime] NOT NULL,
[TaskType] [nvarchar](255) NULL,
[JobName] [nvarchar](255) NOT NULL,
[StartTime] [datetime] NULL,
[EndTime] [datetime] NULL,
[NodeID] [nvarchar](255) NULL,
[GroupName] [nvarchar](255) NULL,
[CompStat] [int] NULL,
[RerunCounter] [int] NOT NULL,
[JobStatus] [nvarchar](255) NULL,
[CpuMSec] [int] NULL,
[ElapsedSec] [int] NULL,
[StatusReason] [nvarchar](255) NULL,
[NumericOrderNo] [int] NULL,
CONSTRAINT [PK_DsJobStat] PRIMARY KEY CLUSTERED
( [OrderID] ASC,
[JobNo] ASC,
[Odate] ASC,
[JobName] ASC,
[RerunCounter] ASC
));
-- 48992126 rows
CREATE TABLE [dbo].[AJF](
[JobName] [nvarchar](255) NOT NULL,
[JobNo] [int] NOT NULL,
[OrderNo] [int] NOT NULL,
[Odate] [datetime] NOT NULL,
[SchedTab] [nvarchar](255) NULL,
[Application] [nvarchar](255) NULL,
[ApplGroup] [nvarchar](255) NULL,
[GroupName] [nvarchar](255) NULL,
[NodeID] [nvarchar](255) NULL,
[Memlib] [nvarchar](255) NULL,
[Memname] [nvarchar](255) NULL,
[CreationTime] [datetime] NULL,
CONSTRAINT [AJF$PrimaryKey] PRIMARY KEY CLUSTERED
( [JobName] ASC,
[JobNo] ASC,
[OrderNo] ASC,
[Odate] ASC
));
-- 413176 rows
CREATE TABLE [dbo].[DsAvg](
[JobName] [nvarchar](255) NULL,
[GroupName] [nvarchar](255) NULL,
[JobStatus] [nvarchar](255) NULL,
[ElapsedSecAVG] [float] NULL,
[CpuMSecAVG] [float] NULL
);
CREATE NONCLUSTERED INDEX [DJS_Dashboard_2] ON [dbo].[DsJobStat]
( [JobName] ASC,
[Odate] ASC,
[StartTime] ASC,
[EndTime] ASC
)
INCLUDE ( [OrderID],
[JobNo],
[NodeID],
[GroupName],
[JobStatus],
[CpuMSec],
[ElapsedSec],
[NumericOrderNo]) ;
CREATE NONCLUSTERED INDEX [Idx_Dashboard_AJF] ON [dbo].[AJF]
( [OrderNo] ASC,
[Odate] ASC
)
INCLUDE ( [SchedTab],
[Application],
[ApplGroup]) ;
CREATE NONCLUSTERED INDEX [DsAvg$JobName] ON [dbo].[DsAvg]
( [JobName] ASC
)
Plano de execução:
https://www.brentozar.com/pastetheplan/?id=rkUVhMlXM
Atualizar depois de receber respostas
Muito obrigado @Joe Obbish
Você está certo sobre o problema desta consulta, que é entre DsJobStat e DsAvg. Não se trata muito de como participar e não usar NOT IN.
De fato, há uma mesa como você adivinhou.
CREATE TABLE [dbo].[DSJobNames](
[JobName] [nvarchar](255) NOT NULL,
CONSTRAINT [DSJobNames$PrimaryKey] PRIMARY KEY CLUSTERED
( [JobName] ASC
) );
Eu tentei sua sugestão,
SELECT DsJobStat.JobName AS JobName
, AJF.ApplGroup AS GroupName
, DsJobStat.JobStatus AS JobStatus
, AVG(CAST(DsJobStat.ElapsedSec AS FLOAT)) AS ElapsedSecAVG
, Avg(CAST(DsJobStat.CpuMSec AS FLOAT)) AS CpuMSecAVG
FROM DsJobStat
INNER JOIN DSJobNames jn
ON jn.[JobName]= DsJobStat.[JobName]
INNER JOIN AJF
ON DsJobStat.Odate=AJF.Odate
AND DsJobStat.NumericOrderNo=AJF.OrderNo
WHERE NOT EXISTS ( SELECT 1 FROM [DsAvg] WHERE jn.JobName = [DsAvg].JobName )
GROUP BY DsJobStat.JobName, AJF.ApplGroup, DsJobStat.JobStatus
HAVING AVG(CAST(DsJobStat.ElapsedSec AS FLOAT)) <> 0;
Mensagem de execução:
(0 row(s) affected)
Table 'DSJobNames'. Scan count 5, logical reads 1244, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'DsAvg'. Scan count 5, logical reads 2129, physical reads 0, read-ahead reads 24, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'DsJobStat'. Scan count 8, logical reads 84, physical reads 0, read-ahead reads 83, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'AJF'. Scan count 5, logical reads 757999, physical reads 944, read-ahead reads 757311, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
(1 row(s) affected)
SQL Server Execution Times:
CPU time = 21776 ms, elapsed time = 33984 ms.
Plano de execução: https://www.brentozar.com/pastetheplan/?id=rJVkLSZ7f
Respostas:
Vamos começar considerando a ordem de junção. Você tem três referências de tabela na consulta. Qual pedido de associação pode oferecer o melhor desempenho? O otimizador de consulta considera que a junção de
DsJobStat
paraDsAvg
eliminará quase todas as linhas (as estimativas de cardinalidade caem de 212195000 para 1 linha). O plano real nos mostra que a estimativa é bem próxima da realidade (11 linhas sobrevivem à junção). No entanto, a junção é implementada como uma junção anti-mesclagem correta, para que todas as 212 milhões de linhas daDsJobStat
tabela sejam varridas apenas para produzir 11 linhas. Isso certamente poderia estar contribuindo para o longo tempo de execução da consulta, mas não consigo pensar em um operador físico ou lógico melhor para essa associação que teria sido melhor. Tenho certeza de que oDJS_Dashboard_2
O índice é usado para outras consultas, mas todas as chaves extras e as colunas incluídas exigirão apenas mais IO para essa consulta e o atrasarão. Portanto, você potencialmente tem um problema de acesso à tabela com a verificação de índice naDsJobStat
tabela.Vou assumir que a associação
AJF
não é muito seletiva. No momento, ele não é relevante para os problemas de desempenho que você está vendo na consulta, então vou ignorá-lo pelo restante desta resposta. Isso pode mudar se os dados na tabela mudarem.O outro problema aparente no plano é o operador de spool de contagem de linhas. Este é um operador muito leve, mas está executando mais de 200 milhões de vezes. O operador está lá porque a consulta é gravada com
NOT IN
. Se houver uma única linha NULLDsAvg
, todas as linhas deverão ser eliminadas. O spool é a implementação dessa verificação. Essa provavelmente não é a lógica que você deseja, então seria melhor escrever essa parte para usarNOT EXISTS
. O benefício real dessa reescrita dependerá do seu sistema e dados.Simulei alguns dados com base no plano de consulta para testar algumas reescritas de consulta. Minhas definições de tabela são significativamente diferentes das suas porque seria muito difícil simular dados para cada coluna. Mesmo com as estruturas de dados abreviadas, pude reproduzir o problema de desempenho que você está enfrentando.
Com base no plano de consulta, podemos ver que existem cerca de 200000
JobName
valores exclusivos naDsAvg
tabela. Com base no número real de linhas após a junção a essa tabela, podemos ver que quase todos osJobName
valoresDsJobStat
também estão naDsAvg
tabela. Portanto, aDsJobStat
tabela possui 200001 valores exclusivos para aJobName
coluna e 1000 linhas por valor.Acredito que esta consulta represente o problema de desempenho:
Todas as outras coisas no seu plano de consulta (
GROUP BY
,HAVING
junção de estilo antigo etc.) acontecem depois que o conjunto de resultados é reduzido para 11 linhas. Atualmente, não importa do ponto de vista do desempenho da consulta, mas pode haver outras preocupações que podem ser reveladas pelos dados alterados em suas tabelas.Estou testando no SQL Server 2017, mas recebo a mesma forma básica de plano que você:
Na minha máquina, essa consulta leva 62219 ms de tempo da CPU e 65576 ms de tempo decorrido para executar. Se eu reescrever a consulta para usar
NOT EXISTS
:O spool não é mais executado 212 milhões de vezes e provavelmente tem o comportamento pretendido pelo fornecedor. Agora, a consulta é executada em 34516 ms do tempo da CPU e 41132 ms do tempo decorrido. A maior parte do tempo é gasta na digitalização de 212 milhões de linhas a partir do índice.
Essa verificação de índice é muito infeliz para essa consulta. Em média, temos 1000 linhas por valor único de
JobName
, mas sabemos depois de ler a primeira linha se precisaremos das 1000 linhas anteriores. Quase nunca precisamos dessas linhas, mas ainda precisamos varrê-las de qualquer maneira. Se soubermos que as linhas não são muito densas na tabela e que quase todas serão eliminadas pela junção, podemos imaginar um padrão de IO possivelmente mais eficiente no índice. E se o SQL Server leu a primeira linha por valor exclusivo deJobName
, verificou se esse valor estavaDsAvg
e simplesmente passou para o próximo valor deJobName
se estivesse? Em vez de digitalizar 212 milhões de linhas, um plano de busca que requer cerca de 200 mil execuções pode ser feito.Isso pode ser feito principalmente usando recursão junto com uma técnica pioneira de Paul White descrita aqui . Podemos usar a recursão para executar o padrão de E / S que descrevi acima:
Essa consulta é muito difícil de analisar, por isso recomendo examinar cuidadosamente o plano real . Primeiro, fazemos 200002 buscas no índice
DsJobStat
para obter todos osJobName
valores exclusivos . Em seguida, ingressamosDsAvg
e eliminamos todas as linhas, exceto uma. Para a linha restante, retorneDsJobStat
e obtenha todas as colunas necessárias.O padrão de IO muda totalmente. Antes de conseguirmos isso:
Com a consulta recursiva, obtemos o seguinte:
Na minha máquina, a nova consulta é executada em apenas 6891 ms de tempo de CPU e 7107 ms de tempo decorrido. Observe que a necessidade de usar a recursão dessa maneira sugere que algo está faltando no modelo de dados (ou talvez não tenha sido declarado na pergunta postada). Se houver uma tabela relativamente pequena que contenha todo o possível
JobNames
, será muito melhor usá-la em oposição à recursão na mesa grande. O que se resume a isso é que, se você tiver um conjunto de resultados contendo tudo oJobNames
que precisa, poderá usar o índice procura obter o restante das colunas ausentes. No entanto, você não pode fazer isso com um conjunto de resultados doJobNames
que NÃO precisa.fonte
NOT EXISTS
. Eles já responderam com "Eu já tentei os dois, entre e não existe, antes de postar a pergunta. Não há muita diferença."Veja o que acontece se você reescrever a condição,
Para
Considere também reescrever sua junção SQL89 porque esse estilo é horrível.
Ao invés de
Tentar
Eu também suspeito que essa condição possa ser escrita melhor, mas teríamos que saber mais sobre o que está acontecendo
Você realmente precisa saber que a média não é zero ou apenas que um elemento do grupo não é zero?
fonte