Arquivo
Quiz – Porque o SQL não utiliza meu indice?
Recentemente vi uma pergunta bem interessante, e vou publicar aqui também, pode ser que você já tenha visto, portanto se esse for o caso não estrague a brincadeira com a solução nos comentários ok? J Valeu.
Vamos lá, vou colocar um script e fazer uma pergunta e o leitor que conseguir me dar a melhor resposta ganhará um.. ummm….. super parabéns….
Eu irei analisar as respostas e publicar o nome do Leitor aqui no Blog, ao responder envie um e-mail para mim no fabiano_amorim( at )bol.com.br com o maior número de detalhes que você puder levantar em relação a questão abaixo.
Vamos lá,
O problema é o seguinte, eu tenho um índice em uma tabela e o SQL não o utiliza, porém se eu trocar a coluna do índice e colocar na clausula INCLUDE ele passa a usar o índice, o que eu quero saber é o porque deste comportamento.
USE TEMPDB
GO
DROP TABLE TABTeste
CREATE TABLE TabTeste(ID Int Identity(1,1) Primary Key,
Nome1 VarChar(4) NOT NULL,
Valor1 Int NOT NULL)
GO
DECLARE @i INT
SET @i = 0
WHILE (@i < 1000)
BEGIN
INSERT INTO TabTeste(Nome1, Valor1)
VALUES(‘aaaa’, 0)
SET @i = @i + 1
END;
GO
Na consulta abaixo o SQL tem todos os valores da consulta no índice, já que a coluna ID é o índice cluster, portanto ele faz parte do índice nonclustered.
Então porque o SQL não utiliza o índice por Nome1 e Valor1? Neste caso o SQL não deveria utilizar o índice?
CREATE NONCLUSTERED INDEX ix_TesteSem_Include ON TabTeste(Nome1, Valor1)
GO
SELECT ID
FROM TabTeste
WHERE Nome1 = ‘aaaa’
AND Valor1 <= 10
ORDER BY ID
Se eu recriar o índice, mas desta vez incluindo a coluna Valor1 como INCLUDE ele passa a utilizar o índice, porque?
CREATE NONCLUSTERED INDEX ix_Teste_Include ON TabTeste(Nome1) INCLUDE(Valor1)
GO
SELECT ID
FROM TabTeste
WHERE Nome1 = ‘aaaa’
AND Valor1 <= 10
ORDER BY ID
Muito boa pergunta eim!
Relembrando respondam no meu e-mail, em breve estarei publicando a resposta…
Operador do dia – Concatenation
O operador de hoje é o concatenetion, seu comportamento é bem simples, ele recebe vários inputs, concatena tudo e retorna cada linha lida. Ele pode ser visualizado quando utilizado o comando UNION ALL.
É um exemplo clássico de operadores que recebem mais de um input, e tem mais de um filho.
Antes de iniciar a falar sobre ele vamos entender alguns pontos importantes em relação aos operadores dos Planos de Execução.
Todos os operadores executam os mesmos comandos de Init(), GetNext() e Close(), e estes operadores podem receber um ou mais filhos como input, vamos ver alguns exemplos.
Script para criação de tabela de teste.
USE tempdb
GO
CREATE TABLE TABTeste(ID Int Identity(1,1) PRIMARY KEY,
Nome VarChar(250) DEFAULT NewID())
GO
SET NOCOUNT ON
GO
INSERT INTO TABTeste DEFAULT VALUES
GO 10000
O comando acima cria uma tabela e inclui 10000 registros.
SELECT *
FROM TABTeste a
INNER JOIN TABTeste b
ON a.ID = b.ID
Plano de execução estimado:
SELECT * FROM TABTeste a INNER JOIN TABTeste b ON a.ID = b.ID
|–Merge Join(Inner Join, MERGE:([b].[ID])=([a].[ID]), RESIDUAL:([tempdb].[dbo].[TABTeste].[ID] as [b].[ID]=[tempdb].[dbo].[TABTeste].[ID] as [a].[ID]))
|–Clustered Index Scan(OBJECT:([tempdb].[dbo].[TABTeste].[PK__TABTeste__3214EC2743869CDD] AS [b]), ORDERED FORWARD)
|–Clustered Index Scan(OBJECT:([tempdb].[dbo].[TABTeste].[PK__TABTeste__3214EC2743869CDD] AS [a]), ORDERED FORWARD)
Como podemos observar, na consulta acima o SQL utilizou o operador de MERGE para fazer a operação de Join entre as tabelas do select, e neste caso ele está recebendo dois filhos(duas vezes a tabela TABTeste) como input.
O operador de hoje, o Concatenation é um exemplo de operadores que tem mais de um filho como input.
SELECT * FROM TABTeste
UNION ALL
SELECT * FROM TABTeste
UNION ALL
SELECT * FROM TABTeste
UNION ALL
SELECT * FROM TABTeste
SELECT * FROM TABTeste UNION ALL SELECT * FROM TABTeste UNION ALL SELECT * FROM TABTeste UNION ALL SELECT * FROM TABTeste
|–Concatenation
|–Clustered Index Scan(OBJECT:([tempdb].[dbo].[TABTeste].[PK__TABTeste__3214EC2743869CDD]))
|–Clustered Index Scan(OBJECT:([tempdb].[dbo].[TABTeste].[PK__TABTeste__3214EC2743869CDD]))
|–Clustered Index Scan(OBJECT:([tempdb].[dbo].[TABTeste].[PK__TABTeste__3214EC2743869CDD]))
|–Clustered Index Scan(OBJECT:([tempdb].[dbo].[TABTeste].[PK__TABTeste__3214EC2743869CDD]))
O operador Concatenation recebe o resultado dos scans efetuados pelo Clustered Index Scan e copia as linhas lidas para o Outupt, e repete este processo para todos os Inputs.
O SQL irá executar este processo na ordem que é exibido o
plano, neste caso, o primeiro scan é o acima e o ultimo é o abaixo.
Operador do Dia – Compute Scalar
O operador de hoje, é o compute scalar, e ele esta presente na maioria dos planos de execução que vemos.
Como o próprio nome já diz o Compute Scalar efetua um determinado calculo e retorna o valor calculado, este calculo pode ser uma conversão, uma concatenação e outros…
Quase sempre ele é passado despercebido por apresentar um custo mínimo em relação a query, mas ele pode ser um ponto de atenção quando estamos falando de cursores, e loops muito grandes. Principalmente se estiver com problema de CPU.
Por exemplo, utilizar o Compute Scalar para fazer uma simples conversão pode ser tranqüilo, mas execute isso 1 milhão de vezes e verá que se você conseguir evitar este passo terá um ganho de CPU, e por conseqüência terá ganho de tempo.
USE tempdb
GO
CREATE TABLE TABTeste(ID Int Identity(1,1) PRIMARY KEY,
Nome VarChar(250) DEFAULT NewID())
GO
SET NOCOUNT ON
GO
INSERT INTO TABTeste DEFAULT VALUES
GO 10000
O comando acima cria uma tabela e inclui 10000 registros.
Agora vamos fazer um loop para o SQL passar pelo código 1 milhão de vezes.
DECLARE @I Int
SET @I = 0
WHILE @I < 1000000
BEGIN
IF EXISTS(SELECT ID FROM TABTeste WHERE ID = @I)
BEGIN
PRINT ‘Entrou no IF’
END
SET @I = @I + 1;
END
GO
Plano de execução estimado:
Repare que o SQL Server utilizou o Compute Scalar, vamos olhar melhor no plano em modo texto.
|–Compute Scalar(DEFINE:([Expr1003]=CASE WHEN [Expr1004] THEN (1) ELSE (0) END))
|–Nested Loops(Left Semi Join, DEFINE:([Expr1004] = [PROBE VALUE]))
|–Constant Scan
|–Clustered Index Seek(OBJECT:([tempdb].[dbo].[TABTeste].[PK__TABTeste__3214EC27096F09E1]), SEEK:([tempdb].[dbo].[TABTeste].[ID]=[@I]) ORDERED FORWARD)
Conforme podemos observar o Compute Scalar foi Utilizado para verificar se o Nested Loop retornou alguma coisa ou não, ou seja, ele está fazendo o papel do IF EXISTS…
O resultado de execução da query e o uso de CPU está na imagem abaixo, gerada pelo Profiler.
Agora vamos alterar o código para remover o uso do Compute Scalar
DECLARE @I Int, @Var Int
SET @I = 0
WHILE @I < 1000000
BEGIN
SELECT @Var = ID FROM TABTeste WHERE ID = @I
IF @@ROWCOUNT > 0
BEGIN
PRINT ‘Entrou no IF’
END
SET @I = @I + 1;
END
GO
Plano de execução estimado:
|–Clustered Index Seek(OBJECT:([tempdb].[dbo].[TABTeste].[PK__TABTeste__3214EC27096F09E1]), SEEK:([tempdb].[dbo].[TABTeste].[ID]=[@I]) ORDERED FORWARD)
Desta vez o SQL não utilizou o Compute Scalar, vamos ver o resultado no profiler.
Como você pode observar o SQL utilizou menos CPU e terminou a execução em nos tempo. Não estou querendo mostrar qual é a melhor forma de fazer uma validação, apenas que exibir o uso do operador. De qualquer forma, fica ai a dica do uso da @@RowCount. Eu já mudei um código onde tinha uns 30 IF EXISTS dentro de um loop desses e o resultado foi bem satisfatório.
Vamos ver mais do Compute Scalar na pratica.
DECLARE @Tab TABLE(ID SmallInt PRIMARY KEY)
SELECT ‘Fabiano’ + ‘ – ‘ + ‘Amorim’ FROM @Tab
SELECT ‘Fabiano’ + ‘ – ‘ + ‘Amorim’ FROM @Tab
|–Compute Scalar(DEFINE:([Expr1003]=’Fabiano – Amorim’))
|–Clustered Index Scan(OBJECT:(@Tab))
O plano gerado foi exatamente para fazer a concatenação, simples não?
Agora vamos ver um comportamento bem interessante do Compute Scalar, que mudou a partir do SQL Server 2005.
Imagine o seguinte código:
DECLARE @Tab TABLE(ID SmallInt PRIMARY KEY)
DECLARE @ID_Int Integer
SELECT *
FROM @Tab
WHERE ID = @ID_Int
Repare que a coluna ID é do tipo SmallInt e a variável @ID_Int é do tipo Integer, ou seja o SQL terá que converter o valor de @ID_Int para poder efetuar a comparação com a coluna ID.
No SQL Server 2000 ele gera o seguinte plano:
SELECT * FROM @Tab WHERE ID = @ID_Int
|–Nested Loops(Inner Join, OUTER REFERENCES:([Expr1002], [Expr1003], [Expr1004]))
|–Compute Scalar(DEFINE:([Expr1002]=Convert([@ID_Int])-1, [Expr1003]=Convert([@ID_Int])+1, [Expr1004]=If (Convert([@ID_Int])-1=NULL) then 0 else 6|If (Convert([@ID_Int])+1=NULL) then 0 else 10))
| |–Constant Scan
|–Clustered Index Seek(OBJECT:(@Tab), SEEK:(@Tab.[ID] > [Expr1002] AND @Tab.[ID] < [Expr1003]), WHERE:(Convert(@Tab.[ID])=[@ID_Int]) ORDERED FORWARD)
Uau, que trampo eim? Agora vamos ver o que acontece se eu rodar este código no SQL 2005 ou 2008.
Plano de execução no SQL Server 2005
SELECT * FROM @Tab WHERE ID = @ID_Int
|–Clustered Index Seek(OBJECT:(@Tab), SEEK:([ID]=[@ID_Int]) ORDERED FORWARD)
Aaaa, agora sim… Ficou fácil não? Ué mas o SQL não precisa mais converter o valor?
Vamos dar uma olhada melhor nos hints do operador Clustered Index Seek.
O time de desenvolvimento do Query Optimizer mudou um pouco a coisa, agora o SQL utiliza a função “Scalar Operator” para converter o valor de @ID_Int para o tipo da coluna ID.
Interessante…
Por hoje é só, até segunda-feira com o próximo Operador do dia.
Operador do Dia – Bookmark Lookup
O operador de hoje, é um dos mais famosos, e comuns de visualizarmos nos planos de execução.
Eles mudaram bastante conforme o tempo, no SQL 2000 ele se chamava BookMark Lookup, já no SQL 2005 SP2 e 2008 ele é conhecido como Key Lookup. No modo texto do plano de execução ele é apresentado como Index Seek.
Ícone no SQL Server 2005 e 2008.
O Key Lookup é utilizado em tabelas que contem um índice Cluster, o SQL utiliza este operador para localizar dados em um índice cluster, dados estes que não pertencem a um determinado índice nonclustered.
Eu escrevi um artigo falando sobre sua funcionalidade e com alguns exemplos no link abaixo.
Eu mencionei que ele é famoso porque a bastante coisa sobre ele na internet.
Quando você encontrar ele em um plano de execução, de cara você sabe que tem uma chance de otimizar a consulta criando um Covered Index, ou seja, criar um índice nonclustered que irá compor todas as colunas do seu select.
Funcionamento:
Vamos usar o seguinte script.
CREATE TABLE TabTeste (ID Int PRIMARY KEY, Nome VarChar(80), SobreNome VarChar(80))
CREATE NONCLUSTERED INDEX ix_Teste ON TABTeste(Nome)
SELECT * FROM TabTeste WITH(index = ix_Teste)
WHERE Nome = ‘Fabiano’
SELECT * FROM TabTeste WITH(index = ix_Teste) WHERE Nome = ‘Fabiano’
|–Nested Loops(Inner Join, OUTER REFERENCES:([dbo].[TabTeste].[ID]))
|–Index Seek(OBJECT:([dbo].[TabTeste].[ix_Teste]), SEEK:([dbo].[TabTeste].[Nome]=’Fabiano’) ORDERED FORWARD)
|–Clustered Index Seek(OBJECT:([dbo].[TabTeste].[PK__TabTeste__3214EC27131C685F]), SEEK:( [dbo].[TabTeste].[ID]= [dbo].[TabTeste].[ID]) LOOKUP ORDERED FORWARD)
Na consulta acima, o SQL irá utilizar o índice ix_teste para pegar os dados da tabela TabTeste, porém no índice ix_teste eu só tenho os dados das colunas Nome, e ID (todo índice nonclustered também tem a chave do índice cluster), mas na consulta estou pedindo todas as colunas (*).
Neste caso o SQL irá ler as colunas ID e Nome do índice ix_teste e o valor de ID e Sobrenome do índice Cluster que é a PK. Depois de ler os valores, ele pega os 2 ID e faz um Join para “juntar” qual Sobrenome pertence a qual ID e Nome, por fim retornando os dados para o Select.
Operador do dia – Assert
Vou tentar falar de 1 operador para cada dia, vamos todos torcer para que eu lembre e consiga ter tempo para isso, vamos lá.
Para não ficar falando por 1 ano sobre os operadores, vou falar apenas daqueles operadores mais comuns, o primeiro é o Assert.
O Assert é utilizado para validar uma determinada condição, ele valida se uma Constraint não foi violada, por exemplo uma contraint Check que define que só pode haver 2 valors, o assert irá validar se o valor que foi passado como entrada está de acordo com a constraint.
Vamos ver alguns exemplos para entendermos melhor.
Assert validando Check Constraints:
CREATE TABLE Tab1(ID Int IDENTITY(1,1) PRIMARY KEY, Sexo Char(1))
GO
ALTER TABLE TAB1 ADD CONSTRAINT ck_Sexo_M_F CHECK(Sexo IN(‘M’,‘F’))
GO
INSERT INTO Tab1(Sexo) VALUES(‘X’)
GO
O plano de execução para o insert é o seguinte,
INSERT INTO Tab1(Sexo) VALUES(‘X’)
|–Assert(WHERE:(CASE WHEN [rd_des_176].[dbo].[Tab1].[Sexo]<>’F’ AND [rd_des_176].[dbo].[Tab1].[Sexo]<>’M’ THEN (0) ELSE NULL END))
|–Table Insert(OBJECT:([rd_des_176].[dbo].[Tab1]), SET:([rd_des_176].[dbo].[Tab1].[Sexo] = [Expr1004]), DEFINE:([Expr1004]=CONVERT_IMPLICIT(char(1),[@1],0)))
Como podemos observar o plano utiliza o Assert para verificar se o valor que está sendo inserido não viola a contraint,
Se o valor for diferente de ‘F’ e diferente de <> ‘M’ então retorna 0 senão retorna NULL,
O Assert é programado para gerar um erro caso o seu valor de saída seja diferente de NULL, o seja, o valor não é nem ‘F’ e nem ‘M’.
Assert validando Foreign Keys:
ALTER TABLE Tab1 ADD ID_TipoSexo Int
GO
CREATE TABLE Tab2(ID Int IDENTITY(1,1) PRIMARY KEY, Sexo Char(1))
GO
INSERT INTO Tab2(Sexo) VALUES(‘F’)
INSERT INTO Tab2(Sexo) VALUES(‘M’)
INSERT INTO Tab2(Sexo) VALUES(‘I’)
GO
ALTER TABLE TAB1 ADD CONSTRAINT fk_Tab2 FOREIGN KEY (ID_TipoSexo) REFERENCES Tab2(ID)
GO
INSERT INTO Tab1(ID_TipoSexo, Sexo) VALUES(4, ‘X’)
INSERT INTO Tab1(ID_TipoSexo, Sexo) VALUES(4, ‘X’)
|–Assert(WHERE:(CASE WHEN NOT [Pass1009] AND [Expr1008] IS NULL THEN (0) ELSE NULL END))
|–Nested Loops(Left Semi Join, PASSTHRU:([tempdb].[dbo].[Tab1].[ID_TipoSexo] IS NULL), OUTER REFERENCES:([tempdb].[dbo].[Tab1].[ID_TipoSexo]), DEFINE:([Expr1008] = [PROBE VALUE]))
|–Assert(WHERE:(CASE WHEN [tempdb].[dbo].[Tab1].[Sexo]<>’F’ AND [tempdb].[dbo].[Tab1].[Sexo]<>’M’ THEN (0) ELSE NULL END))
| |–Clustered Index Insert(OBJECT:([tempdb].[dbo].[Tab1].[PK__Tab1__3214EC277097A3C8]), SET:([tempdb].[dbo].[Tab1].[ID_TipoSexo] = [@1],[tempdb].[dbo].[Tab1].[Sexo] = [Expr1004],[tempdb].[dbo].[Tab1].[ID] = [Expr1003]))
| |–Compute Scalar(DEFINE:([Expr1004]=CONVERT_IMPLICIT(char(1),[@2],0)))
| |–Compute Scalar(DEFINE:([Expr1003]=getidentity((1856985942),(2),NULL)))
| |–Constant Scan
|–Clustered Index Seek(OBJECT:([tempdb].[dbo].[Tab2].[PK__Tab2__3214EC27755C58E5]), SEEK:([tempdb].[dbo].[Tab2].[ID]=[tempdb].[dbo].[Tab1].[ID_TipoSexo]) ORDERED FORWARD)
Desta vez podemos ver o operador Assert duas vezes, primeiro validando a Check Constraint, e depois ele pegando o resultado do Join da Tab1 com a Tab2 e utilizando o mesmo controle de 0 continua a execução ou NULL gera erro. O interessante aqui é que o “[Expr1008] IS NULL”, pois se o valor passado para a coluna ID_TipoSexo for NULL então não pode gerar erro, neste caso ele retornaria o 0, e continuaria o processo.
Se você executar o INSERT o SQL irá reclamar do valor do X, se você alterar o X para F, ele irá reclamar do valor do 4, se você trocar o 4 para NULL, 1, 2 ou 3 o insert irá ser executado com sucesso.
Assert validando SubQuery:
O Assert também faz a validação de uma SubQuery por ex, sabemos que uma SubQuery Scalar não pode retornar mais de um valor, mas nem sempre isso acontece, o responsável por validar se a SubQuery Scalar retornou mais de um valor é o Assert.
INSERT INTO Tab1(ID_TipoSexo, Sexo) VALUES((SELECT ID_TipoSexo FROM Tab1), ‘F’)
INSERT INTO Tab1(ID_TipoSexo, Sexo) VALUES((SELECT ID_TipoSexo FROM Tab1), ‘F’)
|–Assert(WHERE:(CASE WHEN NOT [Pass1016] AND [Expr1015] IS NULL THEN (0) ELSE NULL END))
|–Nested Loops(Left Semi Join, PASSTHRU:([tempdb].[dbo].[Tab1].[ID_TipoSexo] IS NULL), OUTER REFERENCES:([tempdb].[dbo].[Tab1].[ID_TipoSexo]), DEFINE:([Expr1015] = [PROBE VALUE]))
|–Assert(WHERE:([Expr1017]))
| |–Compute Scalar(DEFINE:([Expr1017]=CASE WHEN [tempdb].[dbo].[Tab1].[Sexo]<>’F’ AND [tempdb].[dbo].[Tab1].[Sexo]<>’M’ THEN (0) ELSE NULL END))
| |–Clustered Index Insert(OBJECT:([tempdb].[dbo].[Tab1].[PK__Tab1__3214EC277097A3C8]), SET:([tempdb].[dbo].[Tab1].[ID_TipoSexo] = [Expr1008],[tempdb].[dbo].[Tab1].[Sexo] = [Expr1009],[tempdb].[dbo].[Tab1].[ID] = [Expr1003]))
| |–Top(TOP EXPRESSION:((1)))
| |–Compute Scalar(DEFINE:([Expr1008]=[Expr1014], [Expr1009]=’F’))
| |–Nested Loops(Left Outer Join)
| |–Compute Scalar(DEFINE:([Expr1003]=getidentity((1856985942),(2),NULL)))
| | |–Constant Scan
| |–Assert(WHERE:(CASE WHEN [Expr1013]>(1) THEN (0) ELSE NULL END))
| |–Stream Aggregate(DEFINE:([Expr1013]=Count(*), [Expr1014]=ANY([tempdb].[dbo].[Tab1].[ID_TipoSexo])))
| |–Clustered Index Scan(OBJECT:([tempdb].[dbo].[Tab1].[PK__Tab1__3214EC277097A3C8]))
|–Clustered Index Seek(OBJECT:([tempdb].[dbo].[Tab2].[PK__Tab2__3214EC27755C58E5]), SEEK:([tempdb].[dbo].[Tab2].[ID]=[tempdb].[dbo].[Tab1].[ID_TipoSexo]) ORDERED FORWARD)
Na consulta acima, o SQL gerou um Stream Aggregate para calcular quantas linhas a subquery irá retornar, depois o Assert pegou este resultado o fez a sua analise.
É interessante ver como o Query Optimizer é esperto o suficiente para não utilizar operadores quando eles não são necessários, por exemplo:
INSERT INTO Tab1(ID_TipoSexo, Sexo) VALUES((SELECT ID_TipoSexo FROM Tab1 WHERE ID = 1), ‘F’)
INSERT INTO Tab1(ID_TipoSexo, Sexo) VALUES((SELECT TOP 1 ID_TipoSexo FROM Tab1), ‘F’)
Nas consultas acima, o Query Optimizer é esperto o suficiente para saber que somente uma linha será retornada, portanto não há necessidade de uso do Assert.
É Isso ai pessoal por hoje é só.
Até amahã.
Marry-go-round
Ok, hoje não vou falar sobre o Query Processor, mas, por falar em Query Processor, logo devem ser publicadas algumas matérias que escrevi para a SQL Magazine falando sobre o Query Processor mais especificamente sobre o Query Optimizer, quando for lançado eu aviso aqui no Blog.
Hoje vou escrever sobre feature bem interessante relacionada ao Database Engine.
O mecanismo conhecido como “Marry-go-round scanning“, é uma feature que existe no SQL Server Enterprise, veja bem, eu disse Enterprise, portanto sem chance de testar em versões inferiores, voltando ao assunto, o conceito é o seguinte, quando uma consulta é enviada para o SQL ele pode detectar que existe um plano de execução(eita e eu disse que não ia falar sobre eles, mas eles me perseguem J) que está fazendo um scan na mesma tabela ou índice da consulta que foi enviada, caso este plano de execução esteja rodando, ele começa a fazer o scan a partir do scan que já estava rodando, depois que o primeiro scan termina, o segundo scan volta ao inicio da tabela e começa a ler novamente os dados que já haviam sido lidos pela consulta que estava rodando. Unf, ficou meio confuso né? Vamos tentar elaborar um cenário, acho que vai ficar mais fácil de entender.
Imagine que exista uma tabela com 1 milhão de registros e o SQL recebe o seguinte select.
SELECT * FROM TabTeste
Quando o SQL começa a executar o plano de execução da consulta acima, imagine que outra consulta é acionada por outra sessão, fazendo o mesmo select
SELECT * FROM TabTeste
O Engine detecta que já existe um plano fazendo um scan na tabela TabTeste, e se une a ele até fazer a leitura total da tabela, quando o primeiro scan termina, o segundo scan volta para a primeira linha da tabela e faz a leitura até chegar na linha onde ele se juntou ao primeiro scan.
Com isso o SQL aproveita todos os recursos que estavam sendo utilizados pelo primeiro scan e compartilha o processamento para as duas consultas, e cada pagina será lida apenas uma vez para cada usuário.
Isso me fez lembrar de uma ocasião onde li o seguinte, se você quer garantir que a ordenação de sua tabela seja por uma determinada coluna, especifique a coluna no ORDER BY.
Você pode se perguntar, – Ora mas no caso do SQL optar por um plano que ira utilizar um determinado índice, ele irá retornar as linhas conforme a ordem do índice, por ex: Faço um select em uma tabela de clientes e o SQL usa o índice da coluna Nome, as linhas serão retornadas por ordem de nome, ou então faço um select em uma tabela que possui um índice cluster por uma coluna ID do tipo Integer, o SQL irá retornar os dados ordenados pela coluna ID já que o índice cluster ordena os dados na tabela. Na teoria isso está certo, mas existem alguns casos em quem isso não será verdade. Vamos analisar alguns deles:
· Pode acontecer do SQL optar por utilizar paralelismo para retornar os dados, quando isso acontecer a ordem do resultado não será exatamente a esperada, ou seja, ordenado pela coluna do índice.
· Outra situação onde o SQL pode não retornar os dados na ordem esperada é quando ele opta por utilizar o mecanismo de “merry-go-round” mencionado acima.
· Caso você utilize os HINTS NOLOCK e TABLOCK o SQL irá retornar os dados na ordem de alocação do índice, ou seja, na ordem em que os arquivos estão escritos em disco, e nem sempre esta ordem é seqüencial. Mesmo no índice cluster os dados podem estar desalinhados fisicamente, na verdade muita gente equivocadamente diz que um índice cluster ordena os dados fisicamente, e isso não é verdade, este comportamento pode comprovar isso.
Se ficar alguma dúvida fique a vontade em perguntar,
That’s all folks
Cheers
Table Variables e Transactions
Hoje um amigo perguntou sobre o conceito de transações no SQL Server,
Ele queria fazer o seguinte, chamar um processo para fazer alguns cálculos, depois gravar o resultado disso em uma tabela, e depois efetuar um rollback, pois o processo seria de simulação.
A dúvida era que quando ele efetuava um rollback o SQL fazia um rollback de tudo, inclusive no insert que ele havia feito para pegar o resultado da simulação.
Ou seja, ele queria guardar o valor da simulação em uma tabela para exibir na tela.
Mesmo não concordando muito com a lógica aplicada, sugeri para ele usar uma variável de tabela para guardar o resultado, já que ela não faz parte do escopo de uma transação portanto não afetada pelo rollback.
Por ex:
BEGIN TRAN
DECLARE @TMP TABLE (LETRA CHAR(2))
INSERT INTO @TMP(LETRA)
VALUES(‘EU’)
ROLLBACK TRAN
SELECT LETRA + ‘ ‘ + ‘CONTINUO AQUI!’ FROM @TMP
Uma outra solução seria usar SavePoints, mas também não acho que seria legal, maasss fica a dica sobre as variáveis do tipo table.
Scalar Functions e Analysis Services – Named Calculation
Scalar functions são bastante utilizadas em banco de dados, mas elas são verdadeiras killers de CPU…
Estou trabalhando na manutenção de um projeto ETL para BI e me deparei com um desses casos onde vale a pena escrever…
Tenho uma consulta que retorna aproximadamente 800 mil linhas o que é bastante comum quando estamos falando de bases DW, mas o interessante aqui é que o select que retorna os dados para criação da minha dimensão usa uma function para gerar o Ano, Mês e Dia a partir de uma Data existente na tabela. Depois de converter a data para Ano usando a function YEAR do SQL eu pego o valor de Ano e jogo em uma outra tabela na coluna Ano, e depois uso ela na dimensão de tempo no meu cubo Analysis Services.
Bom vou resumir para ver se da pra ficar mais fácil de entender, eu tenho uma Data e preciso do Ano, Mês e Dia desta data em colunas separadas para poder efetuar os filtros por um determinado período no Cubo.
Ai é que ta, na verdade eu nem precisava fazer a conversão no SQL para jogar no AS, mas eu posso simplesmente criar uma Named Calculation na tabela do DataSource View no AS, antes de mostrar isso, vamos ver porque Scalar Functions são bad.
Vamos criar uma base para os testes,
use tempdb
SET NOCOUNT ON
IF OBJECT_ID(‘TabTeste’) IS NOT NULL
DROP TABLE TabTeste
GO
CREATE TABLE TabTeste (ID Int Identity(1,1),
Nome VarChar(200) DEFAULT NEWID(),
Data DateTime DEFAULT GETDATE())
GO
— Vamos inserir um registro para simular o problema
INSERT INTO TabTeste(Nome, Data) VALUES(‘Teste’, ‘20050101’)
GO
— Massa aleatoria de dados.
INSERT INTO TabTeste DEFAULT VALUES
GO 1000000
Agora vamos criar uma Scalar Function para usar nos testes.
IF OBJECT_ID(‘fn_PreencheComZeros’) IS NOT NULL
DROP FUNCTION dbo.fn_PreencheComZeros
GO
CREATE FUNCTION dbo.fn_PreencheComZeros(@Coluna VarChar(250), @Qtde_Zeros Int)
RETURNS Varchar(200)
AS
BEGIN
DECLARE @Result VarChar(200)
SET @Result = RIGHT(REPLICATE(‘0’, @Qtde_Zeros) + @Coluna, @Qtde_Zeros)
RETURN @Result;
END
GO
Agora um exemplo simples de um select para retornar os dados que eu preciso.
— Retorna os dados com da tabela criando 3 colunas, Ano, Mes e Dia.
SELECT YEAR(Data) AS Ano,
MONTH(Data) AS Mes,
DAY(Data) AS Dia,
*
FROM TabTeste
— Rodou em 31 segundos e usou 1109 ms de CPU
/* Exemplo do resultado
Ano Mes Dia ID Nome Data
2005 1 1 1 Teste 2005-01-01 00:00:00.000
2008 10 21 2 E09D7055-CC12-40FC-8D3E-F7A28521D601 2008-10-21 11:35:46.087*/
Repare que na data de 01/01/2005 o Mês veio como "1" e não "01" a mesma coisa acontece com o dia, eu sei que existem outras formas de fazer retornar o "01" mas vamos dizer que eu me depare com uma solução que usa a função para formatar os valores para retornar conforme o desejado. É bem comum usar funções deste tipo para fazer diversas formatações desde concatenações até cálculos super complexos.
— Com a simples função de Preencher com Zero o SQL ficaria assim:
SELECT dbo.fn_PreencheComZeros(YEAR(Data),4) AS Ano,
dbo.fn_PreencheComZeros(MONTH(Data),2) AS Mes,
dbo.fn_PreencheComZeros(DAY(Data),2) AS Dia,
*
FROM TabTeste
— Rodou em 35 segundos e usou 22438 ms de CPU
/* Exemplo do resultado
Ano Mes Dia ID Nome Data
2005 01 01 1 Teste 2005-01-01 00:00:00.000
2008 10 21 2 E09D7055-CC12-40FC-8D3E-F7A28521D601 2008-10-21 11:35:46.087*/
Agora imagine que uma mente Brilhante resolva sofisticar a função e usa-lá para diversas formatações deixando o código dentro dela um pouco maior…
— Conforme mais código na function pior fica a coisa…
SELECT DBO.fn_PreencheComZerosSuper(YEAR(Data),4,0) AS Ano,
DBO.fn_PreencheComZerosSuper(MONTH(Data),2,0) AS Mes,
DBO.fn_PreencheComZerosSuper(DAY(Data),2,0) AS Dia,
*
FROM TabTeste
— Rodou em 1:15 e usou 73515 ms de CPU, ou seja o dobro do tempo inicial.
Mas o que fazer neste caso?
Bom a resposta é, evite usar Functions de qualquer tipo que seja em colunas, caso enha que fazer alguma formatação tente formatar o código direto na coluna por ex:
— Exemplo de uma solução
SELECT RIGHT(‘0000’ + Convert(VarChar(4),YEAR(Data)),4) AS Ano,
RIGHT(’00’ + Convert(VarChar(2), MONTH(Data)),2) AS Mes,
RIGHT(’00’ + Convert(VarChar(2), DAY(Data)),2) AS Dia,
*
FROM TabTeste
— Rodou em 33 segundos e usou 1984 ms de CPU
Named Calculation são colunas calculadas que você pode criar no seu DataSource View sem que você tenha que alterar a estrutura de suas tabelas, ou seja, não precisa gastar tempo para popular e espaço para armazenar os dados sendo que você pode criar uma Named Calculation que irá retornar o valor que você deseja.
No meu exemplo acima, optei por ao invés de ter que calcular e gravar os dados de Ano, Mês e Dia em uma tabela e depois ler estes valores, achei melhor criar 3 novas Named Calculation direto no AS, segue um print das telas para ficar mais claro.
Depois de clicar no OK a coluna ficará disponível como todas as outras mas com um ícone() diferente.
That’s all folks…
Microsoft SQL Server 2008 Internals
A Amazon publicou um link para pré-venda do novo livro da Delaney(co-Autores Paul Randal, Kimberly Trip, Conor…) que está no forno e com previsão de entrega para Fevereiro de 2008.
http://www.amazon.com/dp/0735626243/
Estou aguardando ansiosamente…





