HOSPEDAGEM DE SITES DISCO VIRTUAL CONSTRUTOR BLOG
LOCAMAIL EXCHANGE E-MAIL MARKETING
BANDA LARGA
PC PROTEGIDO
FLASH COMMUNICATION MX WINDOWS STREAMING MEDIA PODCAST
PABX VIRTUAL PORTAL DE VOZ
HOSPEDAGEM
COMÉRCIO
ELETRÔNICO
REGISTRO
DE DOMÍNIO
REVENDA SERVIÇOS
DEDICADOS
SERVIÇOS
DE E-MAIL
SERVIÇOS
DE VOZ
SEGURANÇA
ÁUDIO E
VÍDEO
ACESSO

Acelere sua aplicação SQL Server

Olá, pessoal! Este é meu primeiro post no nosso Blog, e acho que cabem aqui algumas explicações. Estou na Locaweb há mais ou menos cinco anos e meio, sempre dentro da área de tecnologia, e estive aqui durante boa parte do crescimento espantoso desta que é a melhor e maior empresa de serviços de internet do Brasil, sem nenhuma pretensão e nem falsa modéstia.

Neste tempo, aprendi uma coisa ou outra sobre as ferramentas que utilizamos e oferecemos, e acho que posso me arriscar a dar umas dicas técnicas de vez em quando. Aos incautos ou tecnoalérgicos, um aviso: este post não interessa para vocês. Aos escovadores de bits e bytes: usem sem moderação. Desculpem-me pela extensão do texto, divirtam-se e, por favor, mandem feedback para eu saber se estes temas interessam. Caso contrário, posso mudar de tema e contar uns podres da festa de fim de ano.
 
Uma situação comum que encontramos na LocaWeb é de clientes que se frustram com aplicações utilizando MS SQL Server. Eles acham que o desempenho do servidor está ruim, já que suas aplicações se mostram lentas e ocasionalmente exibem mensagens de erro na tela para seus clientes. Em praticamente todos os casos, ao analisar o que ocorre, verificamos que as práticas adotadas na aplicação não favorecem o desempenho do banco de dados, e isto acaba gerando uma cascata de problemas que culmina com mensagens de erro nada amigáveis.

Um dos problemas mais graves é o uso de cursores. “Como assim? Que outra alternativa há para percorrer dados no SQL?”, pergunta-se o leitor. Respondo com uma frase cujo autor não poderei citar aqui, por puro esquecimento do nome dele: “Com cursor, qualquer um faz. O desafio em SQL é fazer tudo sem cursor”. Se preferirem, ao invés de falar mal dos cursores, podemos falar que o problema é a falta de uso de variáveis-tabela, que são a alternativa que apresentarei em seguida.

Cursores são uma forma fácil de percorrer dados de um conjunto no SQL. Qualquer programador domina o conceito de “loop” e é capaz de iterar sobre um conjunto de dados até chegar no último elemento. O problema é que no SQL Server isso é muito ineficiente. Funciona bem para pequenos conjuntos de dados, mas mesmo nestes a técnica que vou propor traz ganhos no longo prazo.

Suponhamos que temos uma tabela de 200.000 registros e que tenhamos que percorrer alguns dados dela (algo como 10.000) fazendo um “update” em uma segunda tabela nos registros que tiverem o mesmo valor da primeira. O caminho natural seria fazer o seguinte:

declare
c
cursor local for
 select chave from tabela1 where campo = ‘valor’

declare @chave int

open c

fetch next from c into @chave

while (@@fetch_status = 0)
 begin
  update tabela2 set coluna = 1 where chave = @chave
  fetch next from c into @chave
 end

close c
deallocate c

Bem, este é o caminho do cursor. A alternativa é utilizar variáveis-tabela no lugar dos cursores. Estas variáveis funcionam exatamente como uma tabela, ou uma tabela temporária, só que são ordens de grandeza mais rápidas no uso do que um cursor. Elas têm muitos usos, e praticamente em 100% dos casos aceleram a aplicação. Abaixo coloco uma implementação do mesmo código que coloquei acima, mas usando uma variável-tabela no lugar do cursor.

declare
@t
table
 (chave int)

insert into @t select chave from tabela1 where campo = ‘valor’

declare @chave int

while (select count(*) from @t) > 0
 begin
  select top 1 @chave = T.chave from @t T
  update tabela2 set coluna = 1 where chave = @chave
  delete from @t where chave = @chave
 end

Este código, além de mais enxuto, é ordem de grandeza mais rápido. Quanto maior o conjunto de dados que o cursor contém, maior a diferença de desempenho.

Outro bom exemplo de aplicação de variáveis-tabela é quando tenho que fazer “joins” de muitas tabelas. É uma boa prática em SQL evitar “joins” de mais de 4 tabelas ao mesmo tempo. Mesmo com menos tabelas, ao fazer o “join” entre tabelas muito grandes a tendência é termos um desempenho muito ruim. A solução? Ao invés de fazer o SQL comparar todos os registros das tabelas (que, a grosso modo, é o que o “join” vai fazer - DBAs, não me matem, estou simplificando), você declara diversas variáveis-tabela e filtra as tabelas individualmente, fazendo os “joins” já com um conjunto menor de dados.

