Um dos algoritmos disponíveis para unir duas tabelas no SQL Server é o Nested Loops. A junção de loops aninhados usa uma entrada de junção como a tabela de entrada externa e outra como a tabela de entrada interna. O loop externo itera a tabela de entrada externa linha por linha. O loop interno, executado para cada linha externa, procura as linhas correspondentes na tabela de entrada interna.
Isso é chamado de junção de loops aninhados ingênuos.
Se você tiver um índice em condições de junção na tabela de entrada interna, não será necessário executar um loop interno para cada linha da tabela externa. Em vez disso, você pode passar o valor da tabela externa como um argumento de pesquisa e conectar todas as linhas retornadas da tabela interna às linhas da tabela externa.
A busca pela tabela interna é um acesso aleatório. O SQL Server, a partir da versão 2005, possui a otimização de classificação em lote (não confunda com o operador Classificar no Modo Lote para índices Columnstore). O objetivo da otimização é ordenar as chaves de pesquisa da tabela externa antes de obter dados da tabela interna. Assim, um acesso aleatório será seqüencial.
O plano de execução não exibe a operação de classificação em lote como um operador separado. Em vez disso, você pode ver a propriedade Optimized=true no operador Nested Loops. Se fosse possível ver a classificação em lote como um operador separado no plano, teria a seguinte aparência:
Neste pseudoplano, lemos dados do índice ix_CustomerID não clusterizado na ordem dessa chave de índice CustomerID. Em seguida, precisamos realizar Key Lookup no índice clusterizado, pois ix_CustomerID não é um índice de cobertura. Key Lookup é uma operação de pesquisa de chave de índice clusterizado – um acesso aleatório. Para torná-lo sequencial, o SQL Server pode executar a classificação em lote pela chave de índice clusterizado.
Para saber mais sobre a classificação em lote, consulte meu artigo Classificação em lote e loops aninhados.
Essa otimização fornece um grande impulso com um número suficiente de linhas. Você pode ler mais sobre os resultados dos testes no blog OPTIMIZED Nested Loops Joins, criado por Craig Freedman, um desenvolvedor de otimizador.
No entanto, se o número real de linhas for menor que o esperado, os custos adicionais da CPU para criar esse tipo podem ocultar seus benefícios, aumentar o consumo da CPU e reduzir seu desempenho.
Considere este exemplo específico:
use tempdb; go -- create a test table (SalesOrderID - clustered PK) create table dbo.SalesOrder(SalesOrderID int identity primary key, CustomerID int not null, SomeData char(200) not null); go -- add test data with n as (select top(1000000) rn = row_number() over(order by (select null)) from sys.all_columns c1,sys.all_columns c2) insert dbo.SalesOrder(CustomerID, SomeData) select rn%500000, str(rn,100) from n; -- create a clustered index create index ix_c on dbo.Salesorder(CustomerID); go -- the batch sort optimization is enabled by default (Nested Loops: Optimized = true) select * from dbo.SalesOrder with(index(ix_c)) where CustomerID < 1000; -- disable it with the DISABLE_OPTIMIZED_NESTED_LOOP hint (Nested Loops: Optimized = false) select * from dbo.SalesOrder with(index(ix_c)) where CustomerID < 1000 option(use hint('DISABLE_OPTIMIZED_NESTED_LOOP')); go
O resultado retornado:
Gostaria de chamar sua atenção para a ordem diferente das linhas na saída. O servidor retorna as linhas na ordem em que as processa, pois não especificamos explicitamente ORDER BY. No primeiro caso, lemos gradualmente do índice ix_c. No entanto, para otimizar as leituras aleatórias do índice clusterizado, filtramos as linhas pela chave de índice clustered SalesOrderID. No segundo caso, não há classificação, assim como as leituras são feitas na ordem da chave CustomerID do índice não clusterizado ix_c.
Diferença do sinalizador de rastreamento 2340
Apesar do fato de que a documentação especifica o sinalizador de rastreamento 2340 como um equivalente da dica DISABLE_OPTIMIZED_NESTED_LOOP, isso não é verdade.
Considere o exemplo a seguir onde usarei o comando UPDATE STATISTICS … WITH PAGECOUNT não documentado para enganar o otimizador dizendo que a tabela ocupa mais páginas do que realmente tem. Então, dê uma olhada nessas consultas:
- Sem dicas (adicionei MAXDOP para manter um plano simples);
- Com a dica DISABLE_OPTIMIZED_NESTED_LOOP;
- Com o sinalizador de rastreamento 2340.
-- create a huge table update statistics dbo.SalesOrder with pagecount = 100000; go set showplan_xml on; go -- 1. without hints select * from dbo.SalesOrder with(index(ix_c)) where CustomerID < 1000000 option(maxdop 1); -- 2. hint select * from dbo.SalesOrder with(index(ix_c)) where CustomerID < 1000000 option(use hint('DISABLE_OPTIMIZED_NESTED_LOOP'), maxdop 1); -- 3. trace flag select * from dbo.SalesOrder with(index(ix_c)) where CustomerID < 1000000 option(querytraceon 2340, maxdop 1); go set showplan_xml off; go
Com isso, temos os seguintes planos:
Loops aninhados tem a propriedade Optimized =false em todos os três planos. O fato é que ao aumentar a largura da tabela, também aumentamos o custo de acesso aos dados. Quando o custo é alto o suficiente, o SQL Server pode usar o operador de classificação explícito, em vez do operador de classificação em lote implícito. Podemos ver isso no primeiro plano de consulta.
Na segunda consulta, usamos a dica DISABLE_OPTIMIZED_NESTED_LOOP que desativa a classificação de lote implícita. No entanto, ele remove uma classificação explícita por um operador separado.
No terceiro plano, podemos ver que apesar de adicionar o sinalizador de rastreamento 2340, o operador de classificação existe.
Assim, a diferença da dica do sinalizador é a seguinte:uma dica desabilita a otimização transformando um acesso aleatório em serial dependendo do fato de o servidor implementá-lo com a classificação em lote implícita ou com o operador Sort separado.
P.S. Os planos podem depender do equipamento. Assim, se você não conseguir executar essas consultas, tente aumentar ou diminuir o tamanho da coluna SomeData char(200) na descrição da tabela dbo.SalesOrder.
O artigo foi traduzido pela equipe do Codingsight com a permissão do autor.