Que eu gosto de acompanhar blogs não é nenhum segredo, e recentemente vi esse post bem interessante que o Dennes Torres escreveu: http://cidadaocarioca.blogspot.com/2009/03/sql-performance-de-query.html.
É interessante notar que, conhecendo a estrutura do seu banco de dados e seu negócio, é possível melhorar as consultas através de otimizações diversas, sendo uma delas exatamente como o Dennes colocou. Uma otimização limpa e que pode melhorar bastante o resultado. 
Quando o SQL Server está montando o plano de execução ele também tenta sempre aplicar os filtros o quanto antes, além de deixar os joins com estimativas mais pesadas para o fim, tentando minimizar ao máximo a quantidade de registros que seguem entre uma operação e outra.
Então você pode estar se perguntando porque o LinqToSql ou o SQL Server não podem fazer a mesma otimização sugerida? Em defesa deles, escrevo um pequeno post. :-)
Não determinismo do resultado
Vamos ao primeiro ponto, que mostra claramente uma característica do banco de dados que muitos esquecem. Dependendo da maneira que o SQL Server monta o plano de execução, o resultado pode ser um pouco diferente, quer ver? Para nosso exemplo, vou inserir um novo registro com informações semelhantes ao quinto item da tabela orders (ordenado por data ASC).
select top 10 * from orders   
order by orderdate    
/*    
10248    VINET    5    
10249    TOMSP    6    
10250    HANAR    4    
10251    VICTE    3    
10252    SUPRD    4
*/     
INSERT INTO    Orders   
SELECT    
    'ALFKI',       
    3,
    OrderDate,    
    RequiredDate,    
    ShippedDate,    
    ShipVia,    
    Freight,    
    ShipName,    
    ShipAddress,    
    ShipCity,    
    ShipRegion,    
    ShipPostalCode,    
    ShipCountry    
FROM Orders    
WHERE OrderID = 10252     
Se executarmos as duas consultas descritas no post, o resultado é exatamente o mesmo, sendo que a segunda é mais otimizada. Veja abaixo, tudo certo…
-- SELECT gerado pelo LinqToSQL   
select top 5 * from orders    
inner join customers    
on orders.customerid=customers.customerid    
inner join employees    
on orders.employeeid=employees.employeeid    
order by orderdate    
/*    
10248    VINET    5    
10249    TOMSP    6    
10250    HANAR    4    
10251    VICTE    3    
10252    SUPRD    4    
*/ 
-- SELECT gerado na mão   
select * FROM    
(select top 5 * from orders    
order by orderdate) as X    
inner join customers    
on X.customerid=customers.customerid    
inner join employees    
on X.employeeid=employees.employeeid    
/*    
10248    VINET    5    
10249    TOMSP    6    
10250    HANAR    4    
10251    VICTE    3    
10252    SUPRD    4    
*/
Agora, supondo que o SQL Server resolvesse utilizar uma outra técnica de join para resolver essa consulta, qual seria o resultado? Uma distribuição dos dados diferente, novas estatísticas, falta de índices e outras razões poderiam causar isso. Para simularmos a situação, vou usar uma hint de merge join…
select top 5 * from orders   
inner merge join customers    
on orders.customerid=customers.customerid    
inner join employees    
on orders.employeeid=employees.employeeid    
order by orderdate    
/*    
10248    VINET    5    
10249    TOMSP    6    
10251    VICTE    3    
10250    HANAR    4    
11081    ALFKI    3
*/      
select * FROM   
(select top 5 * from orders    
order by orderdate) as X    
inner merge join customers    
on X.customerid=customers.customerid    
inner join employees    
on X.employeeid=employees.employeeid    
/*    
10250    HANAR    4    
10252    SUPRD    4    
10249    TOMSP    6    
10251    VICTE    3    
10248    VINET    5
*/     
O que temos? Resultados diferentes, pois no primeiro o top 5 é aplicado em cima de uma ordenação dos dados resultante de dois merge joins, enquanto no segundo o join já é feito sobre um conjunto de dados de cinco registros, que trouxe o VINET ao invés do ALFKI.   
Essa é uma característica do SQL Server e você deve estar ciente do que pode acontecer. Se fosse utilizado o TOP N WITH TIES, aí o resultado seria composto dos mesmos registros, em ordem diferente, mas você veria todo mundo.
  
Questão do campo NULL
Outro aspecto que merece atenção é o cenário onde existe um pedido feito pela Internet (suposição), que não possui empregado associado. Vamos ver o que acontece com as duas consultas?
select top 10 * from orders   
order by orderdate 
SELECT * FROM Orders   
WHERE OrderID = 10249 
-- Inserindo um registro de compra feito pela internet (sem empregado relacionado)   
INSERT INTO    Orders    
SELECT    
    'ALFKI',    
    null,       
    OrderDate,    
    RequiredDate,    
    ShippedDate,    
    ShipVia,    
    Freight,    
    ShipName,    
    ShipAddress,    
    ShipCity,    
    ShipRegion,    
    ShipPostalCode,    
    ShipCountry    
FROM Orders    
WHERE OrderID = 10249
-- SELECT gerado pelo LinqToSQL   
select top 5 OrderID, orders.CustomerID, orders.EmployeeID from orders    
inner join customers    
on orders.customerid=customers.customerid    
inner join employees    
on orders.employeeid=employees.employeeid    
order by orderdate    
/*    
10248    VINET    5    
10249    TOMSP    6    
10250    HANAR    4    
10251    VICTE    3    
10252    SUPRD    4    
*/ 
-- SELECT gerado na mão   
select OrderID, X.CustomerID, X.EmployeeID FROM    
(select top 5 * from orders    
order by orderdate) as X    
inner join customers    
on X.customerid=customers.customerid    
inner join employees    
on X.employeeid=employees.employeeid    
/*    
10248    VINET    5    
10249    TOMSP    6    
10250    HANAR    4    
10251    VICTE    3    
*/
Ops, esse caso é mais grave! Como na segunda consulta o TOP 5 é feito antes do JOIN e existe um registro que não se enquadra no resultado por ser um INNER JOIN, acabamos com somente quatro registros retornados.
  
Conclusão
É importante sabermos dos possíveis efeitos colaterais da otimização acima, por isso mencionei que é necessário conhecer o negócio para saber se é factível de ser implementado. O primeiro realmente é uma situação muito específica e talvez não traga impacto, mas o segundo pode trazer problemas maiores, por retornar um registro a menos.
Então no fim, o SQL Server e o LinqtoSQL precisam de manter a consulta original, pois é semanticamente incorreto fazerem esse tipo de otimização. Já o DBA não, se ele tiver controle do banco, pode usar esse tipo de estratégia bem destacada pelo Dennes, que ajuda MUITO o SQL Server.
Isso significa que você não deve usar uma tecnologia de mapeamento objeto/relacional? Besteira! Porém saiba que sempre existe um trade-off, mas no fim acredito que os ganhos de produtividade da equipe de desenvolvimento serão maiores do que um eventual ajuste de desempenho feito pelo seu time.
Pronto! O primeiro post sobre SQL Server no novo blog. :-)
[]s   
Luciano Caixeta Moreira    
luticm79@hotmail.com


Luti,
ResponderExcluirPrimeiramente, parabéns por manter vivo o Blog. Vc sabe da sua reputação que construiu sobre SQL Server e Frameworks de acesso a dados.
Gostaria de ressaltar, que além da produtividade, existe um aspecto de custo de manutenção. Modelos ORMs em projetos Enterprise tendem a serem "bons" repositórios de Business Logic. Como eu disse, "tendem", já que tudo depende de outras variáveis que já citou.
abs,