Fabiano Neves's profileFabiano Neves Amorim - S...PhotosBlogListsMore Tools Help

Blog


    March 27

    Indice / Lookup / Recompile

    Recentemente o Luciano Moreira publicou um post no seu blog perguntando porque as vezes o SQL Server não usa um determinado índice, aproveitando a deixa resolvi escrever um pouco sobre isso.

     

    O link para o post do Luciano é este Exibir artigo...

     

     

    DROP TABLE TMP

     

    CREATE TABLE TMP(ID INT IDENTITY(1,1) PRIMARY KEY, NOME VARCHAR(200), Nome2 VarChar(200))

     

    CREATE INDEX ix_Teste on TMP(Nome)

     

    SET NOCOUNT ON

    DECLARE @I INT

     

    SET @I = 0

    WHILE @I < 1000

    BEGIN

      INSERT INTO TMP(NOME, Nome2) VALUES('A', NewID())

      INSERT INTO TMP(NOME, Nome2) VALUES('B', NewID())

      INSERT INTO TMP(NOME, Nome2) VALUES('C', NewID())

      INSERT INTO TMP(NOME, Nome2) VALUES('D', NewID())

      INSERT INTO TMP(NOME, Nome2) VALUES('E', NewID())

      SET @I = @I + 1;

    END

     

    -- Foram incluidos 5000 registros na tabela TMP

    SELECT COUNT(*) FROM TMP

     

    /*

      Pressione CTRL-M para incluir o plano de execução no resultado da consulta e execute o comando abaixo.

      O Select irá selecionar 1/5 dela

    */

    SELECT * FROM TMP

    WHERE Nome = 'A'

     

    /*

      Repare que o SQL Server não utilizou o indice pela coluna Nome e achou

      melhor fez um clustered scan, Vamos analisar melhor este comportamento.

      Primeiramente vamos ver quantas páginas o SQL Server terá que ler para

      retornar os dados da consulta, para isso vamos ligar o statistics io e rodar

      o select novamente.

    */

    SET STATISTICS IO ON

     

    SELECT * FROM TMP

    WHERE Nome = 'A'

    /*

    Table 'TMP'. Scan count 1, logical reads 37, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

    Podemos observar que o SQL leu 37 páginas de dados para retornar o resultado.

     

    Agora vamos forçar o uso do indice por nome para fazer o Seek e não o Clustered Scan,

    repare no plano de execução que o SQL fez um Seek e um bookmark para

    pegar o valor da coluna Nome2 que não faz parte do indice nonclustered ix_teste.

     

    No indice cluster(no nosso exemplo é o campo ID que é a primary key) temos as informações de todos

    os dados da tabela, portanto sempre que uma consulta seleciona campos a mais do que os cobertos pelo

    indice nonclustered o SQL irá no indice cluster ler esta informação.

    No nosso exemplo abaixo isso aconteceu porque a coluna Nome2 não pertence ao indice nonclustered,

    portanto o SQL fez o lookup para ler esta informação no indice cluster.

    */

    SELECT * FROM TMP WITH(INDEX = ix_teste)

    WHERE Nome = 'A'

     

    /*

    Table 'TMP'. Scan count 1, logical reads 2076, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

    Podemos observar que desta vez o SQL leu 2076 páginas de dados para retornar o resultado.

    Com isso podemos concluir que o Lookup é MUITO "caro" para sua consulta e o

    SQL só irá optar fazer o lookup se a quantidade de linhas que será retornada for

    aproximadamente menor que 0,1% do total de sua tabela.

     

    Uma solução para forçar o uso do indice sem perder tanto em performance

    seria incluir a coluna Nome2 no indice noncluster,

    No SQL 2000

    CREATE INDEX ix_Teste on TMP(Nome, Nome2)

    No SQL 2005 poderiamos utilizar a clausula INCLUDE

    CREATE INDEX ix_Teste on TMP(Nome) INCLUDE(Nome2)

    */

     

     

    /*

    Vamos incluir uma linha na tabela TMP com o valor X e efetuar

    o select para retonar apenas o valor X e ver o plano de execução.

    */

    INSERT INTO TMP(NOME, Nome2) VALUES('X', NewID())

     

    SELECT * FROM TMP

    WHERE Nome = 'X'

    GO

    SELECT * FROM TMP

    WHERE Nome = 'A'

    GO

     

    /*

    Repare que no primeiro select o SQL usou o Indice e no

    segundo não usou porque irá retornar muitas linhas

    */

     

     

    /*

    Um outro comportamento interessante é quando o valor do filtro é passado

    por uma variável, vimos no select acima que o SQL usou o indice para

    o filtro nome = 'X', vamos passar o valor 'X' para uma variável

    e utiliza-la no where.

    */

    DECLARE @var VarChar(200)

    SET @var = 'X'

    SELECT * FROM TMP

    WHERE Nome = @var

    /*

    Desta vez o SQL não usou o indice e fez o Scan mesmo a consulta retornando

    apenas 1 registro, isso acontece porque o SQL não sabe o valor de @var

    por este motivo ele opta por fazer o Scan porque ele não sabe se @var irá retornar

    1 linha ou 5000.

     

    Existem várias soluções para este problema, a idéia a fazer o SQL compilar

    o código com o valor de @var.

     

    Você poderia utilizar a SP_EXECUTESQL

    Ou então criar uma procedure e executar a consulta na procedure

    Ou então utilizar o Hint RECOMPILE

     

    Veja um exemplo do uso das 3 opções

    */

     

    -- SP_EXECUTESQL

    DECLARE @SQL NVARCHAR(200)

    SET @SQL = 'SELECT * FROM TMP WHERE Nome = @Var'

    EXEC SP_EXECUTESQL @SQL, N'@Var VarChar(200)', @Var = 'X'

     

    -- Procedure

    -- Criei a proc utilizando a clausula WITH RECOMPILE

    -- mas esse assunto fica pra uma 2 Parte do post

    CREATE PROC st_Teste @Var VarChar(200)

    WITH RECOMPILE AS

    SELECT * FROM TMP

    WHERE Nome = @var

    GO

    EXEC st_Teste @Var = 'A'

     

    -- HINT - RECOMPILE

    DECLARE @var VarChar(200)

    SET @var = 'X'

    SELECT * FROM TMP

    WHERE Nome = @var

    OPTION(RECOMPILE)

     

    _________________________________________________________________
    Fabiano Neves Amorim
    Análise - NewCon Enterprise
    * fabiano@cnpm.com.br - http://fabianosqlserver.spaces.live.com/

    February 29

    SQL Server 2008 VS 29 de Fevereiro

    Eu gosto muito da Microsoft, seus produtos são muito bons e tal mas de vez em quanto(ta bom vai, direto) ela da uns foras que não tem desculpa.

     

    Estou utilizando o SQL Server 2008 CTP6 versão de Fevereiro que foi lançada esses dias, e hoje meu SQL Server parou de Iniciar pois da um erro na inicialização no serviço da engine.

     

    Segue o Log do erro:

    2008-02-29 14:19:40.42 Server      Error: 17190, Severity: 16, State: 1.

    2008-02-29 14:19:40.42 Server      FallBack certificate initialization failed with error code: 5.

    2008-02-29 14:19:40.56 Server      Unable to initialize SSL encryption because a valid certificate could not be found, and it is not possible to create a self-signed certificate.

    2008-02-29 14:19:40.60 Server      Error: 17182, Severity: 16, State: 1.

    2008-02-29 14:19:40.60 Server      TDSSNIClient initialization failed with error 0x80092004, status code 0x80. Reason: Unable to initialize SSL support.

    2008-02-29 14:19:40.60 Server      Error: 17182, Severity: 16, State: 1.

    2008-02-29 14:19:40.60 Server      TDSSNIClient initialization failed with error 0x80092004, status code 0x1. Reason: Initialization failed with an infrastructure error. Check for previous errors.

    2008-02-29 14:19:40.60 Server      Error: 17826, Severity: 18, State: 3.

    2008-02-29 14:19:40.60 Server      Could not start the network library because of an internal error in the network library. To determine the cause, review the errors immediately preceding this one in the error log.

    2008-02-29 14:19:40.63 Server      Error: 17120, Severity: 16, State: 1.

    2008-02-29 14:19:40.63 Server      SQL Server could not spawn FRunCM thread. Check the SQL Server error log and the Windows event logs for information about possible related problems.

     

    Depois de pesquisar um pouco na internet descobri que as CTPs do SQL 2008 não funcionam no dia 29 de Fevereiro, como o dia de hoje só ocorre a cada 4 anos recomendaram que não usemos o SQL hoje.

     

    Segue o texto e o link de palavras do Sr. Christian Kleinerman (Microsoft) Group Program Manager SQL Server 2008 que fala um espanhol muito bom por sinal, assisti uma palestra dele falando sobre o SQL 2008 no evento da Solid Quality em SP no ano passado.

     

    “We have recently discovered an issue with SQL Server 2008 CTPs that result in SQL Server 2008 not starting or installing on Feb 29 GMT only. We recommend that you do not run, install or upgrade this CTP on Feb 29 GMT to minimize any impact in your environment. You can install starting on March 1 GMT. If you have an immediate issue that cannot wait until march 1st GMT contact csskat@microsoft.com before taking any further steps.”

     

    Ta bom eu sei que é uma versão para testes e tudo mais só que um erro desse me parece meio que culpa de algum estagiário(porque é sempre culpa deles?) que desenvolveu o código.

     

    Ainda bem que procurei sobre o problema no www.google.com.br e não no www.live.com/?searchonly=true pois vai que hoje as buscas no live não retornem muito bem! Vocês podem achar que estou exagerando mas isso é só porque vocês não tem o SQL 2008 instalado em suas máquinas, Fico pensando o que pode acontecer com no Halloween ou alguma sexta-feira 13 que venha por ai. J

    February 18

    XML Notepad

    Semana passada baixei o XML Notepad da Microsoft, me surpreendi com a qualidade e simplicidade do software. Esse merece ir para a lista de utilitários.

     

    XML_Notepad

    February 13

    SysProcesses

    Hoje fiz um comando SQL para saber quem está gastando mais recursos do banco SQL, o comando é legal porque mostra o SQL que está sendo executado pela sessão.

     

    SELECT

      Hostname,

      DB_Name(DBID) Banco,

      Blocked,

      CPU,

      Physical_IO,

      MemUsage,

      (SELECT TEXT FROM ::fn_get_sql(SQL_Handle)) AS Comando_SQL,

      Login_time,

      Last_Batch,

      Open_Tran,

      Program_Name

      FROM master.dbo.sysprocesses

     WHERE spid > 50

     ORDER BY Physical_IO desc, cpu DESC

     

    Obs.: Só roda no SQL Server 2005 L

    February 08

    Evento Online de SQL Server

    Evento online que aconteceu a alguns dias atrás, tem várias WebCasts de SQL Server falando sobre assuntos variados, vale a pena reservar um tempo para dar uma olhada.

     

    http://events.unisfair.com/index.jsp

    _________________________________________________________________
    Fabiano Neves Amorim
    Análise - NewCon Enterprise
    * fabiano@cnpm.com.br - http://fabianosqlserver.spaces.live.com/

    ( (55 - 14) 3404-3700

    February 06

    Business Intelligence Microsoft

    Business Objects vendida pra a SAP por $6,8 bi e a Cognos vendida para a IBM por $5,0 bi, e qual foi o resultado?

     

    Microsoft líder em vendas de Business Intelligence,

     

    http://www.informationweek.com/news/showArticle.jhtml?articleID=206104502&subSection=News

    PerfMom e Profiler

    Hoje no newsletter da SQLTeam.com recebi o link para um artigo falando sobre a integração entre o PerfMom e o Profiler 2005.

    Eu já tinha visto o Javier Loría da Solid Quality abrir um log do Performance Monitor no Profiler do SQL Server 2005, assim como usar o Profiler para capturar os códigos MDXs gerados pelo Analysis Services, quem já usou o OWC e sempre quis ver o MDX que o componente gerava fica ai a dica, voltando ao PerfMom, após ler o artigo e fazer alguns testes na minha máquina cheguei a seguinte conclusão.

     

    DBAs só não fazer o trabalho deles por 3 motivos:

    1.       Porque não querem.

    2.       Porque não sabem.

    3.       Porque gostam de culpar os desenvolvedores pelos problemas de performance J.

     

    Como diria a Delaney, Have Fun!

    January 31

    Status Backup\Restore

    Hoje usei um script bem legal que peguei no Blog do MVP SQL Server Junior Galvão.

     

    Ele retorna varias informações sobre um backup ou restore que está acontecendo no banco.

     

    SELECT command,

           Dateadd(ms,estimated_completion_time,Getdate()) AS Hora_Estimada_ParaTermino,

           estimated_completion_time / 1000 AS Segundos_Para_Termino,

           estimated_completion_time / 1000 / 60 Minutos_Para_Termino,

           start_time AS Inicio_do_Comando,

           percent_complete AS Percentual_Completo

     FROM sys.dm_exec_requests

    WHERE session_id = <Numero da sessão que está rodando o comando>

     

    Thanks Junior.

    __________________________________________________________________
    Fabiano Neves Amorim
    Análise - NewCon Enterprise
    * fabiano@cnpm.com.br - http://fabianosqlserver.spaces.live.com/

    ( (55 - 14) 3404-3700

    Ainda sobre TempDB

    Ainda falando sobre o TempDB, hoje li um artigo escrito pelo Paul Randal, vale a pena gastar um tempo para ler.

     

    Achei bem legal ele falando que mesmo quando se trata do mesmo assunto existem várias idéias e opiniões diferentes por parte dos funcionários da Microsoft. Portanto não devemos SEMPRE confiar em tudo que eles dizem ou escrevem.

     

    Fiquei imaginando a cena, o cara querendo convencer o Paul de que o DBCC CHECKDB é isso e não é aquilo, e ele simplesmente respondendo, 'I wrote that code - I'm afraid you *are* wrong'. (Eu escrevi o código – Temo que você está errado)

     

    Obs.: “I’m afraid” segundo o Concise Oxford Dictionary significa pedir desculpas educadamente, rs senti uma certo sarcasmo na frase.

     

    Segue o link para o “Debate” sobre criar ou não vários arquivos para o TempDB.

     

    http://www.sqlservercentral.com/Forums/Topic448122-361-2.aspx

     

    __________________________________________________________________
    Fabiano Neves Amorim
    Análise - NewCon Enterprise
    * fabiano@cnpm.com.br - http://fabianosqlserver.spaces.live.com/

    ( (55 - 14) 3404-3700

    January 29

    TempDB

    Em relação a performance um problema muito comum é a contenção no TempDB.

    O Banco de dados TempDB é utilizado para várias operações, ao invés de escrever aqui quais são estas operações eu vou de CTRL-C + CTRL+V do Books Online que é mais facil.

    The tempdb system database is a global resource that is available to all users connected to the instance of SQL Server and is used to hold the following:

    ·         Temporary user objects that are explicitly created, such as: global or local temporary tables, temporary stored procedures, table variables, or cursors.

    ·         Internal objects that are created by the SQL Server Database Engine, for example, work tables to store intermediate results for spools or sorting.

    ·         Row versions that are generated by data modification transactions in a database that uses read-committed using row versioning isolation or snapshot isolation transactions.

    ·         Row versions that are generated by data modification transactions for features, such as: online index operations, Multiple Active Result Sets (MARS), and AFTER triggers.

     

    No site do TechNet tem um excelente artigo falando sobre o TempDB no SQL Server 2005, segue o link Working with tempdb in SQL Server 2005.

     

    No artigo acima podemos observar em “Troubleshooting contention caused by to DML operations” que quando temos problema de performance no TempDB é recomendado criar um arquivo de dados para cada processador existente no servidor e habilitar o trace flag 1118.

    Obs.: Neste caso um processador Dual Core é considerado como 2 processadores, ou seja, criar 2 arquivos.

     

    Conforme o artigo Q328551 a mesma recomendação também foi feita pela Microsoft para servidores rodando SQL Server 2000.

     

    No TechEd 2007 participei de uma mesa redonda sobre Otimização de Performance com o SQL Server 2005 - Dicas e Truques e discutimos sobre o TraceFlag 1118, e os profissionais da Microsoft presentes reforçaram o que os artigos acima recomendam.

    Lembro do Fabricio Catae Premier Field Engineer dizer o seguinte:

    ·         Por precaução e para não ter futuros problemas nós recomendamos o uso do TraceFlag 1118 e criamos um arquivo do TempDB para cada processador.

     

    Concordo com ele, na duvida se o TempDB está sendo um problema ou não, antes de ter problema habilite o TraceFlag e crie um arquivo para cada processador do servidor.

     

    Antes de continuar a falar sobre isso vamos entender um pouco mais sobre os Extents pois eles tem tudo a ver com o TraceFlag 1118.

     

    No SQL Server existem dois tipos de extents, os extents mistos e extents uniformes.

     

    Assim como o extent uniforme o extent misto tem 64 kb(8 páginas de 8k). A diferença entre eles é que um extent misto pode ter páginas de mais de um objeto diferente. Veja o exemplo abaixo.

     

     Extent

     

    Repare que no Extent Misto temos informações de vários objetos, Table2, Index1, Index2 e etc...

     

    O SQL Server usa 2 tipos de controladores para alocação dos extents, o GAM e o SGAM.

     

    ·         GAM - Para os extents uniformes o SQL irá usar a Global Allocation Map que é responsável por registrar as alocações dos extents uniformes de todos os objetos do banco de dados. O GAM usa um bit para controlar se o extent está livre ou alocado. Se o bit valer 1 o extent está livre se o bit valor 0 o extent está alocado.

     

    ·         SGAM - Para os extents mistos o SQL irá usar a Shared Global Allocation Map como o próprio nome diz “Shared” ele é responsável por registrar quais são os extents mistos e qual deles tem páginas livres para uso. O SGAM também usa um bit para fazer este controle, se o bit for 1 então o extent é misto e tem pelo menos uma página livre para utilização, se o bit for 0 então o extent não é um extent misto ou então todas as páginas já estão utilizadas.

     

    Vamos imaginar o seguinte cenário, um simples insert um uma nova tabela temporária.

    Todo objeto criado no banco de dados é iniciado com um extent misto até que a tabela ocupe 8 páginas de dados, sabendo disso então podemos dizer que o SQL irá alocar um extend misto para gravar minha informação nesta tabela temporária. Para chegar neste extent misto e na página onde minha informação será gravada o DataBase Engine usa a controladora SGAM.

     

    Ok já sei que o SQL aloca extents mistos para novos objetos, mas o que isso tem a ver com o TraceFlag e o TempDB?

    Tem a ver que quando habilitamos o TraceFlag 1118 o SQL passará a não mais alocar extents mistos e sim extents uniformes, pulando a etapa que usa a SGAM para achar um extent misto.

     

    Podemos observar que o SQL tem o trabalho de alocar extents mistos e conforme a tabela vai crescendo o SQL tem controlar se no extent atual cabe os dados que estão sendo inseridos ou se ele precisa alocar um novo extent,isso irá acontecer quando a tabela ficar maior que 64 k, para fazer este controle o SQL irá utilizar a SGAM e a PFS(controla o espaço livre dentro de uma página). Ao alocar direto um extend uniforme o SQL não precisa toda hora consultar a SGAM e a PFS para verificar se ele necessita alocar outro extent para inserir os dados.

     

    O Luciano Moreira deu um show de explicação sobre como, quando, onde e porque utilizar o TraceFlag 1118 o artigo pode ser lido em Analisando o trace flag 1118.

     

    Um detalhe importante é que recentemente(não tão recente assim) a Microsoft divulgou um artigo dizendo que ao fazer o que ela diz você pode ter problemas de desempenho. O artigo pode ser lido aqui Q936185,

     

    O MVP Linchi Shea escreveu um artigo no seu blog falando sobre esse problema, Reduce the Contention on tempdb with Trace Flag 1118: To Enable, or Not to Enable?

     

    Até a próxima e stay tuned!

     

    ______________________________________________________________
    Fabiano Neves Amorim (MCP - MCTS - SQL Server)
    Análise - NewCon Enterprise
    * fabiano@cnpm.com.br - http://fabianosqlserver.spaces.live.com/

    January 17

    Upgrade SQL Server

    Ao migrar de SQL 2000 para SQL 2005 existem algumas considerações importantes no Transact-SQL que necessitam de nossa atenção, por exemplo existem alguns comandos que hoje estão rodando normalmente no SQL 2000 e ao migrar simplesmente não funcionam, vamos a alguns exemplos.

     

    /*

      Exemplo de sintaxes que não funcionam ao migrar de SQL 2000 para 2005

    */

     

    use master

     

    if exists(select * from master.dbo.sysdatabases where name = 'TesteCompatibilidade')

    begin

      drop database TesteCompatibilidade

    end

     

    create database TesteCompatibilidade

     

    use TesteCompatibilidade

     

    if OBJECT_ID('teste') is not null

    begin

      drop table teste

    end

    GO

     

    -- Vamos criar uma tabela para teste

    create table teste (id int Identity(1,1), nome VarChar(200) default NEWID())

    GO

     

    -- Vamos incluir 10 linhas na tabela teste

    insert into teste values(default)

    go 10

     

    create index ix_teste on teste(nome)

    GO

     

    -- Seta o banco para usar o nivel de compatibilidade do SQL Server 2000(80)

    -- Consultas que funcionam normalmente no SQL Server 2000

    sp_dbcmptlevel TesteCompatibilidade, 80

    GO

    --Consulta forcando o uso de um indice sem colocar o WITH depois do nome da tabela

    select * from teste(index=ix_teste)

    GO

    -- Consulta usando order by baseado em uma coluna, porem o alias ID é do mesmo nome da coluna ID

    -- repare que o SQL ordenou a tabela pelo campo ID da tabela e não pelo alias ID(campo nome)

    select ID, Nome as ID  from teste order by ID

    GO

    --Consulta usando order by passando o Nome da tabela mais o alias da coluna

    select ID, Nome as AliasParaNome  from teste order by teste.AliasParaNome

    GO

     

    -- Marca o banco para usar o nivel de compatibilidade do SQL Server 2005

    -- Os mesmo selects efetuados acima não funcionan no SQL Server 2005

    sp_dbcmptlevel TesteCompatibilidade, 90

    GO

    --Consulta forcando o uso de um indice sem colocar o WITH depois do nome da tabela

    select * from teste(index=ix_teste)

    GO

    -- Consulta usando order by baseado em uma coluna, porem o alias ID é do mesmo nome da coluna ID

    -- repare que o SQL ordenou a tabela pelo campo ID da tabela e não pelo alias ID(campo nome)

    select ID, Nome as ID  from teste order by ID

    GO

    --Consulta usando order by passando o Nome da tabela mais o alias da coluna

    select ID, Nome as AliasParaNome  from teste order by teste.AliasParaNome

     

    A Microsoft disponibiliza um software que ajuda a evitar surpresas como estas, ele se chama “Microsoft SQL Server 2005 Upgrade Advisor” vale a pena baixar o software e rodar ele na sua base para ver as incompatibilidades. Também vale a pena dar uma lida no “SQL Server 2005 Upgrade Technical Reference Guide”.

     

    _________________________________________________________________
    Fabiano Neves Amorim (MCP - MCTS - SQL Server)
    Análise - NewCon Enterprise
    * fabiano@cnpm.com.br - http://fabianosqlserver.spaces.live.com/

    ( (55 - 14) 3404-3700

    January 07

    Data Types

    Em relação a performance uma falha muito encontrada em banco de dados SQL Server é a má escolha dos datatypes das colunas.

     

    É muito importante conhecer e escolher corretamente os datatypes das colunas nas suas tabelas por causa dos tamanho que elas ocupam  no seu banco de dados, por exemplo vamos imaginar o seguinte cenário.

     

    create table testeA (ID   BigInt Primary Key,

                         Nome NChar(80),

                         Data DateTime,

                         Ano  Int,

                         Mes  Int)

     

    /*

    ID - BigInt = 8 bytes

    Nome NChar() = 160 bytes(2 bytes para cada caracter unicode)

    Data - DateTime = 8 bytes

    Ano - Int = 4 bytes

    Mes - Int = 4 bytes

    Total = 184 bytes de dados por linha.

    */

     

    -- Esta mesma tabela poderia ter a seguinte estrutura

     

    create table testeB (ID   Int Primary Key,

                         Nome Char(80),

                         Data SmallDateTime,

                         Ano  SmallInt,

                         Mes  TinyInt)

     

    /*

    ID - Int = 4 bytes

    Nome Char() = 80 bytes(1 bytes para cada caracter)

    Data - DateTime = 4 bytes

    Ano - SmallInt = 2 bytes

    Mes - TinyInt = 1 byte

    Total = 91 bytes de dados por linha.

    */

     

    Uma página de dados do SQL Server tem uma tamanho fixo de 8k,

    ou 8192 bytes sendo distribuidos entre 3 partes: Page Header 96 bytes, Data Rows e Row OffSet Array.

     

    Page Structure

     

    Considerando que o SQL Server tem 8096 bytes livres(8192 - 96) para armazenamento dos dados da minha tabela e o ArrayOffSet(2 bytes) podemos dizer que dentro de cada página da minha tabela testeA temos 43 linhas de dados gravados, já na tabela testeB temos 87 registros.

     

    Ou seja, a tabela testeB ocupa a metade do tamanho da tabela testeA, porque isso é importante? Vamos fazer alguns testes de select.

     

    -- Primeiro vamos incluir 5000 registros em cada tabela

    set NoCount on

    declare @i Int

    set @i = 0

    while @i < 5000

    begin

      insert into testeA(ID, Nome, Data, Ano, Mes)

      values(@i, 'Fabiano - ' + Convert(VarChar, @i), GetDate(), Year(GetDate()), Month(GetDate()))

      insert into testeB(ID, Nome, Data, Ano, Mes)

      values(@i, 'Fabiano - ' + Convert(VarChar, @i), GetDate(), Year(GetDate()), Month(GetDate()))

      set @i = @i + 1

    end

     

    -- Repare que o SQL alocou 122 paginas para a tabela

    DBCC SHOWCONTIG(TesteA)

    --- Pages Scanned................................: 122

     

    -- Podemos fazer a seguinte conta para verificar qual o tamanho da tabela

    -- 122 páginas vezes o tamanho ocupado por cada pagina / 1024 para converter o tamanho em Kb

    select (122 * 8192) / 1024 -- 976

    -- Confirmando o tamanho da tabela

    sp_spaceUsed TesteA -- Data = 976

     

    --Vamos fazer um select na tabela TesteA e verificar quantos IOs são feitos para retornar 1/5 da tabela.

    set statistics io on

    select * from testeA

    where ID < 1000

    -- Table 'testeA'. Scan count 1, logical reads 26, physical reads 0, read-ahead reads 0.

     

    -- Vamos verificar o mesmo select na tabela TesteB,

    -- Observe que o Select na tabela testeB fez bem menos leitura para retornar os dados

    -- obtendo assim ganho de performance.

    select * from testeB

    where ID < 1000

    -- Table 'testeB'. Scan count 1, logical reads 14, physical reads 0, read-ahead reads 0.

     

    A escolha dos data types são muito importantes para evitar IOs desnecessários e evitar que seu banco de dados se torne muito grande rapidamente.

    No projeto REAL da Microsoft, a equipe de desenvolvimento opotu por definir os campos do tipo Data como Int para poder ganhar 4 bytes de diferença entre o DateTime e o Int, a data é gravada no formato ‘yyyymmdd’.

     

    Vale lembrar tambem que no SQL Server 2005 Enterprise service pack 2 existe o campo VarDecimal que vem para substituir os campos Decimal e Numeric. Você pode ler mais sobre ele no link vardecimalStorage.docx

     

    Fica a seguinte regra: “Quanto mais dados em menos páginas, menos leitura para retornar mais dados”. J

     

    __________________________________________________________________
    Fabiano Neves Amorim (MCP - MCTS - SQL Server)
    Análise - NewCon Enterprise
    * fabiano@cnpm.com.br - http://fabianosqlserver.spaces.live.com/

    SQL Server 2008

    Novidades no Transact SQL do SQL Server 2008,

     

    -- Criar uma tabela para teste

    create table TabNome(Nome VarChar(80), SobreNome VarChar(80))

     

    -- Incluir 4 linhas na tabela

    -- Existe uma nova Sintax no SQL 2008 que permite incluir varias linhas com apenas um comando Insert.

    -- Commando relativo ao SQL Server 2008

    insert into TabNome(Nome, Sobrenome)

    values('Fabiano','Amorim'),('Ricardo','Lopes'),('Eduardo','Silva'),('Maria','Oliveira')

    -- Commando relativo ao SQL Server 2000, 2005

    insert into TabNome(Nome, Sobrenome)

    values('Fabiano','Amorim')

    insert into TabNome(Nome, Sobrenome)

    values('Ricardo','Lopes')

    insert into TabNome(Nome, Sobrenome)

    values('Eduardo','Silva')

    insert into TabNome(Nome, Sobrenome)

    values('Maria','Oliveira')

     

    -- É possivel fazer um select passando uma lista como tabela segue um exemplo,

    -- repare que o Alias do nome das colunas é passado após o nome da tabela.

    select TabNome.*, Tab.Apelido

    from TabNome

    inner join (

      values('Fabiano','Amo'),('Ricardo','Lop'),('Eduardo','Sil'),('Maria','Oli')

               ) AS tab (Nome, Apelido)

    ON TabNome.Nome = tab.Nome;

     

    __________________________________________________________________
    Fabiano Neves Amorim (MCP - MCTS - SQL Server)
    Análise - NewCon Enterprise
    * fabiano@cnpm.com.br - http://fabianosqlserver.spaces.live.com/

    January 03

    Casa Windows

    Até que ficou legal eim !
     
     
    Casa Windows
     

    SET VS SELECT

    É muito comum durante o desenvolvimento de um código SQL necessitarmos zerar o valor das variáveis que serão utilizadas no código.

    Uma dica em relação a performance é que o comando SELECT é mais rápido do que o SET, porém está regra só se aplica quando podemos substituir um bloco de SET por um SELECT por ex:

     

    -- Executa um loop zerando o valor de 10 variaveis utilizando SET

    -- Gere o plano de execução e repare que no plano de execução que no Loop o SQL gera um SELECT para cada comando SET.

    DECLARE @i Int, @Test1 int, @Start datetime

    DECLARE @V1 Char(6),

            @V2 Char(6),

            @V3 Char(6),

            @V4 Char(6),

            @V5 Char(6),

            @V6 Char(6),

            @V7 Char(6),

            @V8 Char(6),

            @V9 Char(6),

            @V10 Char(6);

     

    SET @Test1 = 0

    SET @i = 0

    SET @Start = GetDate()

    WHILE @i < 50000

    BEGIN

      SET @V1 = ''

      SET @V2 = ''

      SET @V3 = ''

      SET @V4 = ''

      SET @V5 = ''

      SET @V6 = ''

      SET @V7 = ''

      SET @V8 = ''

      SET @V9 = ''

      SET @V10 = ''

          SET @i = @i + 1                  

    END                               

    SET @Test1 = DATEDIFF(ms, @Start, GetDate())

    SELECT @test1

     

    GO

    -- Executa um loop zerando o valor de 10 variaveis utilizando SELECT

    -- Diferente do primeiro plano de execução o SQL gerou apenas 1 instrução para setar os valores para as 10 variáveis.

    DECLARE @i Int, @Test1 int, @Start datetime

    DECLARE @V1 Char(6),

            @V2 Char(6),

            @V3 Char(6),

            @V4 Char(6),

            @V5 Char(6),

            @V6 Char(6),

            @V7 Char(6),

            @V8 Char(6),

            @V9 Char(6),

            @V10 Char(6);

     

    SET @Test1 = 0

    SET @i = 0

    SET @Start = GetDate()

    WHILE @i < 50000

    BEGIN

    SELECT @V1 = '',

           @V2 = '',

           @V3 = '',

           @V4 = '',

           @V5 = '',

           @V6 = '',

           @V7 = '',

           @V8 = '',

           @V9 = '',

           @V10 = '',

           @i = @i + 1;

    END                               

    SET @Test1 = DATEDIFF(ms, @Start, GetDate())

    SELECT @test1

     

    Uma outra observação importante é que o SQL usou muito mais CPU no primeiro comando do que no segundo vejamos o profiler.

     

     imagem

     

    Fica a dica de utilizar o comando SELECT para atribuir valor a um bloco de variáveis.

     

    Segue o link de um excelente artigo sobre o assunto.

    http://vyaskn.tripod.com/differences_between_set_and_select.htm

     

    __________________________________________________________________ 
    Fabiano Neves Amorim (MCP - MCTS - SQL Server)
    Análise - NewCon Enterprise
    * fabiano@cnpm.com.br - http://fabianosqlserver.spaces.live.com/

    ( (55 - 14) 3404-3700

    December 17

    Tipo de Joins no SQL Server

    Semana passada participei de um treinamento de SQL Server na empresa em que falamos sobre execution plan, durante o treinamento surgiram alguns operadores de joins em nossas consultas de exemplo e percebi a necessidade de falar mais sobre alguns tipos de joins, ou seja, assunto para o Blog.

     

    Estou lendo o livro SQL Server 2005 Query Tunning and Optimization e vou aproveitar os excelentes exemplos e explicações de como os operadores de join funcionam.

     

    Vamos nos basear na seguinte consulta para os exemplos:

     

    Select codigo from tabela1

    Inner join tabela2

      On tabela1.id = tabela2.id

    Where tabela1.id = 10

      

    Nested Loops Joins – Compara cada linha de uma tabela(também conhecido por OUTER TABLE(FROM)) com cada linha de outra tabela(também conhecido por inner table) procurando pelas linhas que satisfaçam o relacionamento.

    Pseudo-Código:

     

    for each row tabela1 in the outer table

      for each row tabela2 in the inner table

         if tabela1 joins with tabela2

            return(tabela1, tabela2)

     

    Merge Join  - Trabalha em simultâneo lendo e comparando cada linha das duas tabelas ordenadas pelo relacionamento. Para cada etapa compara a linha atual para cada valor na tabela do join, se o valor for igual a entrada então ele retorna o join e continua a comparação. Se o valor não for igual ele descarta o resultado e continua a próxima leitura. Um Merge Join não necessariamente irá ler toda a tabela, quando a leitura chegar no fim de qualquer tabela envolvida no join o loop é parado.

    Pseudo-Código:

     

    Get first row tabela1 from input 1

    Get first row tabela2 from input 1

    While not at the end of either input

    Begin

      If tabela1 joins with tabela2

      Begin

        Output(tabela1, tabela2)

        Get next row tabela2 from input 2

      end

      else if tabela1 < tabela2

        get next row tabela1 from input 1

      else

        get next row tabela2 form input 2

    end

     

    Hash Join – O hash join compartilha alguns conceitos do Merge Join, o algoritmo de Hash é executado em duas fases conhecidas por Build(Construção) e Probe(prova ou experimento). Durante a fase de Build o SQL irá ler todas as linhas do primeiro input(from) e criar uma tabela com os dados lidos mais o relacionamento gerando uma Hash Table em memória. Na fase do probe o SQL irá ler cada linha do segundo input(tabela do join) gerar o valor hash e verificar se o seu valor tem o mesmo valor da tabela Hash table recém criada retornando todas as linhas que tem o mesmo valor. É importante destacar que não necessariamente a Hash table sempre será gerada em memória pois o SQL irá reservar a quantidade de memória necessária para criação da tabela antes de cria-lá baseado nas statisticas geradas pelo otimizador. Caso durante e build o SQL necessite de mais memória para gerar a tabela Hash ele pode gravar os dados em um espaço temporário ou seja, TempDB.

    Pseudo-Código:

     

    For each row tabela1 in the build table

    Begin

      Calculate hash value on tabela1 join key(s)

      Insert tabela1 into the appropriate hash bucket

    End

    For each row tabela2 in the probe table

    Begin

      Calculate hash value on tabela2 join key(s)

      For each row tabela1 in the corresponding hash bucket

        If tabela1 with tabela2

          Ouput (tabela1, tabela2)

    end

     

    Cabe a dica que para os seus testes você pode forcar o SQL a usar um determinado tipo de join se os exemplos:

     

    SELECT Tabela1.ID

    FROM Tabela1

    INNER LOOP JOIN Tabela2

      ON Tabela1.ID = Tabela2.ID

    WHERE Tabela1.ID = 10

    GO

    SELECT Tabela1.ID

    FROM Tabela1

    INNER MERGE JOIN Tabela2

      ON Tabela1.ID = Tabela2.ID

    WHERE Tabela1.ID = 10

    GO

    SELECT Tabela1.ID

    FROM Tabela1

    INNER HASH JOIN Tabela2

      ON Tabela1.ID = Tabela2.ID

    WHERE Tabela1.ID = 10

     

    Fonte – Inside SQL Servr 2005: Query Tunning and Optimization.

    __________________________________________________________________
    Fabiano Neves Amorim (MCP - MCTS - SQL Server)
    Análise - NewCon Enterprise
    * fabiano@cnpm.com.br - http://fabianosqlserver.spaces.live.com/

    ( (55 - 14) 3404-3700

    December 11

    Indices

    Primeiramente quero agradecer ao Sanches pelas dicas sobre os Posts, Fiquem a vontade para me enviar criticas ou sugestões para posts.

     

    Esta semana fiz uma alteração em uma tabela do no nosso banco de dados onde mudei a ordem das colunas do meu índice cluster, isso pode ser muito útil caso as consultas que são executadas nesta tabela tenham as colunas do índice cluster no where. Vamos a um exemplo prático.

     

    --Vamos criar uma tabela para teste

    drop table teste

    create table teste (ID   Int Identity(1,1) NOT NULL,

                        Nome VarChar(200) NOT NULL,

                        Data DateTime)

     

    alter table teste add constraint xpk_Nome_ID primary key (Nome, ID)

    go

     

    set nocount on

    declare @i Int

    set @i = 0

     

    while @i < 1000

    begin

      insert into teste (nome,data) values('A', GetDate())

      insert into teste (nome,data) values('B', GetDate())

      insert into teste (nome,data) values('C', GetDate())

      insert into teste (nome,data) values('D', GetDate())

      insert into teste (nome,data) values('E', GetDate())

      set @i = @i + 1;

    end

     

    -- Vamos imaginar a seguinte consulta

    -- Repare que o SQL não consegue fazer o seek porque a tabela está ordenada pela coluna Nome e não pela coluna ID

    select * from teste

    where id = 10

     

    --Apaga a primary key

    alter table teste drop constraint xpk_Nome_ID

    GO

    --Recria a primary key ordenado por ID e Nome

    alter table teste add constraint xpk_ID_Nome primary key (ID, Nome)

     

    -- Ao executar a consulta novamente conseguimos um seek.

    select * from teste

    where id = 10

     

    Claro que isso depende de um estudo bem feito das consultas que são executadas na sua tabela, bem como a alteração dos valores das colunas(UPDATE) porque uma má alteração poderia causar fragmentação na tabela, mas fragmentação é assunto para um outro post.

     

    Valeu

    __________________________________________________________________
    Fabiano Neves Amorim (MCP - MCTS - SQL Server)
    Análise - NewCon Enterprise
    * fabiano@cnpm.com.br - http://fabianosqlserver.spaces.live.com/

    ( (55 - 14) 3404-3700

    December 04

    Statisticas e Plano de Execução

    Semana passada me deparei com um problema de performance de uma procedure chamada pela minha aplicação Win32 exibindo uma mensagem de TimeOut para o usuário. Somente após as estatísticas das tabelas serem atualizadas é que o processo conseguiu rodar.

     

    Vamos analisar um pouco o problema.

     

    Primeira pergunta: O que são e para que servem as estatísticas das tabelas?

     

    O SQL Server guarda informações em relação aos registros das tabelas para que o Otimizador possa decidir qual o melhor plano de execução para uma determinada consulta baseado nos dados que ele lê nas statistics das tabelas.

    Vamos a um exemplo:

     

    use
    MASTER
    GO
    create
    database INSIDE
    GO
    --Altera a propriedade Auto_Update_Statistics para OFF
    alter
    database INSIDE set AUTO_UPDATE_STATISTICS off
    GO
    use
    INSIDE
    GO
    --Vamos criar uma tabela para teste
    drop
    table teste
    create
    table teste (id Int Identity(1,1), Nome VarChar(200))
    set
    nocount on
    declare
    @i Int
    set
    @i = 0
    while
    @i < 1000
    begin
    insert into teste (nome) values('A')
    insert into teste (nome) values('B')
    insert into teste (nome) values('C')
    insert into teste (nome) values('D')
    insert into teste (nome) values('E')
    set @i = @i + 1;
    end
    while
    @i < 10000
    begin
    insert into teste (nome) values('A')
    set @i = @i + 1;
    end
    create
    index ix_teste on teste(nome)
    --Vamos ver as Statistics criadas pelo indice
    DBCC
    SHOW_Statistics(teste, ix_teste)
    -- Faz um Table Scan pois ele lê nas Statistics que é o melhor meio de acesso aos dados, a consulta irá retornar 1000 registros
    -- Repare que na propriedade Estimeted Number Rows do Execution Plan o SQL já sabe quantas linhas a consulta irá retornar.
    select
    * from teste
    where
    nome = 'A'
     
    --Vamos incluir uma massa de dados para o valor X
    declare
    @i Int
    set
    @i = 0
    while
    @i < 10000
    begin
    insert into teste (nome) values('X')
    set @i = @i + 1;
    end
    -- Repare que o SQL criou um novo plano de execução para a consulta abaixo. Sendo que ele deveria gerar um table scan como no primeiro SQL.
    -- ele não gerou porque as statistics estão desatualizadas repare que atualizando as statistics e executando a consulta novamente o SQL escolhe o melhor plano
    -- Podemos concluir que o deixar as statistics desatualizadas pode levar o otimizador a um mal plano de execução.
    -- Verificar a propriedade Estimeted Number Rows do Execution Plan.
    select
    * from teste
    where
    nome = 'X'
    --Vamos atualizar as statistics
    UPDATE
    STATISTICS teste
    GO
    --Faz um full Scan
    select
    * from teste
    where
    nome = 'X'

     

     

    Segunda pergunta: Como identificar o problema?

     

    Identifiquei que o problema era um mal plano de execução gerado pelo SQL de uma consulta feita dentro da procedure que retornava os dados do processo.

    Esta consulta fazia vários joins com tabelas com mais de 15 milhões de registros e o SQL Server estava fazendo um Index Scan em quase todas estas tabelas.

     

    Rodei a procedure no SSMS com a opção para exibir o Plano de Execução (CTRL+M) ligada e tive o seguinte resultado.

     

     Plano de Execução

    Observando a imagem acima podemos identificar que as querys 5  e 6 são responsáveis pelo gargalo da procedure então me limitei em analisar estas consultas.

     

    Após analisar as consultas constatei que o SQL Server não estava gerando um bom plano de execução para as consultas já que ele estava gerando vários Index Scan ao invés de Seeks.

     

    Terceira pergunta: Como resolver o problema?

    Após identificar que o problema era o plano de execução temos várias maneiras de forçar o SQL Server a gera determinado plano por exemplo com o uso de Hints, no meu caso achei melhor criar um cursor e dentro de cada linha do cursor eu executo a instrução SQL mas passando uma condição bem restritiva no WHERE baseado no valor do cursor fazendo o SQL Server gerar um plano de execução com vários Seeks o que foi bem melhor que apenas um Index Scan.

     

    Fiz mais outras alterações como trocar os DataTypes das tabelas temporárias usadas na procedure DateTime para SmallDateTime, Int para SmallInt ou TyniInt, SET NOCOUNT ON dentre outros, mas o ideal é sempre melhorar o gargalo.

     

    A melhora foi muito grande, vamos analisar os resultados no profiler.

     

     Profiler

    Conclusão de 2:20 minutos para 0. J

     

    Verificamos então como identificar o problema, fica a dica sobre Statistics e a analise dos planos de execução utilizando o CTRL-M, é claro que isso não funciona sempre mas ajuda.

     

    __________________________________________________________________
    Fabiano Neves Amorim (MCP - MCTS - SQL Server)
    Análise - NewCon Enterprise
    * fabiano@cnpm.com.br - http://fabianosqlserver.spaces.live.com/

    ( (55 - 14) 3404-3700

    November 30

    Primeiro Post

    Meu primeiro Post no Blog.

     

    Senhoras e senhores, inicia aqui mais um Blog sobre tecnologia, mais especificamente sobre SQL Server.

     

    Minha intenção é colocar no blog dicas sobre o uso do SQL Server e fatos do dia a dia que podem ser de interesse dos leitores.

    Sem mais delongas vamos ao que interessa SQL. :-)

     

    Vai aqui uma dica muito boa para quem trabalha com o SQL e pode ser útil do dia a dia.

     

    --Vamos criar uma tabela de exemplo

    IF Object_ID('teste') IS NOT NULL

      DROP TABLE teste

    GO

    CREATE TABLE teste (ID   Int Identity(1,1),

                        Nome VarChar(200))

    GO

     

    -- Agora digamos que eu queira incluir 10 registros nesta tabela, temos várias formas de fazer isso

    -- o mais comum de vermos nos sites por ai é:

     

    DECLARE @i Int

    SET @i = 0

    WHILE @i < 10

    BEGIN

      INSERT INTO teste VALUES(NewID())

      SET @i = @i + 1

    END

    go

     

    -- Agora vamos ver uma forma mais simples.

    IF Object_ID('teste') IS NOT NULL

      DROP TABLE teste

    GO

    CREATE TABLE teste (ID   Int Identity(1,1),

                        Nome VarChar(200))

     

    GO

    -- Usando o GO 10 o comando Insert será executavo 10 vezes.

    INSERT INTO teste VALUES(NewID())

    GO 10

     

    Para quem está começando no SQL Server aqui vão algumas explicações sobre alguns comandos acima que podem ser desconhecidos.

     

    1.       NewID() é uma função que retorna um conjunto de caracteres aleatórios.

     

    2.       Obeject_ID() retorna o ObjectID de um determinado objeto no nosso exemplo a tabela teste. Esta informação está gravada na tabela sysobjects do SQL Server 2000 ou sys.objects do SQL Server 2005.

     

    Por hoje é só, espero nos ajudar na nossa jornada SQL Server.

     

    Observação: O "GO 10" Só funciona no SQL Server Managmente Studio.