Sqlserver
 sql >> Base de Dados >  >> RDS >> Sqlserver

SQL Server Internals:Operadores problemáticos Pt. II – Hash


Isso faz parte de uma série de operadores problemáticos internos do SQL Server. Para ler a primeira postagem, clique aqui.

O SQL Server existe há mais de 30 anos e trabalho com o SQL Server há quase tanto tempo. Eu vi muitas mudanças ao longo dos anos (e décadas!) e versões deste produto incrível. Nestas postagens, compartilharei com você como vejo alguns dos recursos ou aspectos do SQL Server, às vezes com um pouco de perspectiva histórica.

Da última vez, falei sobre uma operação de varredura em um plano de consulta do SQL Server como um operador potencialmente problemático nos diagnósticos do SQL Server. Embora as varreduras sejam usadas com frequência apenas porque não há índice útil, há momentos em que a varredura é realmente uma escolha melhor do que uma operação de busca de índice.

Neste artigo, falarei sobre outra família de operadores que ocasionalmente é vista como problemática:hashing. Hashing é um algoritmo de processamento de dados muito conhecido que existe há muitas décadas. Eu estudei isso em minhas aulas de estruturas de dados quando eu estava estudando ciência da computação na Universidade. Se você quiser informações básicas sobre funções de hash e hash, confira este artigo na Wikipedia. Entretanto, o SQL Server não adicionou hashing ao seu repertório de opções de processamento de consultas até o SQL Server 7. , hashing usa uma função especial para mapear dados de tamanho arbitrário para dados de tamanho fixo. SQL usou hashing como uma técnica de busca para mapear cada página de um banco de dados de tamanho arbitrário para um buffer na memória, que é de tamanho fixo. , costumava haver uma opção para sp_configure chamados 'hash buckets', que permitiam controlar o número de buckets usados ​​para o hash de páginas de banco de dados em buffers de memória.)

O que é hash?


Hashing é uma técnica de busca que não requer que os dados sejam ordenados. O SQL Server pode usá-lo para operações JOIN, operações de agregação (DISTINCT ou GROUP BY) ou operações UNION. O que essas três operações têm em comum é que, durante a execução, o mecanismo de consulta está procurando valores correspondentes. Em um JOIN, queremos encontrar linhas em uma tabela (ou conjunto de linhas) que tenham valores correspondentes com linhas em outra. (E sim, estou ciente de junções que não estão comparando linhas com base na igualdade, mas essas não equijunções são irrelevantes para esta discussão.) Para GROUP BY, encontramos valores correspondentes para incluir no mesmo grupo e para UNION e DISTINCT, procuramos valores correspondentes para excluí-los. (Sim, eu sei que UNION ALL é uma exceção.)

Antes do SQL Server 7, a única maneira de essas operações encontrarem valores correspondentes facilmente era se os dados fossem classificados. Portanto, se não houvesse nenhum índice existente que mantivesse os dados em ordem de classificação, o plano de consulta adicionaria uma operação SORT ao plano. O hash organiza seus dados para uma pesquisa eficiente, colocando todas as linhas que têm o mesmo resultado da função de hash interna no mesmo 'hash bucket'.

Para obter uma explicação mais detalhada da operação hash JOIN do SQL Server, incluindo diagramas, dê uma olhada nesta postagem de blog do SQL Shack.

Depois que o hash se tornou uma opção, o SQL Server não descartou completamente a possibilidade de classificar os dados antes de ingressar ou agregar, mas apenas se tornou uma possibilidade para o otimizador considerar. Em geral, no entanto, se você estiver tentando unir, agregar ou executar UNION em dados não classificados, o otimizador geralmente escolherá uma operação de hash. Muitas pessoas assumem que um HASH JOIN (ou outra operação HASH) em um plano significa que você não tem índices apropriados e que deve construir índices apropriados para evitar a operação de hash.

Vejamos um exemplo. Primeiro, criarei duas tabelas não indexadas.

USE AdventureWorks2016 GO DROP TABLE IF EXISTS Details;

GO

SELECT * INTO Details FROM Sales.SalesOrderDetail;

GO

DROP TABLE IF EXISTS Headers;

GO

SELECT * INTO Headers FROM Sales.SalesOrderHeader;

GO

Now, I’ll join these two tables together and filter the rows in the Details table:

SELECT *

FROM Details d JOIN Headers h

ON d.SalesOrderID = h.SalesOrderID

WHERE SalesOrderDetailID < 100;



O Quest Spotlight Tuning Pack não parece indicar a junção de hash como um problema. Ele apenas destaca as duas varreduras de tabela.



As sugestões recomendam construir um índice em cada tabela que inclua cada coluna não chave como uma coluna INCLUÍDA. Raramente tomo essas recomendações (como mencionei no meu post anterior). Construirei apenas o índice nos Detalhes tabela, na coluna de junção e não tem nenhuma coluna incluída.

