quarta-feira, 25 de março de 2009

Consulta LinqToSQL, desempenho e considerações


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

terça-feira, 24 de março de 2009

Problema com Virtual Server 2005 VMRC


Máquina nova, problemas novos!
Estou aqui todo feliz indo rodar uma máquina virtual, quando eu descubro que não consigo usar o VMRC nem a console do VMRC+ para me conectar à minha VM. Mas qual a razão disso?

Acessei o site de administração e nas propriedades do VMRC notei que ele estava desabilitado, mesmo eu passando pela instalação padrão. Aproveitei, abri o cmd e executando “netstat –ab” descobrir que a porta 5900 já estava em uso pelo processo winvnc4.exe. Uma pesquisa rápida me indica que é um software da RealVNC.

Bingo! Só mudar a porta do VMRC para uma porta disponível (6000) que tudo volta a funcionar normalmente… Então fica a dica, problemas com o Virtual Machine Remote Cliente? Dê uma olhada para ver se a porta já não está sendo utilizada.

[]s
Luciano Caixeta Moreira
luticm79@hotmail.com

segunda-feira, 23 de março de 2009

Apresentação do Luti… De novo?


Oi Pessoal, tudo bem?
Achei cortês fazer uma pequena apresentação no meu novo blog, então para aqueles que já me conhecem, um pouco de paciência.

Me chamo Luciano Caixeta Moreira (vulgo, Luti) e a partir de hoje eu voltei a trabalhar como consultor independente, focado atualmente em SQL Server e .NET, além de estar trabalhando em um projeto específico (detalhes por vir), envolvendo a nova tendência de computação na nuvem.

Comecei a trabalhar com tecnologias Microsoft no ano de 2000 e desde então já ministrei diversos treinamentos MOC, participei de projetos ASP/VB6/.NET/SQL Server, trabalhei em algumas empresas (parceiras da Microsoft, governo, independente, etc.), escrevi vários posts em meus blos, fiz algumas provas, apareci em alguns eventos e bolei alguns artigos.

Nos últimos três anos e três meses trabalhei na Microsoft, como engenheiro de suporte premier e especialista em desenvolvimento (developer evangelist). Nesse período eu gerei algumas informações, que você pode achar interessante, no site http://blogs.msdn.com/luti.

Hoje sou um profissional certificado Microsoft e tenho algumas certificações, que podem ser vistas através do meu transcript compartilhado.

TranscriptID: 700199
Sharing code: MSCertbyLuti
http://www.microsoft.com/learning/mcp/transcripts

Este blog irá seguir a mesma linha do último, onde tocarei assuntos diversos, sempre tentando abordar de forma profunda e bem técnica os temas. Claro que, eventualmente, alguns comentários de carreira, opinião pessoal e informações aleatórias estarão disponíveis por aqui.

Não sei se por nostalgia ou para deixar bem claro o que esse blog irá abordar, ressuscitei uma antiga descrição (sintaticamente correta!)…

Espero que você aproveite essa minha nova jornada e que cresçamos juntos.
Meu e-mail é: luticm79@hotmail.com e você também pode me seguir no twitter: luticm.

[]s
Luciano Caixeta Moreira
luticm79@hotmail.com