Supere MERGE JOIN (INDEX SCAN) com o valor explícito de KEY única em uma KEY FOREIGN

9

Adicionado em 11/11 O problema é que ocorrem conflitos devido à verificação do índice durante o MERGE JOIN. Nesse caso, uma transação tentando obter o bloqueio S em todo o índice na tabela pai FK, mas anteriormente outra transação coloca o bloqueio X em um valor-chave do índice.

Deixe-me começar com um pequeno exemplo (TSQL2012 DB de 70-461 cource usado):

CREATE TABLE [Sales].[Orders](
[orderid] [int] IDENTITY(1,1) NOT NULL,
[custid] [int] NULL,
[empid] [int] NOT NULL,
[shipperid] [int] NOT NULL,
... )

As colunas [custid], [empid], [shipperid]são parâmetros correlacionados de [Sales].[Customers], [HR].[Employees], [Sales].[Shippers]acordo. Em cada caso, temos um índice agrupado em uma coluna referida em uma tabela de restrição.

ALTER TABLE [Sales].[Orders]  WITH CHECK ADD  CONSTRAINT [FK_Orders_Customers] FOREIGN KEY([custid]) REFERENCES [Sales].[Customers] ([custid])
ALTER TABLE [Sales].[Orders]  WITH CHECK ADD  CONSTRAINT [FK_Orders_Employees] FOREIGN KEY([empid]) REFERENCES [HR].[Employees] ([empid])
ALTER TABLE [Sales].[Orders]  WITH CHECK ADD  CONSTRAINT [FK_Orders_Shippers] FOREIGN KEY([shipperid])REFERENCES [Sales].[Shippers] ([shipperid])

Estou tentando INSERT [Sales].[Orders] SELECT ... FROMoutra tabela chamada [Sales].[OrdersCache]que tem a mesma estrutura que as [Sales].[Orders]chaves estrangeiras, exceto. Outra coisa que pode ser importante mencionar a tabela [Sales].[OrdersCache]é um índice em cluster.

CREATE CLUSTERED INDEX idx_c_OrdersCache ON Sales.OrdersCache ( custid, empid )

Como esperado, quando estou tentando inserir um baixo volume de dados, LOOP JOIN funciona bem, fazendo busca de índice nas chaves estrangeiras.

Com altos volumes de dados, o MERGE JOIN é usado pelo otimizador de consultas como a maneira mais eficiente de manter a chave de exclusão na consulta.

E não há nada a ver com isso, exceto OPTION (LOOP JOIN) em nosso caso com chaves estrangeiras ou INNER LOOP JOIN no caso explícito de JOIN.

Abaixo está a consulta que estou tentando executar no meu ambiente:

INSERT Sales.Orders (
        custid, empid, shipperid, ... )
SELECT  custid, empid, 2, ...
FROM Sales.OrdersCache

Observando o plano, podemos ver que todas as três chaves estrangeiras foram validadas com MERGE JOIN. Não é uma maneira apropriada para mim, pois usa o INDEX SCAN com bloqueio de índice inteiro. MERGE JOIN durante a validação de chaves estrangeiras

Usar OPTION (LOOP JOIN) não é adequado, pois custa quase 15% a mais que MERGE JOIN (acho que a regressão será maior com o aumento do volume de dados).

Na instrução SELECT, você pode ver um valor único para o shipperidatributo para todo o conjunto inserido. Na minha opinião, deve haver uma maneira de acelerar a fase de validação do conjunto inserido, pelo menos para o atributo imutável. Algo como:

  • faça LOOP JOIN, MERGE JOIN, HASH JOIN se tivermos um subconjunto indefinido para validação de JOIN
  • se houver apenas um único valor explícito da coluna validada, faremos a validação apenas uma vez (INDEX SEEK).

Existe algum padrão comum para superar a situação acima usando estruturas de código, objetos DDL adicionais, etc?

Adicionado 20/07. Solução. O Query Optimizer já faz uma otimização de validação de 'chave única - chave estrangeira' usando MERGE JOIN. E faz apenas para a tabela Sales.Shippers, deixando LOOP JOIN para outras associações na consulta ao mesmo tempo. Como tenho algumas linhas na tabela pai, o Query Optimizer usa o algoritmo de junção Sort-merge e compara cada linha da tabela interna com a tabela pai apenas uma vez. Portanto, essa é a resposta da minha pergunta se existe algum mecanismo específico para processar efetivamente valores únicos em um conjunto durante a validação de chave única. Essa não é uma decisão tão perfeita, mas é assim que o SQL Server otimiza o caso.

A investigação de afetação de desempenho revelou que, no meu caso, a instrução de inserção MERGE JOIN e LOOP JOIN se tornou aproximadamente igual a 750 linhas inseridas simultaneamente com a seguinte superioridade de MERGE JOIN (no recurso de tempo da CPU). Portanto, usar OPTION (LOOP JOIN) é uma solução apropriada para o meu processo de negócios.

Oleg I
fonte

Respostas:

8

O uso de OPTION (LOOP JOIN) não é adequado, pois custa quase 15% a mais do que MERGE JOIN

As porcentagens de custo exibidas na saída do plano de demonstração são sempre estimativas do modelo do otimizador, mesmo em um plano pós-execução (real). Esses custos provavelmente não refletem o desempenho real do tempo de execução no seu hardware específico. A única maneira de ter certeza é testar as alternativas com sua carga de trabalho, medindo as métricas mais importantes para você (tempo decorrido, uso da CPU e assim por diante).

Na minha experiência, o otimizador alterna entre junção de loops e junção de mesclagem para validação de chave estrangeira muito cedo. Em todas as operações, exceto as maiores, descobri que os loops são preferíveis. E por "grande", quero dizer dezenas de milhões de linhas, pelo menos, certamente não os milhares que você indica nos comentários à sua pergunta.

Na minha opinião, deve haver uma maneira de acelerar a fase de validação do conjunto inserido, pelo menos para o atributo imutável.

Isso faz sentido, em princípio, mas hoje não existe essa lógica na lógica de construção do plano usada pela validação de chave estrangeira. A lógica atual é deliberadamente muito genérica e ortogonal à consulta mais ampla; otimizações específicas complicam os testes e aumentam a probabilidade de erros de borda.

Existe algum padrão comum para superar a situação acima usando estruturas de código, objetos DDL adicionais, etc?

Não que eu esteja ciente. O risco de conflito com a validação de chave estrangeira de junção de mesclagem é bem conhecido , com a solução alternativa mais usada sendo a OPTION (LOOP JOIN)dica. Não há como evitar bloqueios compartilhados durante a validação de chave estrangeira, porque estes são necessários para correção , mesmo nos níveis de isolamento de versão de linha.

Não há resposta geral melhor (do que a dica de junção de loop) se você quiser manter a capacidade de vários processos simultâneos de adicionar linhas às tabelas pai e filho transacionalmente. Mas, se você estiver disposto a serializar as modificações de dados (não as leituras), usar sp_getapplock é uma técnica confiável e simples.

Paul White 9
fonte