CREATE INDEX Header_index on Headers(SalesOrderID);

Uma vez que o índice é construído, o HASH JOIN desaparece. O índice classifica os dados nos Cabeçalhos table e permite que o SQL Server encontre as linhas correspondentes na tabela interna usando a sequência de classificação do índice. Agora, a parte mais cara do plano é a varredura na mesa externa (Detalhes ) que pode ser reduzido criando um índice no SalesOrderID coluna dessa tabela. Deixo como exercício para o leitor.

No entanto, um plano com um HASH JOIN nem sempre é uma coisa ruim. O operador alternativo (exceto em casos especiais) é um NESTED LOOPS JOIN, e geralmente é a escolha quando bons índices estão presentes. No entanto, uma operação de loops NESTED requer várias pesquisas na tabela interna. O pseudocódigo a seguir mostra o algoritmo de junção de loops aninhados:

for each row R1 in the outer table

     for each row R2 in the inner table

         if R1 joins with R2

             return (R1, R2)

Como o nome indica, um NESTED LOOP JOIN é executado como um loop aninhado. A pesquisa da tabela interna geralmente será realizada várias vezes, uma vez para cada linha qualificada na tabela externa. Mesmo que haja apenas uma pequena porcentagem das linhas qualificadas, se a tabela for muito grande (talvez na casa das centenas de milhões, bilhões ou linhas), haverá muitas linhas para serem lidas. Em um sistema que é limitado por E/S, esses milhões ou bilhões de leituras podem ser um gargalo real.

Um HASH JOIN, por outro lado, não faz várias leituras de nenhuma tabela. Ele lê a tabela externa uma vez para criar os buckets de hash e, em seguida, lê a tabela interna uma vez, verificando os buckets de hash para ver se há uma linha correspondente. Temos um limite superior de uma única passagem por cada tabela. Sim, há recursos de CPU necessários para calcular a função de hash e gerenciar o conteúdo dos buckets. Existem recursos de memória necessários para armazenar as informações com hash. Mas, se você tiver um sistema vinculado a E/S, poderá ter recursos de memória e CPU de sobra. O HASH JOIN pode ser uma escolha razoável para o otimizador nessas situações em que seus recursos de E/S são limitados e você está unindo tabelas muito grandes.

Aqui está o pseudocódigo para o algoritmo de junção de hash:

for each row R1 in the build table

  begin

     calculate hash value on R1 join key(s)

     insert R1 into the appropriate hash bucket

  end

for each row R2 in the probe table

  begin

     calculate hash value on R2 join key(s)

     for each row R1 in the corresponding hash bucket

         if R1 joins with R2

         output (R1, R2)

  end

Como mencionado anteriormente, o hashing também pode ser usado para operações de agregação (assim como UNION). Novamente, se houver um índice útil que já tenha os dados classificados, o agrupamento dos dados pode ser feito com muita eficiência. No entanto, também existem muitas situações em que o hash não é um operador ruim. Considere uma consulta como a seguinte, que agrupa os dados em Detalhes tabela (criada acima) pelo ProductID coluna. Há 121.317 linhas na tabela e apenas 266 ProductID diferentes valores.

SELECT ProductID, count(*)

FROM Details

GROUP BY ProductID;

GO

Usando operações de hash


Para usar o hash, o SQL Server precisa apenas criar e manter 266 buckets, o que não é muito. Na verdade, o Quest Spotlight Tuning Pack não indica que haja algum problema com essa consulta.

Sim, ele precisa fazer uma varredura na tabela, mas isso porque precisamos examinar todas as linhas da tabela e sabemos que as varreduras nem sempre são ruins. Um índice só ajudaria na pré-classificação dos dados, mas usar a agregação de hash para um número tão pequeno de grupos ainda fornecerá um desempenho razoável, mesmo sem nenhum índice útil disponível.

Assim como as varreduras de tabela, as operações de hash são frequentemente vistas como um operador "ruim" para se ter em um plano. Há casos em que você pode melhorar muito o desempenho adicionando índices úteis para remover as operações de hash, mas isso nem sempre é verdade. E se você está tentando limitar o número de índices em tabelas que são fortemente atualizadas, você deve estar ciente de que as operações de hash nem sempre são algo que precisa ser 'consertado', portanto, deixar a consulta para usar um hash pode ser uma coisa razoável façam. Além disso, para determinadas consultas em tabelas grandes executadas em sistemas com limite de E/S, o hash pode realmente fornecer melhor desempenho do que algoritmos alternativos devido ao número limitado de leituras que precisam ser executadas. A única maneira de saber com certeza é testar várias possibilidades em seu sistema, com suas consultas e seus dados.

Na postagem a seguir desta série, falarei sobre outros operadores problemáticos que podem aparecer em seus planos de consulta, então volte em breve!