Eu tenho um banco de dados relativamente grande de 550 GB em uma instância do SQL Server 2016 EE que tem um limite máximo de memória de 112 GB do total de 128 GB de RAM disponível para o sistema operacional. O banco de dados está no nível de compatibilidade mais recente de 130. Os desenvolvedores reclamaram da consulta abaixo que é executada dentro de um tempo aceitável de 30 segundos quando executada isoladamente, mas quando executam seus processos em escala, a mesma consulta é executada várias vezes simultaneamente em vários encadeamentos e é quando eles observam que o tempo de execução sofre e o desempenho/taxa de transferência cai. O T-SQL problemático é:
select distinct dg.entityId, et.EntityName, dg.Version
from DataGathering dg with(nolock)
inner join entity e with(nolock)
on e.EntityId = dg.EntityId
inner join entitytype et with(nolock)
on et.EntityTypeID = e.EntityTypeID
and et.EntityName = 'Account_Third_Party_Details'
inner join entitymapping em with(nolock)
on em.ChildEntityId = dg.EntityId
and em.ParentEntityId = -1
where dg.EntityId = dg.RootId
union all
select distinct dg1.EntityId, et.EntityName, dg1.version
from datagathering dg1 with(nolock)
inner join entity e with(nolock)
on e.EntityId = dg1.EntityId
inner join entitytype et with(nolock)
on et.EntityTypeID = e.EntityTypeID
and et.EntityName = 'TIN_Details'
where dg1.EntityId = dg1.RootId
and dg1.EntityId not in (
select distinct ChildEntityId
from entitymapping
where ChildEntityId = dg1.EntityId
and ParentEntityId = -1)
O plano de execução real mostra o aviso de concessão de memória abaixo:
O plano gráfico de execução pode ser encontrado aqui:
https://www.brentozar.com/pastetheplan/?id=r18ZtCidN
Abaixo estão as contagens de linhas e tamanhos das tabelas tocadas por esta consulta. O operador mais caro é uma varredura de índice de um índice não clusterizado na tabela DataGathering, o que faz sentido considerando o tamanho da tabela em comparação com as outras. Eu entendo por que/como a concessão de memória é necessária, o que acredito ser devido à forma como a consulta é escrita, o que requer vários tipos e operadores de hash. O que eu preciso de conselho / orientação é como evitar as concessões de memória, T-SQL e código de refatoração não é meu ponto forte, existe uma maneira de reescrever essa consulta para que ela tenha mais desempenho? Se eu puder ajustar a consulta para ser executada mais rapidamente isoladamente, espero que os benefícios sejam transferidos para quando ela for executada em escala, que é quando o desempenho começa a sofrer. Feliz em fornecer mais informações e esperando aprender algo com isso!
Após atualizar as estatísticas em 3 das tabelas:
UPDATE STATISTICS Entity WITH FULLSCAN;
UPDATE STATISTICS EntityMapping WITH FULLSCAN;
UPDATE STATISTICS EntityType WITH FULLSCAN;
...o plano de execução melhorou um pouco:
https://www.brentozar.com/pastetheplan/?id=rkVmdkh_4
Infelizmente, o aviso "Excessive Grant" ainda está lá.
Josh Darnell gentilmente sugeriu refatorar a consulta para o abaixo para evitar que o paralelismo seja inibido que ele detectou em um determinado operador. A consulta refatorada gera o erro "Msg 4104, Level 16, State 1, Line 7 O identificador de várias partes "et.EntityName" não pôde ser vinculado." Como faço para contornar isso?
DECLARE @tinDetailsId int;
SELECT @tinDetailsId = et.EntityTypeID
FROM entitytype et
WHERE et.EntityName = 'TIN_Details';
select distinct dg1.EntityId, et.EntityName, dg1.version
from datagathering dg1 with(nolock)
inner join entity e with(nolock)
on e.EntityId = dg1.EntityId
where dg1.EntityId = dg1.RootId
and e.EntityTypeID = @tinDetailsId
and dg1.EntityId not in (
select distinct ChildEntityId
from entitymapping
where ChildEntityId = dg1.EntityId
and ParentEntityId = -1)
UNION ALL
select distinct dg.entityId, et.EntityName, dg.Version
from DataGathering dg with(nolock)
inner join entity e with(nolock)
on e.EntityId = dg.EntityId
inner join entitytype et with(nolock)
on et.EntityTypeID = e.EntityTypeID
and et.EntityName = 'Account_Third_Party_Details'
inner join entitymapping em with(nolock)
on em.ChildEntityId = dg.EntityId
and em.ParentEntityId = -1
where dg.EntityId = dg.RootId
Isso pode não ajudar com a situação de concessão de memória (espero que as atualizações adicionais de estatísticas ajudem com isso), mas notei que o paralelismo está sendo inibido nesta consulta. Confira esta parte do plano:
Como há apenas uma linha no lado externo da junção de loops aninhados, todas as 900.000 linhas estão sendo canalizadas para um encadeamento. Portanto, apesar dessa consulta ser executada no DOP 8, essa parte do plano é completamente serial. Isso inclui o tipo. Aqui está o XML para esse tipo:
Se possível, considere evitar a junção a EntityType e, em vez disso, apenas pegue esse ID e filtre a tabela Entity com ele. Isso permitirá que seja apenas um predicado em uma varredura de índice da tabela Entity, permitindo o paralelismo e acelerando a execução.
Algo assim:
Que você poderia referenciar na metade inferior da consulta, eliminando a junção:
Você gostaria de fazer a mesma coisa com
EntityName
"Account_Third_Party_Details" na parte superior da consulta, pois tem o mesmo problema - com o dobro de linhas.PS: Totalmente alheio ao tópico em questão, notei que você tem
nolock
dicas em todas as tabelas desta consulta. Certifique-se de que você está ciente das implicações disso. Confira este blog bacana sobre o assunto:Maus hábitos : Colocar NOLOCK em todos os lugares por Aaron Bertrand
The Read Uncommitted Isolation Level de Paul White