Neste artigo, vamos abordar o tópico de desempenho de variáveis de tabela. No SQL Server, podemos criar variáveis que funcionarão como tabelas completas. Talvez outros bancos de dados tenham os mesmos recursos, porém, usei tais variáveis apenas no MS SQL Server.
Assim, você pode escrever o seguinte:
declare @t as table (int value)
Aqui, declaramos a variável @t como uma tabela que conterá uma única coluna Value do tipo Integer. É possível criar tabelas mais complexas, porém, em nosso exemplo, uma coluna é suficiente para explorar a otimização.
Agora, podemos usar essa variável em nossas consultas. Podemos adicionar muitos dados a ela e realizar a recuperação de dados desta variável:
insert into @t select UserID from User or select * from @t
Percebi que as variáveis de tabela são usadas quando é necessário buscar dados para uma grande seleção. Por exemplo, há uma consulta no código que retorna os usuários do site. Agora, você coleta IDs de todos os usuários, adiciona-os à variável da tabela e pode pesquisar endereços para esses usuários. Talvez alguém pergunte por que não executamos uma consulta no banco de dados e recebemos tudo imediatamente? Eu tenho um exemplo simples.
Suponha que os usuários venham do serviço Web, enquanto seus endereços estão armazenados em seu banco de dados. Neste caso, não há saída. Recebemos vários IDs de usuário do serviço e, para evitar consultar o banco de dados, alguém decide que é mais fácil adicionar todos os IDs ao parâmetro de consulta como uma variável de tabela e a consulta ficará organizada:
select * from @t as users join Address a on a.UserID = users.UserID os
Tudo isso funciona corretamente. No código C#, você pode combinar rapidamente os resultados de ambas as matrizes de dados em um objeto usando LINQ. No entanto, o desempenho da consulta pode sofrer.
O fato é que as variáveis de tabela não foram projetadas para processar grandes volumes de dados. Se não me engano, o otimizador de consultas estará sempre usando o método de execução LOOP. Assim, para cada ID de @t, ocorrerá uma busca na tabela de endereços. Se houver 1.000 registros em @t, o servidor verificará o endereço 1.000 vezes.
Em termos de execução, devido ao número insano de varreduras, o servidor simplesmente cai tentando encontrar dados.
É muito mais eficaz varrer toda a tabela de endereços e encontrar todos os usuários de uma só vez. Este método é chamado MERGE. No entanto, o SQL Server o escolhe quando há muitos dados classificados. Nesse caso, o otimizador não sabe quanto e quais dados serão adicionados à variável, e se há ordenação porque tal variável não inclui índices.
Se houver poucos dados na variável da tabela e você não inserir milhares de linhas nela, está tudo bem. No entanto, se você gosta de usar essas variáveis e adicionar uma grande quantidade de dados a elas, deve continuar lendo.
Mesmo se você substituir a variável da tabela por SQL, isso acelerará bastante o desempenho da consulta:
select * from ( Select 10377 as UserID Union all Select 73736 Union all Select 7474748 …. ) as users join Address a on a.UserID = users.UserID
Pode haver milhares dessas instruções SELECT e o texto da consulta será enorme, mas será executado milhares de vezes mais rápido para uma grande quantidade de dados porque o SQL Server pode escolher um plano de execução eficaz.
Esta consulta não parece ótima. No entanto, seu plano de execução não pode ser armazenado em cache porque alterar apenas um ID também alterará todo o texto da consulta e os parâmetros não podem ser usados.
Acho que a Microsoft não esperava que os usuários usassem variáveis tabulares dessa maneira, mas há uma boa solução alternativa.
Existem várias maneiras de resolver este problema. No entanto, na minha opinião, o mais eficaz em termos de desempenho é adicionar OPTION (RECOMPILE) ao final da consulta:
select * from @t as users join Address a on a.UserID = users.UserID OPTION (RECOMPILE)
Esta opção é adicionada uma vez no final da consulta após ORDER BY. O objetivo dessa opção é fazer com que o SQL Server recompile a consulta em cada execução.
Se medirmos o desempenho da consulta depois disso, o tempo provavelmente será reduzido para realizar a pesquisa. Com grandes dados, a melhoria de desempenho pode ser significativa, de dezenas de minutos a segundos. Agora, o servidor compila seu código antes de executar cada consulta e não usa o plano de execução do cache, mas gera um novo, dependendo da quantidade de dados na variável, e isso geralmente ajuda muito.
A desvantagem é que o plano de execução não é armazenado e o servidor precisa compilar a consulta e procurar um plano de execução eficaz a cada vez. No entanto, não vi as consultas em que esse processo levou mais de 100 ms.
É uma má ideia usar variáveis de tabela? Não não é. Apenas lembre-se de que eles não foram criados para grandes dados. Às vezes, é melhor criar uma tabela temporária, se houver muitos dados, e inserir dados nessa tabela, ou até mesmo criar um índice na hora. Eu tive que fazer isso com relatórios, embora apenas uma vez. Naquela época, reduzi o tempo para gerar um relatório de 3 horas para 20 minutos.
Eu prefiro usar uma grande consulta em vez de dividi-la em várias consultas e armazenar os resultados em variáveis. Permita que o SQL Server ajuste o desempenho de uma grande consulta e isso não o decepcionará. Observe que você deve recorrer a variáveis de tabela apenas em casos extremos, quando realmente vê seus benefícios.