Arquivo

Arquivo do Autor

Quiz – Porque o SQL não utiliza meu indice?

24 de novembro de 2008 Deixe um comentário

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

clip_image002

 

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

clip_image004

 

Muito boa pergunta eim!

Relembrando respondam no meu e-mail, em breve estarei publicando a resposta…

Categorias:SQL Server

Operador do dia – Concatenation

24 de novembro de 2008 Deixe um comentário

 

clip_image001  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:

clip_image002

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

clip_image003

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.

Categorias:SQL Server

Operador do Dia – Compute Scalar

21 de novembro de 2008 Deixe um comentário

 

clip_image001  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:

clip_image002

 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.

clip_image003

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:

clip_image004

       |–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.

clip_image005

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

clip_image006

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:

clip_image007

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

clip_image008

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.

clip_image009

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.

Categorias:SQL Server

Operador do Dia – Bookmark Lookup

20 de novembro de 2008 Deixe um comentário

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.

clip_image002 Ícone no SQL Server 2005 e 2008.

clip_image004 Ícone no SQL Server 2000.

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.

Exibir artigo…

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’

clip_image006

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.

Categorias:SQL Server

Operador do dia – Assert

19 de novembro de 2008 Deixe um comentário

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,

clip_image002

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’) 

clip_image004

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ã.

Categorias:SQL Server

Comics

6 de novembro de 2008 Deixe um comentário

Bad days, need to be funny days…

Comics (5)

Comics (1)

Comics (2) Comics (3)

Comics (4)

Comics

Categorias:Entretenimento

Marry-go-round

4 de novembro de 2008 Deixe um comentário

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

Categorias:SQL Server

Table Variables e Transactions

27 de outubro de 2008 3 comentários

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.

Categorias:SQL Server

Scalar Functions e Analysis Services – Named Calculation

21 de outubro de 2008 1 comentário

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.

 

 imagem1

 

 imagem2

Depois de clicar no OK a coluna ficará disponível como todas as outras mas com um ícone(imagem3) diferente.

 

That’s all folks…

 

Categorias:SQL Server

Microsoft SQL Server 2008 Internals

20 de outubro de 2008 Deixe um comentário

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.

SQL2008_

http://www.amazon.com/dp/0735626243/ 

Estou aguardando ansiosamente…

Categorias:Não categorizado