Vou dar um exemplo pra facilitar. Imagine que tenho 3 tabelas, cada uma com 100.000 linhas. Preciso fazer um “join” entre as 3, para pegar informações de cada uma delas. Imagine que no meu select vou retornar algo como 5  linhas apenas. Um código assim poderia ser:

select
 P.nome
from
 PESSOA P -- 100.000 registros
 inner join
 EMPRESA E -- 100.000 registros
 on P.chave_empresa = E.chave
 inner join
 NOTA_FISCAL N -- 100.000 registros
 on E.cnpj = N.cnpj
where
 N.num_nota in (1,2,3,4,5)

Só me interessam 5 registros da tabela NOTA_FISCAL neste exemplo, cada um relacionado a apenas 1 empresa e apenas 1 pessoa, ou seja, só vou retornar 5 registros no total. Para isso, estou fazendo um join de 3 tabelas muito grandes, o que é bem ineficiente.

Pois bem, vou dar um exemplo com variáveis-tabela onde o desempenho é muito melhor:

 declare @t1 table (cnpj NVARCHAR(20))
 
 insert into @t1 select cnpj from NOTA_FISCAL where num_nota in (1,2,3,4,5)

 declare @t2 table (chave BIGINT)

 insert into @t2 select E.chave from EMPRESA E inner join @T1 T on E.cnpj = T.cnpj

 /* ESTE SELECT RETORNARIA O MESMO RESULTADO DO SCRIPT QUE NAO USA VARIAVEIS-TABELA ACIMA */
 select P.nome from PESSOA P inner join @t2 T on P.chave_empresa = T.chave

Não aceitem minha palavra, façam o teste. O resultado é garantido e impressionante.

Espero ter dado aqui uma primeira contribuição para quem sente que seus scripts SQL às vezes estão com o freio de mão puxado. Quem gostou, avise-me para saber se devo escrever mais. Quem não gostou, me avise também, assim na próxima dou umas dicas de cinema!

10 comentários para “Acelere sua aplicação SQL Server”

  1. Vitor disse:

    Interessante a dica de utilizar tabelas. Vou fazer alguns testes, pois tenho enfrentado alguns problemas de desempenho.

  2. Augusto disse:

    Muito bom !!!

  3. Pedro disse:

    Legal…, aguardo mais dicas.

  4. Fernando Girotto disse:

    Bastante interessante o artigo, porém para o 2o exemplo, seguindo os conceitos da álgebra relacional, poderia ter sido feito, com o mesmo ganho de performance proposto, o seguinte:

    select
    P.nome
    from
    PESSOA P — 100.000 registros
    inner join
    EMPRESA E — 100.000 registros
    on P.chave_empresa = E.chave
    inner join
    NOTA_FISCAL N — 100.000 registros
    on (N.num_nota in (1,2,3,4,5)) and (E.cnpj = N.cnpj)

    Seria legal fazer uns testes comparando esses métodos numa segunda parte deste artigo. ;)

  5. José disse:

    Eu gostaria muito de ver uma desta dicas de cinema… tem uma base de dados muito grade, e sinceramente… está precisando de uma otimização nas buscas!!!

    esse foi legal… mas já utilizo variáveis tabela.!!!
    Valeu

  6. Eder Trindade disse:

    Bom dia Henrique Macedo,

    Reproduzimos o experimento acima em duas versões do SQL server. Na versão 2000 do Sql Server os resultados obtidos são similares aos seus, contudo na versão 2005 o comportamento é o oposto, a utilização do cursor fast_forward local se apresentou cerca de 12 vezes mais eficientes para um conjunto de 500.000 elementos.
    Quando houver mais novidades favor entrar em contato.

  7. Farofa disse:

    aeeeeeeeeeeee!!!

    sou de Olinda - Pernambuco
    sou estudando de Desenvolvimento de Software da UNIBRATEC (Universidade Brasileira de Tecnologia)

    eu tava com um problemão com esse negócio de “Inner Join” no SQL server… aí eu vim no seu site e esclareci tudo…

    quero que vc saiba que estou grandemente agradecido, viu?
    valeeeu!!!

    Deus te abençôe!!!! o/

  8. Rogério disse:

    Dica muito boa.
    Obrigado !!

  9. Walcir disse:

    legal !
    sobre a sua pergunta entre continuar com as dicas ou contar os podres da festa, fico com as dicas !
    abraço

  10. Vitor disse:

    Bah, meu os teus exemplos de update linha a linha é muito amador. Já ouviu falar em Hash Join? já ouviu falar em update from ?

    Deixa o banco trabalhar por ti.

    Abraço.

Deixar um comentário