Arquivo
Join reordering e BushyPlans
Galera acabei de escrever um artigo pro blog da Sr.Nimbus, falei sobre Bushy Plans… Assunto interessante para os geeks de plantão.
http://www.srnimbus.com.br/join-reordering-e-bushyplans-2/
Aproveita e cadastra o RSS do blog da Sr.Nimbus, http://srnimbus.dominiotemporario.com/category/blog/feed/
Abs.
PASS 24 Hours–Amanhã (27/11)
Copia descarada do post do Ivan
http://ivanglima.com/esta-chegando-24-horas-de-pass-portugues/
Não perca, amanhã tem minha sessão sobre T-SQL avançado… amanha as 09:00.
Abs.
———————————————————————————————————
Pessoal, esse mês teremos o primeiro evento 24 Hours de PASS em Português em conjunto com os nossos amigos de Portugal!
24 Horas de PASS – Português
Para quem não conhece, o 24HOP é um evento gratuito e você pode acompanhar tudo on-line. Com duas rodadas de 12h non-stop de SQL Server entre os dias 27 e 28 desse mês, tendo início às 11h de Brasília (ou 13h de Portugal, se você estiver do lado de lá do Atlântico.)
As comunidades Brasileira e Portuguesa prepararam diversas apresentações, e como sempre a Sr. Nimbus não poderia deixar de participar. Nós vamos marcar presença com algumas palestras, sendo que a primeiríssima palestra do evento será nossa! Mais precisamente dos nossos amigos e colegas Fabiano Amorim (Twitter | Blog) e Gilberto Uchôa (Blog):
Sessão 01 – 11:00 (13:00 GMT) "T-SQL Expert – Escrevendo códigos “like a Boss”" Fabiano Amorim & Gilberto Uchôa
A agenda com todas as palestras pode ser encontrada no site do PASS. Dá só uma olhada no pessoal que estará apresentando!
Então não deixe de assistir e aproveite pra aprender bastante SQL Server com os melhores profissionais da área! ![]()
Demonstração – Execplans on demand
Treinamento on-demand de planos de execução e otimização no SQL Server.
Galera depois de váras semanas trabalhando na produção do nosso treinamento on-demand, estou MUITO feliz com o resultado… o treinamento está no ar e gravei um total de 5 horas 51 minutos 34 segundos de vídeos falando sobre otimização no SQL Server.
Eu editei um “teaser“ com 10 mins de “melhores momentos” do treinamento… tem bastante coisa legal. Eu tenho certeza de que qualquer geek ficará muito feliz em ver o que acontece por dentro de um HD ao rodar uma consulta no SQL Server
… pois é, eu abri um só pra te mostrar isso!
Abaixo você pode conferir o vídeo:
Demonstração do treinamento OnDemand – ExecPlans Parte I from Sr. Nimbus on Vimeo.
O Luciano também divulgou um teaser com o treinamento de Transaction Log… terminar o video com WinDbg foi pra fechar com chave de ouro
SENSACIONAL!
Veja o video dele aqui:
Sr. Nimbus Demonstração do treinamento OnDemand – Transaction Log from Sr. Nimbus on Vimeo.
Se interessou? Basta comprar o acesso ao treinamento, esperar receber o acesso aos vídeos e ser feliz
. É sério, R$150,00 reais por isso, tá muito barato…
Desconto treinamento “de graça” termina amanhã (31-10)–Corre!
Galera, um lembrete… na boa, não é porque trabalho na Sr.Nimbus que estou falando isso não… mas se liga, porque o descontão pra comprar o treinamento on-demand da Sr.Nimbus quase de graça só vai até amanhã… depois do dia 31-10 o valor sobe de R$30,00 para R$150,00.
Mais informações aqui no meu blog mesmo:
https://blogfabiano.com/2012/10/18/treinamentos-on-demand-da-sr-nimbus-quase-de-graa/
Se você ainda não comprou, voa que ainda da tempo…
Abs.
Fabiano Amorim
Olá pessoal,
Mais uma vez venho trazer boas noticias a todos vocês. Depois de varias reuniões, idas e vindas é que com uma grande satisfação e com o apoio da Microsoft e Dell e também das comunidades técnicas SQL Server RS e MCITP SC promovem mais um evento repleto de SQL Server. Como o titulo sugere o nome do evento será: SQL Internal Ops Conference.
O evento acontecerá em Porto Alegre – Rio Grande do Sul na Universidade PUC e terá presença de grandes nomes da comunidade SQL Server Brasil assim como também membros do time de produto de SQL Server da Microsoft.
Sem mais delongas, quem quiser saber mais sobre o evento como local, data, hora, inscrição, submissão de palestras, vocês podem acessar o site do SQL Internal Ops Conference. Vale lembrar também que também estamos aceitando a submissão de palestras, então se você tem algum conteudo que…
Ver o post original 12 mais palavras
Treinamentos On-Demand da Sr.Nimbus, quase de graça…
Update*:
Aaa eu não poderia esquecer de mencionar que esse treinametno de planos de execução é só a parte 1
… ou seja, vem MUITA coisa por ai.
Galera noticia quente…
Nós na Sr.Nimbus estamos oferecendo treinamentos on-demand de SQL Server. A ideia é uma exploração profunda de um assunto, somada à facilidade de realizar o treinamento remotamente, no horário e no ritmo que for melhor para você. Nossos dois primeiros on-demand são Transaction Log Internals e Planos de Execução no SQL Server, e como promoção de lançamento, os treinamentos adquiridos até 31/10 saem por **20%** do valor, é sério tá praticamente de graça.
Clique no link abaixo para mais informações:
http://www.srnimbus.com.br/treinamentos-on-demand-na-sr-nimbus/
Eu estou gravando o treinamento de planos de execução, a ementa é a seguinte:
Query optimizer: como funciona e como analisar um plano de execução
Este treinamento apresenta ao profissional que utiliza o SQL Server a análise de planos de execução com foco em otimização de consultas T-SQL. Entenda como um plano de execução funciona, como ele é criado, e como influenciar o plano é a chave para otimização de códigos T-SQL. O treinamento é o início de uma série de módulos que irão apresentar a fundo como ler e influenciar um plano de execução.
Após uma apresentação relacionada ao otimizador de consultas, iremos explorar como os planos são executados analisando os operadores dos planos e todas as suas características, sempre trabalhando com ênfase em performance.
Se você quer mergulhar no mundo do otimizador de consultas, este é o lugar certo! Espere exemplos deep dive, e horas de diversão cheias de comandos avançados e não documentados.
Otimizando consultas analisando operadores
· Key Lookup e RID Lookup
o O que é?
o Como melhorar consultas com Key Lookup utilizando clausula Include
o Diferença entre Key Lookup e Rid Lookup
o Cuidados com “missing indexes” (dicas do SQL Server)
o Até quando vale a pena fazer um lookup?
o O que é prefetch? Devo me preocupar com ele?
o Nested Loops Optimized, o que é isso?
· Melhorando consultas com o operador Sort
o Otimizando consultas com operadores de SORT
o Ordenação na aplicação ou no banco de dados?
o Monitorando Sort Warnings
o Entendendo memory grant
o xEvent – sort_memory_grant_adjustment
o In-Memory sort versus regular-sort
§ Single pass spill e Multiple Pass spill
o Analisando opções para evitar sort_warnings
· Melhorando consultas com operador Merge Join
o Entendendo o algoritmo de Merge Join
o Evitando Sort Merge Join
o Otimizando Merge Joins em disco
o Cenários propensos a otimização do Merge Join
Novo Desafio SQL Server – Performance 2
O desafio de hoje consiste em melhorar a performance da seguinte consulta:
CHECKPOINT
DBCC DROPCLEANBUFFERS
GO
SET STATISTICS IO ON
SELECT Pedidos.NumeroPedido,
Pedidos.DT_Pedido,
Pedidos.Valor,
SUM(ItensPed.Qtde) AS TotalItens
FROM Pedidos
INNER JOIN ItensPed
ON Pedidos.NumeroPedido = ItensPed.NumeroPedido
INNER JOIN Produtos
ON ItensPed.ID_Produto = Produtos.ID_Produto
WHERE Pedidos.DT_Pedido BETWEEN ‘20121010’ AND ‘20121020’
AND Produtos.Descricao like ‘%cicle%’
GROUP BY Pedidos.NumeroPedido,
Pedidos.DT_Pedido,
Pedidos.Valor
OPTION (RECOMPILE, MAXDOP 1)
SET STATISTICS IO OFF
Originalmente a consulta faz as seguintes leituras.
STATISTICS IO:
Table ‘ItensPed’. Scan count 0, logical reads 19840, physical reads 1, read-ahead reads 11900
Table ‘Pedidos’. Scan count 1, logical reads 230, physical reads 1, read-ahead reads 221
Table ‘Worktable’. Scan count 1, logical reads 3818, physical reads 0, read-ahead reads 0
Table ‘Produtos’. Scan count 1, logical reads 2283, physical reads 3, read-ahead reads 2270
Profiler:
Vamos focar em melhorar o tempo, mas principalmente o número de leituras de páginas que são executadas nas tabelas.
Eu consegui fechar com os seguintes números:
STATISTICS IO:
Table ‘…’. Scan count 3, logical reads 9, physical reads 4, read-ahead reads 0
Table ‘…’. Scan count 1, logical reads 9, physical reads 1, read-ahead reads 7
Table ‘…’. Scan count 1, logical reads 3, physical reads 3, read-ahead reads 0
Table ‘Worktable’. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0
Table ‘ItensPed’. Scan count 1, logical reads 652, physical reads 2, read-ahead reads 654
Table ‘Produtos’. Scan count 0, logical reads 6, physical reads 3, read-ahead reads 0
Table ‘Pedidos’. Scan count 1, logical reads 8, physical reads 1, read-ahead reads 6
Profiler:
/*
Regras
* Não pode mudar índice cluster
* Não pode mudar schema das tabelas existentes (fks, datatype, nullable…)
* Pode criar quantos índices forem necessários (exceto cluster)
* Vale usar view indexadas
* Vale criar novos objetos (procedures, views, triggers, functions…)
* Vale reescrever a consulta
* Pelo menos 5 caracteres obrigatoriamente são utilizados para fazer o filtro pela descrição do produto
* Valores utilizados como filtro não são fixos… ou seja, tem que funcionar para qualquer valor que for solicitado
*/
Segue script para criar e popular as tabelas:
USE tempdb
GO
IF OBJECT_ID(‘Produtos’) IS NOT NULL
DROP TABLE Produtos
GO
CREATE TABLE Produtos (ID_Produto Int IDENTITY(1,1) PRIMARY KEY,
Descricao VarChar(400),
Col1 VarChar(400) DEFAULT NEWID())
GO
INSERT INTO Produtos (Descricao)
VALUES (‘Bicicleta’), (‘Carro’), (‘Motocicleta’), (‘Trator’)
GO
;WITH CTE_1
AS
(
SELECT TOP 200000 ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS rn
FROM sysobjects a, sysobjects b, sysobjects c, sysobjects d
)
INSERT INTO Produtos (Descricao)
SELECT REPLACE(NEWID(), ‘-‘, ‘ ‘)
FROM CTE_1
GO
–SELECT * FROM Produtos
–GO
IF OBJECT_ID(‘ItensPed’) IS NOT NULL
DROP TABLE ItensPed
GO
IF OBJECT_ID(‘Pedidos’) IS NOT NULL
DROP TABLE Pedidos
GO
CREATE TABLE Pedidos (NumeroPedido VarChar(80) PRIMARY KEY,
DT_Pedido Date,
ID_Cliente Int,
Valor Float)
GO
;WITH CTE_1
AS
(
SELECT TOP 50000 ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS rn
FROM sysobjects a, sysobjects b, sysobjects c, sysobjects d
)
INSERT INTO Pedidos(NumeroPedido, DT_Pedido, ID_Cliente, Valor)
SELECT ‘Ped-‘ + CONVERT(VarChar, rn), — Composto por "Ped + NumeroSequencial"
GetDate() – ABS(CheckSum(NEWID()) / 10000000),
ABS(CHECKSUM(NEWID())) / 1000000 AS ID_Cliente,
ABS(CHECKSUM(NEWID())) / 100000. AS Valor
FROM CTE_1
GO
–SELECT * FROM Pedidos
–GO
IF OBJECT_ID(‘ItensPed’) IS NOT NULL
DROP TABLE ItensPed
GO
CREATE TABLE ItensPed (NumeroPedido VarChar(80) FOREIGN KEY REFERENCES Pedidos(NumeroPedido),
DT_Entrega Date,
ID_Produto Int,
Qtde SmallInt,
PRIMARY KEY(NumeroPedido, ID_Produto) WITH(IGNORE_DUP_KEY=ON))
GO
INSERT INTO ItensPed(NumeroPedido, DT_Entrega, ID_Produto, Qtde)
SELECT NumeroPedido,
DATEADD(d, ABS(CheckSum(NEWID()) / 100000000), DT_Pedido),
ABS(CHECKSUM(NEWID())) / 1000000 + 1 AS ID_Produto,
ABS(CHECKSUM(NEWID())) / 1000000. AS Qtde
FROM Pedidos
GO 100
–SELECT * FROM ItensPed
–GO
Olhos atentos já viram que dei uma dica… Divirta-se e boa sorte!
Abs.
Reposta–Desafio Performance
Galera vamos lá, resposta para o desafio de performance que postei a alguns dias atrás.
Primeiramente eu quero agradecer a todos que participaram e me enviaram sugestões de códigos com melhorias para a consulta.
· Alberto Lima – Microsoft Brasil
· Rodrigo Souza (twitter) – Microsoft Brasil
· Omid Afzalalghom – Itaú BBA
· Thiago Alencar (twitter|blog) – DBA Saraiva
· Alex Rosa (blog) – IBM
· Alan Victor – CNP
· Evandro Camara – Sight Business Intelligence
Se eu me esqueci de você é só me xingar aqui com um comentário que atualizo o post J… Valeu mesmo por terem participado. Se você tentou, mas não me mandou sua solução… mande, mesmo que só para dizer, ei, eu fiz assim… o que acha?
Como divulgado no post original, o Evandro conseguiu uma solução sensacional usando uma view indexada para evitar o acesso à tabela com milhões de linhas. O código da view é o seguinte:
USE tempdb
GO
IF OBJECT_ID(‘vw_OrdersBig_TestPerf’) IS NOT NULL
DROP VIEW vw_OrdersBig_TestPerf
GO
CREATE VIEW vw_OrdersBig_TestPerf
WITH SCHEMABINDING
AS
SELECT CustomerID,
CountCol,
OrderDate,
SUM_Value = SUM(Value),
COUNT_BIG = COUNT_BIG(*)
FROM dbo.OrdersBig
GROUP BY CustomerID,
CountCol,
OrderDate
GO
CREATE UNIQUE CLUSTERED INDEX PK_vw_OrdersBig_TestPerf ON dbo.vw_OrdersBig_TestPerf (CustomerID, CountCol, OrderDate)
GO
Existem duas cosias bem interessantes na solução do Evandro, primeiro, eu já falei várias vezes que views indexadas são espetaculares para melhoria de performance de consultas, principalmente para consultas com agregações, quem já fez treinamento comigo sabe disso. Sim ela tem um custo, e muitas vezes, alto, mas dependendo do cenário, ela pode sim ser utilizada. Outro fato interessante é que o otimizador de consultas trabalha muito bem para identificar que existe uma view indexada que contém os dados desejados pela consulta… como assim?… Olha só, mesmo rodando a query original, o SQL Server gera o seguinte plano de execução:
Como podemos observar no plano acima, mesmo não fazendo select na view o SQL Server utiliza o índice da view indexada para evitar acessar a tabela com 5 milhões de linhas, com isso ele lê bem menos dados, sendo mais preciso ele lê uma “tabela” (view indexada) com apenas 975 páginas contra 35921 páginas da tabela OrdersBig. Só com isso já temos um ganho muito grande.
Novamente, Parabéns Evandro.
O ganhador da solução sem utilizar view indexada, foi o Alberto Lima, mas antes de falar sobre a solução dele, vamos analisar a minha solução e ver alguns pontos importantes sobre a consulta original:
SELECT a.CustomerID,
a.CountCol,
CASE a.CountCol
WHEN ‘Count’ THEN COUNT(1)
WHEN ‘CountDistinct’ THEN COUNT(DISTINCT a.OrderDate)
WHEN ‘CountDistinct_1’ THEN COUNT(DISTINCT 1)
ELSE NULL
END AS Cnt,
CASE (SELECT AVG(b.Value)
FROM OrdersBig b
WHERE b.CustomerID = a.CustomerID)
WHEN 1000 THEN ‘Média = 1 mil’
WHEN 2000 THEN ‘Média = 2 mil’
WHEN 3000 THEN ‘Média = 3 mil’
WHEN 4000 THEN ‘Média = 4 mil’
WHEN 5000 THEN ‘Média = 5 mil’
ELSE ‘Não é número exato’
END AS Sts
FROM OrdersBig AS a
GROUP BY a.CustomerID, a.CountCol
ORDER BY a.CustomerID
OPTION (MAXDOP 1)
Existem 3 problemas na consulta acima.
1. A clausula COUNT(DISTINCT 1) não faz nenhum sentido
2. O CASE com a subquery faz com que o SQL execute a subquery para cada valor analisado no case.
3. A clausula COUNT(DISTINCT a.OrderDate) é o grande problema de performance da consulta
O plano pode ser dividido em duas partes, primeiro para calcular o COUNT + COUNT(DISTINCT)
E depois a parte de CASE +SubQuery:
Vamos resolver os problemas por partes, primeiro eliminando um passo do plano trocando o “COUNT(DISTINCT 1)” por “1”. Concordam que “COUNT(DISTINCT 1)” é sempre igual a “1”? O mais irritante é que o otimizador de consultas não identifica isso sozinho.
Outra alteração que podemos fazer é em relação ao CASE + SubQuery, uma forma muito simples de resolver este problema é não usar a subquery como expressão para o CASE, ou seja, trocamos isso:
…
END AS Cnt,
CASE (SELECT AVG(b.Value)
FROM OrdersBig b
WHERE b.CustomerID = a.CustomerID)
WHEN 1000 THEN ‘Média = 1 mil’
WHEN 2000 THEN ‘Média = 2 mil’
WHEN 3000 THEN ‘Média = 3 mil’
WHEN 4000 THEN ‘Média = 4 mil’
WHEN 5000 THEN ‘Média = 5 mil’
ELSE ‘Não é número exato’
END AS Sts
FROM OrdersBig AS a
…
Por isso:
…
END AS Cnt,
(SELECT CASE AVG(b.Value)
WHEN 1000 THEN ‘Média = 1 mil’
WHEN 2000 THEN ‘Média = 2 mil’
WHEN 3000 THEN ‘Média = 3 mil’
WHEN 4000 THEN ‘Média = 4 mil’
WHEN 5000 THEN ‘Média = 5 mil’
ELSE ‘Não é número exato’
END AS Sts
FROM OrdersBig b
WHERE b.CustomerID = a.CustomerID) AS Sts
FROM OrdersBig AS a
…
Utilizando o AVG(b.Value) como expressão para o CASE evitamos o problema de execução da subquery para cada valor na lista do CASE.
Após efetuar estas duas alterações temos o seguinte plano de execução:
SELECT a.CustomerID,
a.CountCol,
CASE a.CountCol
WHEN ‘Count’ THEN COUNT(1)
WHEN ‘CountDistinct’ THEN COUNT(DISTINCT a.OrderDate)
WHEN ‘CountDistinct_1’ THEN 1
ELSE NULL
END AS Cnt,
(SELECT CASE AVG(b.Value)
WHEN 1000 THEN ‘Média = 1 mil’
WHEN 2000 THEN ‘Média = 2 mil’
WHEN 3000 THEN ‘Média = 3 mil’
WHEN 4000 THEN ‘Média = 4 mil’
WHEN 5000 THEN ‘Média = 5 mil’
ELSE ‘Não é número exato’
END AS Sts
FROM OrdersBig b
WHERE b.CustomerID = a.CustomerID) AS Sts
FROM OrdersBig a
GROUP BY a.CustomerID, a.CountCol
ORDER BY a.CustomerID
OPTION (MAXDOP 1)
Uau, já ficou MUITO mais simples não é? O problema agora é que estou acessando fazendo um scan na tabela OrdersBig 3 vezes.
Outro problema que nos resta, é o “COUNT (DISTINCT OrderDate)”, para resolver este problema eu mudei um pouco a forma de solicitar esta informação, ou invés de usar o COUNT DISTINCT eu usei a ROW_NUMBER particionando a janela por CustomerID e CountCol e depois contei a quantidade de valores igual a 1.
Vamos criar um cenário mais simples para entender o conceito:
IF OBJECT_ID(‘Tab1’) IS NOT NULL
DROP TABLE Tab1
GO
CREATE TABLE Tab1 (Col1 Int, Col2 Int)
GO
INSERT INTO Tab1 VALUES(1, 1), (1, 1), (1, 1), (2, 1), (2, 1), (3, 1), (3, 1)
GO
SELECT Col1,
COUNT(Col2) AS "Count", — Palavra reservada
COUNT(DISTINCT Col2) AS CountDistict
FROM Tab1
GROUP BY Col1
GO
Vamos analisar o conceito que mencionei acima passo a passo, primeiro vamos gerar o ROW_NUMBER particionando por Col1 com base na ordem de Col2.
SELECT *,
ROW_NUMBER() OVER(PARTITION BY Col1 ORDER BY Col2) AS rn
FROM Tab1
Agora se eu contar a quantidade de ocorrências de “1” agrupando por Col1 terei o resultado esperado concordam? … Para fazer isso vamos colocar a consulta em uma CTE e fazer um CASE para retornar apenas os valores igual a 1.
WITH CTE_1
AS
(
SELECT *,
CASE
WHEN ROW_NUMBER() OVER(PARTITION BY Col1 ORDER BY Col2) = 1 THEN 1
ELSE NULL
END AS rn
FROM Tab1
)
SELECT *
FROM CTE_1
Agora podemos simplesmente fazer um COUNT na coluna RN que os valores NULL serão ignorados e teremos o mesmo resultado que o COUNT DISTINCT. Vejamos:
WITH CTE_1
AS
(
SELECT *,
CASE
WHEN ROW_NUMBER() OVER(PARTITION BY Col1 ORDER BY Col2) = 1 THEN 1
ELSE NULL
END AS rn
FROM Tab1
)
SELECT Col1,
COUNT(Col2) AS "Count", — Palavra reservada
COUNT(rn) AS CountDistict
FROM CTE_1
GROUP BY Col1
Agora conseguimos resolver a consulta com apenas um scan na tabela, porém agora temos um novo problema, o SORT por Col1 (Clausula PARTITION BY) + Col2 (Clausula ORDER BY), mas esse é fácil de resolver certo? … Basta criar um índice por Col1 e Col2. Bora fazer isso.
CREATE INDEX ix1 ON Tab1 (Col1, Col2)
Agora temos o seguinte plano:
Nice and clean!
Voltando para nosso cenário, a consulta ficaria assim:
— Criando o índice para evitar o Sort e para cobrir a SubQuery
CREATE INDEX ix1 ON OrdersBig (CustomerID, CountCol, OrderDate) INCLUDE(Value) WITH(DATA_COMPRESSION=PAGE)
GO
;WITH CTE_1
AS
(
SELECT CustomerID,
CountCol,
OrderDate,
CASE
WHEN ROW_NUMBER() OVER(PARTITION BY CustomerID, CountCol, OrderDate ORDER BY OrderDate) = 1 THEN 1
ELSE NULL
END AS DistinctCnt
FROM OrdersBig
)
SELECT CustomerID,
CountCol,
CASE CountCol
WHEN ‘Count’ THEN COUNT(1)
WHEN ‘CountDistinct’ THEN COUNT(DistinctCnt)
WHEN ‘CountDistinct_1’ THEN 1
ELSE NULL
END AS Cnt,
(SELECT CASE AVG(b.Value)
WHEN 1000 THEN ‘Média = 1 mil’
WHEN 2000 THEN ‘Média = 2 mil’
WHEN 3000 THEN ‘Média = 3 mil’
WHEN 4000 THEN ‘Média = 4 mil’
WHEN 5000 THEN ‘Média = 5 mil’
ELSE ‘Não é número exato’
END AS Sts
FROM OrdersBig b
WHERE b.CustomerID = CTE_1.CustomerID) AS Sts
FROM CTE_1
GROUP BY CustomerID, CountCol
ORDER BY CustomerID
OPTION (MAXDOP 1)
Temos o seguinte plano:
Olhos atentos devem ter reparado que eu criei o índice utilizando a clausula “DATA_COMPRESSION = PAGE”, isso faz muito diferença na leitura do índice, já que terei que varrer a tabela ;-).
Outro ponto importantíssimo em relação a performance desta consulta é que o mesmo índice esta sendo utilizado duas vezes, primeiro um Index Scan é realizado já que esta é a primeira vez que os dados estão sendo lidos essa será uma leitura física, quando o Index Seek for realizado os dados já estarão em Cache gerando leituras lógicas. Isso significa que ainda que eu crie outro índice menor (por CustomerID com INCLUDE de Value), a performance da consulta será pior, pois o seek neste novo índice geraria leituras físicas.
Na minha máquina a consulta acima faz o seguinte uso de recursos:
Pra deixar o desafio mais interessante, lanço uma pergunta. Será que da pra fazer isso tudo, com apenas UMA leitura na tabela OrdersBig?
Dá, mas o SQL Server infelizmente ainda não é bem esperto na criação deste plano… Eu poderia evitar a SubQuery do AVG e escrever a consulta assim:
;WITH CTE_1
AS
(
SELECT CustomerID,
CountCol,
OrderDate,
AVG(Value) OVER(PARTITION BY CustomerID) AS Media,
CASE
WHEN ROW_NUMBER() OVER(PARTITION BY CustomerID, CountCol, OrderDate ORDER BY OrderDate) = 1 THEN 1
ELSE NULL
END AS DistinctCnt
FROM OrdersBig
)
SELECT CustomerID,
CountCol,
CASE CountCol
WHEN ‘Count’ THEN COUNT(1)
WHEN ‘CountDistinct’ THEN COUNT(DistinctCnt)
WHEN ‘CountDistinct_1’ THEN 1
ELSE NULL
END AS Cnt,
CASE Media
WHEN 1000 THEN ‘Média = 1 mil’
WHEN 2000 THEN ‘Média = 2 mil’
WHEN 3000 THEN ‘Média = 3 mil’
WHEN 4000 THEN ‘Média = 4 mil’
WHEN 5000 THEN ‘Média = 5 mil’
ELSE ‘Não é número exato’
END AS Sts
FROM CTE_1
GROUP BY CustomerID, CountCol, Media
ORDER BY CustomerID
OPTION (MAXDOP 1)
Infelizmente a operação de SORT é totalmente desnecessária, mas o SQL Server continua gerando o SORT… isso é um BUG que eu já reclamei, e que foi fechado pela Micosoft como “By Design”… anyway, não vou entrar no mérito aqui se isso é bug ou não é… o que espero é que em novas versões do produto a MS de mais atenção para esse tipo de funcionalidade.
A solução ganhadora (do Alberto Lima), é bem interessante porque usa índices filtrados, segue o script completo:
CREATE TABLE dbo.Tmp_OrdersBig
(
[OrderID] [int] IDENTITY(1,1) PRIMARY KEY NONCLUSTERED NOT NULL,
[CustomerID] [int] NULL,
[OrderDate] [date] NULL,
[Value] [numeric](18, 2) NOT NULL,
[CountCol] [varchar](20) NULL,
) ON [PRIMARY]
GO
ALTER TABLE dbo.Tmp_OrdersBig SET (LOCK_ESCALATION = TABLE)
GO
SET IDENTITY_INSERT TMP_ORDERSBIG ON
go
IF EXISTS(SELECT * FROM dbo.OrdersBig)
EXEC(‘INSERT INTO dbo.Tmp_OrdersBig (OrderID, CustomerID, OrderDate, Value, CountCol)
SELECT OrderID, CustomerID, OrderDate, Value, CONVERT(char(15), CountCol) FROM dbo.OrdersBig WITH (HOLDLOCK TABLOCKX)’)
GO
SET IDENTITY_INSERT TMP_ORDERSBIG OFF
GO
DROP TABLE dbo.OrdersBig
GO
EXECUTE sp_rename N’dbo.Tmp_OrdersBig’, N’OrdersBig’, ‘OBJECT’
GO
CREATE CLUSTERED INDEX IX_OrdersBigClustered ON dbo.OrdersBig
(
CustomerID
) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
CREATE NONCLUSTERED INDEX [ix_orders_big_CountCol_CountCol_customerid_CountDistinct] ON [dbo].[OrdersBig]
(
[CountCol] ASC,
[CustomerID] ASC,
[OrderDate] ASC
)
WHERE ([CountCol]=‘CountDistinct’)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
CREATE NONCLUSTERED INDEX [IX_orders_big_Countcol_Customerid_Value] ON [dbo].[OrdersBig]
(
[CountCol] ASC,
[CustomerID] ASC,
[Value] ASC
)WITH (DATA_COMPRESSION=PAGE, PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
USE [tempdb]
GO
CREATE NONCLUSTERED INDEX [IX_orders_big_CountCol_Customerid_Count] ON [dbo].[OrdersBig]
(
[CountCol] ASC,
[CustomerID] ASC
)
WHERE ([CountCol]=‘Count’)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
Script da consulta:
WITH CTE_Customers ( CountCust, CustomerID, CountCol, Value )
AS ( SELECT CountCust = ( CASE WHEN CountCol = ‘Count’
THEN ( SELECT COUNT(1)
FROM OrdersBig e
WHERE CountCol = ‘Count’ and
e.CustomerID = a.CustomerID
)
WHEN CountCol = ‘CountDistinct’
THEN ( SELECT COUNT(DISTINCT d.OrderDate)
FROM OrdersBig d
WHERE CountCol = ‘CountDistinct’ and
d.CustomerID = a.CustomerID
)
WHEN CountCol = ‘CountDistinct_1’
THEN ( SELECT COUNT(DISTINCT 1)
FROM OrdersBig C
WHERE CountCol = ‘CountDistinct_1’ and
c.CustomerID = a.CustomerID
)
ELSE NULL
END ),
CustomerID,
CountCol,
AVG(VALUE) AS Value
FROM OrdersBig AS A
GROUP BY Customerid,
CountCol
)
SELECT a.CustomerID,
a.CountCol,
CountCust as Cnt,
( CASE VALUE
WHEN 1000 THEN ‘Média = 1 mil’
WHEN 2000 THEN ‘Média = 2 mil’
WHEN 3000 THEN ‘Média = 3 mil’
WHEN 4000 THEN ‘Média = 4 mil’
WHEN 5000 THEN ‘Média = 5 mil’
ELSE ‘Não é número exato’
END )
FROM CTE_Customers a
ORDER BY a.CustomerID
OPTION ( MAXDOP 1 );
O plano é o seguinte:
A consulta roda em 4 segundos, e utiliza os seguintes recursos:
Apesar da consulta do Alberto fazer mais leituras de páginas que a minha solução, o tempo foi menor e isso é o que importa.
Eu gostaria muito de saber qual o tempo da minha consulta e da consulta do Alberto na sua máquina, pode testar e postar o resultado em um comentário aqui no blog?
É isso ai galera, espero que tenham gostado… e fiquem de olho que já tenho outro desafio de performance pronto para ser publicado 🙂
Abs.
Aviso – Resposta desafio de performance
Galera só um lembrete,
Semana que vem vou postar o resultado ok?… Quem está trabalhando na solução corre que ta acabando o tempo. ![]()
Se você não sabe do que estou falando clique aqui.
Abs.
Sensacional esse texto que vi no blog do Alex 🙂






