Arquivo

Archive for the ‘Não categorizado’ Category

SQL Server Day

19 de outubro de 2009 3 comentários

Pessoal no sábado dia 07/11 teremos um mega evento com 12 horas de SQL Server, vou ministrar a segunda palestra do dia.

Conto com a presença de vocês,

Abraço.

Community Webcast: SQL Server Day – Parte I 

09:30 – 10:00 – Abertura do Evento
10:00 – 11:00 – Resource Governor e Policy Management – Vitor Fava e Alexandre Lopes
11:00 – 12:00 – Entenda porque o Query Optimizer é mais esperto que você – Fabiano Amorim
12:00 – 13:00 – SQL Azure Database – Diego Nogare
13:00 – 14:00 – Novos Recursos de Desenvolvimento do SQL Server 2008 – Higor Fernandes

Community Webcast: SQL Server Day – Parte II

14:00 – 15:00 – Compressão de Dados e Backup no Microsoft SQL Server 2008 – Pedro A. G. Junior
15:00 – 16:00 – Entendendo TDE (Transparent Data Encryption) – Felipe Ferreira
16:00 – 17:00 – Entendendo as Common Table Expressions (CTE) – Thiago Zavaschi
17:00 – 18:00 – Powershell Coletando e Analisando os Dados -Laerte Junior e Thiago Zavaschi
18:00 – 19:00 – Solução Avançada de Problemas com Extended Events – Vladimir Magalhães        

Community Webcast: SQL Server Day – Parte III

19:00 – 20:00 – Disaster Recovery – Backup, Restore e Tópicos Avançados – Gustavo Maia Aguiar
20:00 – 21:00 – CLR – Roberto Fonseca
21:00  – 22:00 – Analise de Desempenho utilizando as Estatísticas de Espera – Alex Rosa

Categorias:Não categorizado

É nóis no Simple Talk, em ingreis ainda… :-)

1 de outubro de 2009 4 comentários

Pessoal, estou muito feliz em dizer que tive meu primeiro artigo em inglês publicado em um dos maiores sites de tecnologia e SQL Server.

http://www.simple-talk.com/content/article.aspx?article=826

Convido a todos para visitar o artigo, votar nele, e se possível deixar algum comentário (positivo né galera, rsrs, se tiver algum erro me manda no e-mail J).

Agradeço a todos pelo incentivo e apoio.

Abraço.

Categorias:Não categorizado

Query Optimizer – DATE_CORRELATION_OPTIMIZATION

11 de setembro de 2009 2 comentários

 

Aqui estou em São Paulo, me preparando para passar as noites do fim de semana acompanhando uma implantação…. Tempo para um post…

Pessoal, desde o SQL Server 2005, temos um parâmetro que pode ser habilitado no banco de dados chamado DATE_CORRELATION_OPTIMIZATION. Como o padrão é OFF, eu particularmente nunca vi alguem alterar este parâmetro para ON. Eu creio que isso deve se ao fato de termos pouquíssimo material falando sobre esta feature.

É importante que o desenvolvedor ou DBA conheçam o banco de dados, para poder fazer proveito desta otimização, mas você deve estar se pergutando, Mas pra que serve este parâmetro?

Vamos lá:

Quando habilitado o DATE_CORRELATION_OPTIMIZATION coleta informações sobre colunas do tipo datetime de duas tabelas que estão ligadas por uma foreign key. Estas informações são comumentes conhecidas como estatisticas (apesar de não agirem exatamente como uma estatistica), pois elas ajudam o Query Optimizer a identificar uma relação entre as colunas que contém uma data, e assim decidir sobre a criação do plano de execução.

Um bom exemplo de colunas correlatas seria o seguinte: Uma tabela de Pedido que contem uma coluna DataPedido, e uma tabela Itens que contem uma coluna DataEntregaItem.

Estes dias eu fiz uma compra no Submarino e comprei varios produtos, desde livros a bichos de pelucia (deixa eu explicar melhor esse item pra não dar chance de aparecerem comentários sobre isso…rs. Foi pra minha patroa gente… é que ela gosta de pinguin e comprei um de pelucia…). Conclusão, apesar de meu pedido ser apenas um, eu recebi os itens em dias diferentes, isso porque com certeza cada um veio de um lugar. Mas voltanto as tabelas, neste caso teriamos a tabela de pedido com a data de pedido, e a tabela de itens que é relacionada ao pedido e contem a data de entrega de cada item. Podemos dizer que a data de entrega sempre estara bem próxima a data de pedido. Isso representa claramente uma relação entre a data de pedido e a data de entrega.

Após entendido isto, vamos voltar a otimização.

Se você tiver uma consulta onde faz um join entre a tabela de Pedido e Itens fazendo filtro por uma das datas, o SQL Server pode alterar sua consulta a fim de otimiza-la sem você saber disso. Como?… vamos começar com os exemplos.

Primeiro vamos criar uma estrutura de tabelas parecidas com o cenário mencionado acima.

IF OBJECT_ID(‘Pedidos’) IS NOT NULL
BEGIN
  DROP TABLE Itens
  DROP TABLE Pedidos
END
GO
CREATE TABLE Pedidos(ID_Pedido   Integer Identity(1,1),
                     Data_Pedido DateTime NOT NULL, — AS COLUNAS DE DATA NÃO PODEM ACEITAR NULL
                     Valor       Numeric(18,2),
                     CONSTRAINT xpk_Pedidos PRIMARY KEY (ID_Pedido))
GO
CREATE TABLE Itens(ID_Pedido    Integer,
                   ID_Produto   Integer,
                   Data_Entrega DateTime NOT NULL, — AS COLUNAS DE DATA NÃO PODEM ACEITAR NULL
                   Quantidade   Integer,
                   CONSTRAINT xpk_Itens PRIMARY KEY NONCLUSTERED(ID_Pedido, ID_Produto))
GO
— Pelo menos uma das colunas de data, tem que pertencerem a um indice cluster
CREATE CLUSTERED INDEX ix_Data_Entrega ON Itens(Data_Entrega)
GO
— É Obrigatório existir uma foreign key entre as tabelas que contém as datas correlatas
ALTER TABLE Itens ADD CONSTRAINT fk_Itens_Pedidos FOREIGN KEY(ID_Pedido) REFERENCES Pedidos(ID_Pedido)
GO

DECLARE @i Integer
SET @i = 0

WHILE @i < 10000
BEGIN
  INSERT INTO Pedidos(Data_Pedido,
                      Valor)
  VALUES(GetDate() – ABS(CheckSum(NEWID()) / 10000000),
         ABS(CheckSum(NEWID()) / 1000000))
  SET @i = @i + 1
END
GO

INSERT INTO Itens(ID_Pedido, ID_Produto, Data_Entrega, Quantidade)
SELECT ID_Pedido,
       ABS(CheckSum(NEWID()) / 10000000),
       Data_Pedido + ABS(CheckSum(NEWID()) / 100000000),
       ABS(CheckSum(NEWID()) / 10000000)
  FROM Pedidos
GO
INSERT INTO Itens(ID_Pedido, ID_Produto, Data_Entrega, Quantidade)
SELECT ID_Pedido,
       ABS(CheckSum(NEWID()) / 10000),
       Data_Pedido + ABS(CheckSum(NEWID()) / 100000000),
       ABS(CheckSum(NEWID()) / 10000000)
  FROM Pedidos
GO

Após criar as tabelas, teremos uma estrutura parecida com o cenário que mencionei, vamos ver os dados de um pedido.

image

Agora vamos supor que a seguinte consulta seja construida.

SELECT *
  FROM Pedidos
INNER JOIN Itens
    ON Pedidos.ID_Pedido = Itens.ID_Pedido
WHERE Pedidos.Data_Pedido BETWEEN ‘20090301’ AND ‘20090305’

Traduzindo, eu quero todos os dados de Pedidos e Itens vendidos entre 01/03/2009 até 05/03/2009. Para esta consulta o SQL Server criou o seguinte plano de execução:

image

SELECT *    FROM Pedidos   INNER JOIN Itens      ON Pedidos.ID_Pedido = Itens.ID_Pedido   WHERE Pedidos.Data_Pedido BETWEEN ‘20090301’ AND ‘20090305’
  |–Hash Match(Inner Join, HASH:([Performance].[dbo].[Pedidos].[ID_Pedido])=([Performance].[dbo].[Itens].[ID_Pedido]))
       |–Clustered Index Scan(OBJECT:([Performance].[dbo].[Pedidos].[xpk_Pedidos]), WHERE:([Performance].[dbo].[Pedidos].[Data_Pedido]>=’2009-03-01′ AND [Performance].[dbo].[Pedidos].[Data_Pedido]<=’2009-03-05′))
       |–Clustered Index Scan(OBJECT:([Performance].[dbo].[Itens].[ix_Data_Entrega]))

No plano em modo texto, podemos facilmente perceber que o filtro especificado no where, foi aplicado durante a leitura do índice xpk_Pedidos e o campo que recebeu o filtro foi justamente o Data_Pedido espeficidado no where.

A primeira coisa que poderiamos fazer para otimizar esta consulta, seria criar um indice na coluna Data_Pedido. Ok, você pode estar pensando, a isso eu sei, isso todo mundo faria logo de cara, você esta certo, mas vamos fazer isso e continuar na otimização.

CREATE INDEX ix_teste ON Pedidos(Data_Pedido) INCLUDE(Valor)

Após criar o índice temos o seguinte plano:

image

SELECT *    FROM Pedidos   INNER JOIN Itens ON Pedidos.ID_Pedido = Itens.ID_Pedido   WHERE Pedidos.Data_Pedido BETWEEN ‘20090301’ AND ‘20090305’
  |–Hash Match(Inner Join, HASH:([Performance].[dbo].[Pedidos].[ID_Pedido])=([Performance].[dbo].[Itens].[ID_Pedido]))
       |–Index Seek(OBJECT:([dbo].[Pedidos].[ix_teste]), SEEK:([dbo].[Pedidos].[Data_Pedido] >= ‘2009-03-01’ AND [dbo].[Pedidos].[Data_Pedido] <= ‘2009-03-05’) ORDERED FORWARD)
       |–Clustered Index Scan(OBJECT:([Performance].[dbo].[Itens].[ix_Data_Entrega]))

Agora a coisa melhorou um pouco, ao invés de fazer um Clustered Index Scan para ler os dados da tabela Pedidos, o SQL fez um Index Seek baseado no índice que acabamos de criar. Ok o que mais podemos fazer? Vamos começar a ver o Correlation_Optimization.

Vemos que existe um gargalo para ler os dados da tabela Itens, segundo o plano de execução que temos, esta leitura representa 49% de custo da consulta, e ele esta fazendo um Scan, será que da pra fazer um Seek?

Conhecendo o banco de dados e a estrutura das tabelas e negócio, poderiamos alterar a consulta para incluir um filtro na tabela Itens. Mas para isso teremos que ter certeza de que nosso filtro não irá causar uma mudança no resultado dos dados. Uma alternativa seria a seguinte:

  1. Baseado nos pedidos que estão entre 2009-03-01 e 2009-03-05, verificar qual é a menor data de entrega e a maior data de entrega dos itens.
  2. Com estas informações incluir um filtro por Itens.Data_Entrega.

Com isso teriamos a seguinte consulta:

SELECT *
  FROM Pedidos
INNER JOIN Itens
    ON Pedidos.ID_Pedido = Itens.ID_Pedido
WHERE Pedidos.Data_Pedido BETWEEN ‘20090301’ AND ‘20090305’
   AND Itens.Data_Entrega BETWEEN ‘20090301’ AND ‘20090325 23:59:59.000’

Desta forma eu conseguiria forçar um filtro na tabela Itens, veja o plano:

image

Mas pergunta? Como fazer para descobrir os valores do item 1, sozinho? Porque normalmente os valores do between são variáveis e não fixos.

Que tal deixar o SQL se virar por você? , Basta habilitar o DATE_CORRELATION_OPTIMIZATION que ele identifica isso pra você, e aplica o filtro no Data_Entrega sozinho, óia que esperto eim! Vamos ver se é verdade….

ALTER DATABASE <NomeDoSeuBanco> SET DATE_CORRELATION_OPTIMIZATION ON;

Vamos rodar a consulta denovo…

image

SELECT *    FROM Pedidos   INNER JOIN Itens      ON Pedidos.ID_Pedido = Itens.ID_Pedido   WHERE Pedidos.Data_Pedido BETWEEN ‘20090301’ AND ‘20090305’
  |–Hash Match(Inner Join, HASH:([dbo].[Pedidos].[ID_Pedido])=([dbo].[Itens].[ID_Pedido]))
       |–Index Seek(OBJECT:([dbo].[Pedidos].[ix_teste]), SEEK:([dbo].[Pedidos].[Data_Pedido] >= ‘2009-03-01’ AND [dbo].[Pedidos].[Data_Pedido] <= ‘2009-03-05’) ORDERED FORWARD)
       |–Clustered Index Seek(OBJECT:([dbo].[Itens].[ix_Data_Entrega]), SEEK:([dbo].[Itens].[Data_Entrega] >= ‘2009-02-28’ AND [dbo].[Itens].[Data_Entrega] < ‘2009-04-29’) ORDERED FORWARD)

Repare que desta vez, mesmo sem eu colocar o filtro na coluna Data_Entrega o SQL colocou este filtro pra mim.

Conclusão

O DATE_CORRELATION_OPTIMIZATION é uma feature muito boa mas que merece uma analise mais detalhada de onde pode ser utilizada, ela também exige que algumas regras sejam atendidas para que funcione. Por exemplo:

  • Obrigatóriamente deve existir uma foreign key entre as tabelas, e ela deve estar ligada por apenas uma coluna.
  • Ambas as tabelas devem conter uma coluna do tipo DateTime e elas não podem aceitar NULL.
  • Pelo menos uma das colunas DateTime deve pertencer a um Indice Cluster, caso o indice seja composto, a coluna deve ser a chave para ordenação, ou seja, deve ser a primeira especificada.

Bom galera, fica ai a dica…

Abraço

Categorias:Não categorizado

Lançamento Delphi 2010 – Eu vou…

8 de setembro de 2009 Deixe um comentário

Minha querida MS que me desculpe, mas o Deplhi tem me sustentado a alguns anos…

Desde quando a Borland vendeu o Delphi (foi um dia muito triste pra mim 😦 ) pra Embarcadero, eu não participo de um evento oficial sobre o Delphi. Mas ai vou eu, pro lançamento do Delphi 2010, quem sabe não nos encontramos por lá?

http://latam.embarcadero.com/br/delphi2010/

Abraço.

Categorias:Não categorizado

SQL – Query Optimizer – Updates e NonClustered Index

4 de setembro de 2009 6 comentários
Query Optimizer – Unique Index vs Selects e Updates
Autor – Fabiano Neves Amorim

Início

Dando continuidade ao tema Query Optmizer, neste artigo veremos mais detalhes sobre o comportamento interno da criação de um plano de execução, quanto a Unique Index, Selects e Updates.

É importante que os desenvolvedores saibam como o SQL executa uma consulta para assim escrever um código eficiente. Para os DBAs este conhecimento é ainda mais importante. Ao trabalhar com banco de dados, é fundamental que você entenda o comportamento do QO (Query Optimizer).

Como analogia, imagine duas empresas de construção. Ambas vão construir uma casa. Para isso, a primeira empresa começa planejando a construção, analisando qual será a ordem das tarefas, quais tarefas podem ser feitas em paralelo, qual depende da outra, e qual é o melhor trabalhador para efetuar cada tarefa. A segunda empresa não faz o planejamento e já começa a construção.

É bem provável que a empresa que fez o planejamento termine a construção primeiro e a segunda empresa nem termine. Construir uma casa não é uma tarefa simples e requer um planejamento. De forma semelhante, um banco de dados relacional requer que suas consultas sejam analisadas e planejadas antes de serem desenvolvidas. Quanto mais complexa a operação, maior é o valor do planejamento.

Um pouco mais de Query Optimizer

Na matéria sobre Query Processor (QP) publicada na edição 65 da SQL Magazine, vimos que o Query Optimizer (QO) executa uma série de passos para gerar um plano de execução de uma consulta. Uma dessas fases é conhecida como Parse e Normalization (Bind).

O Parse analisa as características dos objetos referenciados nas consultas e valida a sintaxe do comando para ver se ele é lógico e apto para execução. O parse gera uma Sequence Tree, também conhecido como Query Processor Tree, que é uma representação lógica dos passos necessários para a execução do comando SQL.

O Sequence Tree é passado para o Normalization, que efetua a tarefa de Bind. Durante esta etapa, o SQL verifica se as tabelas e colunas existem e lê as informações sobre os objetos acessando o metadata do SQL. Nesta etapa também são efetuadas conversões implícitas, por exemplo: se está sendo realizado um insert de uma string “50.30” em uma coluna do tipo Numeric, o SQL efetua uma conversão implícita desta string para o tipo adequado.

Preparando o ambiente

Para melhorar o entendimento dos exemplos, utilizaremos uma tabela com dados aleatórios que servirão como base para os testes que serão apresentados. A Listagem 1 contém o script para criação destes objetos. Este script cria uma tabela chamada TabTeste, que contém alguns campos e, em seguida, são criados quatro índices, dois unique index e dois nonclustered index. Logo após, cinco registros são inseridos com dados aleatórios.

CREATE TABLE TabTeste (ID Int IDENTITY(1,1) PRIMARY KEY,

Nome VarChar(250) NULL,

Nome2 VarChar(250) NULL,

Val Int NULL,

Val2 Int NULL)

GO

CREATE UNIQUE INDEX ix_Unique ON TabTeste(Nome)

CREATE INDEX ix_NonUnique ON TabTeste(Nome2)

CREATE UNIQUE INDEX ix_UniqueVal ON TabTeste(Val)

CREATE INDEX ix_NonUniqueVal2 ON TabTeste(Val2)

— Massa aleatoria de dados.

INSERT INTO TabTeste(Nome, Val) VALUES(NEWID(), 1)

INSERT INTO TabTeste(Nome, Val) VALUES(NEWID(), 2)

INSERT INTO TabTeste(Nome, Val) VALUES(NEWID(), 3)

INSERT INTO TabTeste(Nome, Val) VALUES(NEWID(), 4)

INSERT INTO TabTeste(Nome, Val) VALUES(NEWID(), 5)

GO

UPDATE TabTeste SET Nome2 = Nome, Val2 = Val

Listagem 1. Script para criação dos objetos de teste

Select e Unique Index

Unique Index é um tipo de índice que pode ser criado sobre uma coluna de uma tabela, onde você deseja garantir que seu valor não pode ser duplicado. Em índices únicos, todas as linhas da tabela terão um valor único para cada registro.

Sempre que você criar um índice e ele for um Unique Index, isto é, se você deseja que o SQL crie um controle para que os valores da coluna não dupliquem, pense nos prós e contras da utilização do Unique Index. Esta informação será de grande valor para o QO decidir se o índice será utilizado ou não, visto que quando o SQL tiver que ler algum valor neste índice ele saberá que este valor é sempre único – o que representa um alto nível de seletividade (vide Nota 1). Quanto mais seletiva for uma coluna, maior a possibilidade de utilização de um índice que pertence a esta coluna.

Vamos utilizar como exemplo a tabela TabTeste, onde temos cinco campos e 4 índices. Dois Unique Index por Nome e Val, e outros dois NonClustered Index por Nome2 e Val2 respectivamente. O Nonclustered Index, ao contrário do Unique Index, permite que os valores sejam duplicados.

Na Listagem 2 temos duas simples consultas que retornam os dados da tabela TabTeste. A primeira utiliza um filtro na coluna Nome, que contém um Unique Index, e a segunda utiliza o mesmo filtro, porém, utilizando a coluna Nome2, que possui um NonClustered Index.

SELECT * FROM TabTeste

WHERE Nome = ‘28198490-10E0-44A2-A331-863242BA717E’

SELECT * FROM TabTeste

WHERE Nome2 = ‘28198490-10E0-44A2-A331-863242BA717E’

Listagem 2. Selects exemplo.

clip_image002

Figura 1. Planos de execução gerados pelo select da Listagem 2.

Antes de analisarmos os planos de execução da Figura 1, vamos entender alguns termos importantes.

Sempre que um índice é criado, o SQL Server pega uma série de informações em relação à coluna informada no índice e grava estes dados em uma área de armazenamento no banco. Estes dados são conhecidos por estatísticas.

As estatísticas fornecem informações muito importantes para o QO. Dentre elas podemos destacar o nível de seletividade, densidade e cardinalidade (vide nota) de uma coluna. Conforme novos registros são incluídos ou apagados, automaticamente o SQL Server tenta manter as estatísticas atualizadas para trabalhar com valores atualizados.

É possível configurar o SQL para que ele não atualize as estatísticas automaticamente. Para isso, basta acessar as propriedades do banco de dados e definir a propriedade Auto Update Statistics como false. É importante destacar que caso seja utilizada esta opção (false), será de extrema importância que você tenha uma rotina de manutenção das estatísticas de seu banco. Estatísticas desatualizadas podem acarretar em planos de execução ineficientes.

Voltando aos planos da Figura 1, como podemos observar, para o primeiro select o SQL gerou um index Seek e o plano todo representa 31% do custo das duas consultas. Neste caso podemos perceber que o primeiro select é mais eficiente que a segunda consulta, que representa 69% do custo total de execução. Ainda que as estatísticas de sua tabela estejam desatualizadas, o SQL sabe que a seletividade dos dados na coluna Nome é altíssima, já que ela sempre irá retornar um registro devido a presença do unique index.

Já a segunda consulta, que faz o select baseado no where pela coluna Nome2, gerou um Clustered Index Scan, ou seja, o SQL irá varrer toda a tabela procurando pelos registros que satisfaçam o SARG (vide nota) especificado no where. Neste caso as estatísticas não forneceram informações precisas. O QO não conseguiu estimar a quantidade de linhas que seriam retornadas pelo comando, processo também conhecido por cardinalidade (vide nota).

Updates e Nonclustered Index

Ao criar um Unique Index devemos considerar a complexidade de um simples UPDATE ou INSERT, pois o SQL tem que garantir que nenhum valor será duplicado e, para isso ele utiliza alguns mecanismos muito importantes. Vamos analisá-los progressivamente; primeiro com um simples update em uma tabela atualizando uma coluna que não possui índice único (Listagem 3) e, posteriormente, em coluna que possui um índice único.

UPDATE TabTeste SET Val2 = Val2 + 1

Listagem 3. Update na tabela TabTeste em uma coluna que não possui índice único.

clip_image004

Figura 2. Planos de execução gerados pelo update da Listagem 3.

Na Figura 2 temos o plano de execução gerado pelo comando de update da Listagem 3. Na Listagem 4 podemos observar o plano de execução no modo texto usando o SET SHOWPLAN_TEXT ON antes de rodar o código da Listagem 3.

UPDATE [TabTeste] set [Val2] = [Val2]+@1

|–Clustered Index Update(OBJECT:([TabTeste].[PK__TabTeste__3214EC2740DB41A9]), OBJECT:([TabTeste].[ix_NonUniqueVal2]), SET:([TabTeste].[Val2] = [Expr1003]))

|–Compute Scalar(DEFINE:([Expr1016]=[Expr1016]))

|–Compute Scalar(DEFINE:([Expr1016]=CASE WHEN [Expr1007] THEN (0) ELSE (1) END))

|–Compute Scalar(DEFINE:([Expr1003]=[TabTeste].[Val2]+(1), [Expr1007]=CASE WHEN [TabTeste].[Val2] = ([TabTeste].[Val2]+(1)) THEN (1) ELSE (0) END))

|–Top(ROWCOUNT est 0)

|–Clustered Index Scan(OBJECT:([TabTeste].[PK__TabTeste__3214EC2740DB41A9]))

Listagem 4. Plano de execução em Modo Texto.

Todos os comandos de Update são executados em dois passos principais, o primeiro é o de leitura dos registros que serão atualizados, e o segundo é o de escrita dos dados que sofrerão o Update. Vamos detalhar e analisar cada passo gerado pelo plano de execução (Listagem 4):

.

· clip_image005 Clustered Index Scan: Etapa onde o SQL lê todos os dados que serão atualizados pelo UPDATE. Este é o primeiro passo que mencionei acima, o de leitura;

· clip_image006 TOP: Se você analisar o plano de execução usando o modo texto (Listagem 4), verá que foi gerado um comando Top (ROWCOUNT est 0), este Top é utilizado para implementar o SET ROWCOUNT, e “est” é uma abreviação de estimate. Esta implementação é necessária para garantir que o número de linhas atualizadas seja exatamente o número desejado.

Repare que se executarmos um SET ROWCOUNT 1 antes do update, o SQL irá executar um comando Top(ROWCOUNT est 1). Ex:

SET ROWCOUNT 1

UPDATE TabTeste SET Val2 = Val2 + 1

OPTION(RECOMPILE)

Listagem 5. Update utilizando SET ROWCOUNT 1.

|–Top(ROWCOUNT est 1)

Listagem 6. Código do plano onde é implementado o TOP (ROWCOUNT est 1)

Na listagem 5 o update utiliza o hint RECOMPILE para forçar a geração de um novo do plano de execução, a fim de vermos a alteração no TOP exibida na Listagem 6.

· clip_image007 Compute Scalar: Nesta etapa temos algumas considerações bem interessantes, e uma novidade que foi implementada no SQL Server 2005. Vamos rever parte do código gerado pelo plano de execução em modo Texto:

CASE

WHEN [TabTeste].[Val2] = [TabTeste].[Val2]+(1)) THEN (1)

ELSE (0)

END

Listagem 7. Comando Case.

o Com o código um pouco mais claro, vamos entender o que está acontecendo aqui:

§ O SQL valida se o valor que será atualizado (Val2 + 1) é igual ao valor da coluna Val2 no índice nonclustered (ix_NonUniqueVal2) afetado, opa, espera ai! Para tudo! Quer dizer que se o valor atual no índice nonclustered, for igual ao novo valor que foi passado para o SQL, ele não perde tempo atualizando esta linha?

Sim, é isso mesmo, é claro que existe um overhead em fazer essa validação, mas com certeza vale à pena verificar se ouve alguma alteração antes se dar o trabalho de sair trocando 6 por meia dúzia.

· Esta validação é importante porque no caso de updates em colunas que pertencem ao um índice nonclustered, o SQL não efetua apenas um update no valor. Internamente o SQL exclui a linha do índice, para depois fazer um insert do novo valor. Os passos para a atualização representada pela listagem 3 seriam:

o Localizar os valores que serão atualizados utilizando o índice cluster.

o Atualizar os valores no índice cluster.

o Efetuar um delete no valor antigo do índice nonclustered.

o Inserir o novo valor no índice nonclustered.

§ Continuando a analisar o Case, caso o valor de Val2 seja igual ao valor de Val2 +1(informação fornecida no “SET Val2 =”) então retorna 1 (nada mudou) senão retorna 0 (valor mudou).

§ Existe uma DMV (sempre elas) que podemos utilizar para saber se o SQL teve que atualizar o valor de um índice nonclustered ou não. Esta DMV é a sys.dm_db_index_operational_stats(vide nota), você pode analisar a coluna leaf_insert_count para verificar se ouve um novo insert no seu índice nonclustered quando o SQL executou o comando de update. Lembra que falei que na verdade ele sofre um delete mais um insert? É por isso você deverá analisar a coluna leaf_insert_count, e não a leaf_update_count. Somente no caso de um índice cluster que o valor alterado é exibido na leaf_update_count.

§ Repetindo, isso só foi implementado no SQL Server 2005. O update da listagem 3 compilado no SQL 2000 gera o seguinte Execution Plan:

clip_image009

Figura 3. Planos de execução gerados pelo update no SQL Server 2000.

UPDATE [TabTeste] SET [Val2]=[Val2]+@1

|–Clustered Index Update(OBJECT:([TabTeste].[PK__TabTeste__32826724]), SET:([TabTeste].[Val2]=[Expr1004]))

|–Compute Scalar(DEFINE:([Expr1004]=[TabTeste].[Val2]+1))

|–Top(ROWCOUNT est 0)

|–Clustered Index Scan(OBJECT:([TabTeste].[PK__TabTeste__32826724]))

Listagem 8. Plano em modo texto.

· Conforme pode ser observado na listagem 8, não existe o operador compute scalar com o case. Realmente é uma pena que a maioria dos usuários de SQL Server no Brasil ainda utilizam a versão 2000.

· clip_image007[1] Compute Scalar: Este outro operador de compute scalar é gerado para cálculos internos do SQL Server.

· clip_image010 Clustered Index Update:

o Este é o segundo passo dos dois passos necessários para executar um update conforme mencionei acima. Aqui é onde o SQL efetua o update nos registros lidos pelo Clustered Index Scan. Como existe um índice cluster na coluna afetada pelo update, o SQL gerou apenas um operador que atualiza os dois índices. São eles: o índice cluster que contem os dados da tabela, e o índice nonclustered (ix_NonUniqueVal2).

Este tipo de plano de execução, que usa o mesmo operador para atualizar dois lugares diferentes, também é conhecido como “Narrow Plan” ou “Per-Row Plan”.

Repare no plano de execução em modo texto exibido na listagem 4 que, os nomes dos dois índices aparecem no operador de Clustered Index Update, ou seja, ambos serão atualizados pelo mesmo operador.

No SQL Server 2000 não é exibida a informação de todos os índices que serão atualizados, embora ele atualize os 2 índices assim como no SQL 2005, este comportamento de exibir todos os índices no plano de execução só foi implementado na versão 2005.

o Sempre que acontece um update em um campo que pertence a um índice nonclustered, o SQL tem que atualizar esta informação na tabela e no índice nonclustered.

Normalmente (existem exceções, veremos uma delas mais abaixo), quando o SQL vai atualizar o valor em uma tabela, ele executa esta operação por ordem da “chave” do índice cluster, e não na ordem da chave dos índices nonclustered.

Isso significa que para cada linha atualizada no índice cluster, o SQL precisa fazer um seek (buscar o valor correspondente) no índice nonclustered para atualizar seu valor. Quando uma grande quantidade de linhas é atualizada este processo de leitura desordenada (visto que está ordenado pela chave do cluster) no índice nonclustered acaba sendo bastante custoso para o SQL Server.

Quando isso acontece, o QO pode optar por usar outro meio para atualizar os dados nos índices nonclustered. Neste caso ele gera um index update ordenado para cada chave do índex nonclustered afetado no update.

O QO escolhe usar esta opção baseado na quantidade de linhas que serão afetadas pelo update e, a quantidade de índices nonclustered que serão atualizados. Existem ainda outras medidas que são levadas em consideração antes de o QO tomar esta decisão, porem as principais são essas.

Este comportamento de gerar um plano de execução com um Index Update para atualizar cada índice envolvido na consulta, é conhecido como “Wide Plan”.

Updates e Unique Index

Depois que analisamos o comportamento de um simples update atualizando uma coluna que não tem um Unique Index, vamos ver o que acontece quando efetuamos outro update, desta vez atualizando uma coluna que possui (Listagem 9).

UPDATE TabTeste SET Val = Val + 1

Listagem 9. Update na tabela TabTeste atualizando uma coluna que contem um índice único.

clip_image012

Figura 4. Plano de execução gerado por um update em uma coluna com unique index.

Na Listagem 10 podemos observar o plano de execução no modo texto usando o SET SHOWPLAN_TEXT ON antes de rodar o código da Listagem 9.

UPDATE [TabTeste] set [Val] = [Val]+@1

|–Index Update(OBJECT:([TabTeste].[ix_UniqueVal]), SET:(Insert, [ID1022] = [TabTeste].[ID],[Val1023] = [TabTeste].[Val]), SET:(Update, [ID1022] = RaiseIfNullUpdate([TabTeste].[ID])) ACTION:([Act1021]))

|–Collapse(GROUP BY:([TabTeste].[Val]))

|–Sort(ORDER BY:([TabTeste].[Val] ASC, [Act1021] ASC))

|–Filter(WHERE:(NOT [Expr1019]))

|–Split

|–Clustered Index Update(OBJECT:([TabTeste].[PK__TabTeste__3214EC2765D7BC2E]), SET:([TabTeste].[Val] = [Expr1003]))

|–Compute Scalar(DEFINE:([Expr1019]=[Expr1019]))

|–Compute Scalar(DEFINE:([Expr1019]=CASE WHEN [Expr1007] THEN (1) ELSE (0) END))

|–Compute Scalar(DEFINE:([Expr1003]=[TabTeste].[Val]+(1), [Expr1007]=CASE WHEN [TabTeste].[Val] = ([TabTeste].[Val]+(1)) THEN (1) ELSE (0) END))

|–Top(ROWCOUNT est 0)

|–Clustered Index Scan(OBJECT:([TabTeste].[PK__TabTeste__3214EC2765D7BC2E]))

Listagem 10. Plano gerado pelo Update da listagem 9.

Agora o plano de consulta parece ter ficado um pouco mais complicado, ou podemos dizer interessante?

Podemos observar que do operador Clustered Index Scan até o Clustered Index Update o SQL gerou exatamente o mesmo plano de execução do primeiro Update que analisamos. Existe apenas a exceção que desta vez o Clustered Index Update não irá atualizar o índice cluster e o nonclustered, atualizará somente o cluster.

Depois do Clustered Index Update temos alguns operadores novos em relação ao update da Listagem 3, são eles: Split, Filter, Sort, Collpase e por fim o Index Update, que será responsável por atualizar, ou melhor, apagar e incluir o novo valor no nonclustered unique index ix_UniqueVal.

O que acontece aqui é que o SQL precisa garantir que o valor que será atualizado não viole a regra de unique. Não pode haver nenhum valor com o mesmo valor que está tentando ser inserido. Fazer este controle pode parecer uma tarefa simples, mas para isso o SQL utiliza alguns operadores específicos, vamos analisar melhor cada um deles.

Imagine que os dados da tabela estejam conforme a Figura 5.

clip_image014

Figura 5. Dados tabela TabTeste.

Digamos que o SQL opte por usar o mesmo plano de execução gerado pelo primeiro update, aquele sem o Split, exibido na Figura 2. Lembra que vimos que o SQL efetua o update na ordem do índice cluster (coluna ID)? Pois é, seguindo esta regra o primeiro registro a ser atualizado seria o ID = 1. Vamos seguir na lógica, localiza o registro no índice nonclustered passando como chave o Val (1). Efetua um delete, e depois efetua um insert com o valor atualizado(Val (1) + 1 = 2), porém o valor 2 já existe (ID = 2), o que causaria um falso erro de violação de unicidade. Este erro é caracterizado por falso, pois o valor 2 posteriormente também seria atualizado para Val(2) + 1 = 3, o que viabilizaria o insert com o valor 2.

Para evitar este tipo de problema, o QO automaticamente escolhe utilizar os operadores Split, Sort e Collapse para fazer o update por ordem da coluna do índice nonclustered, no nosso exemplo, a coluna Val.

Vamos analisar melhor estes operadores:

· clip_image015 Split: O operador de Split será responsável por transformar os updates em deletes seguidos de inserts;

· clip_image016 Sort: O sort irá reordenar os inserts e deletes para que eles sejam executados ordenados pela coluna do nonclustered unique index;

· clip_image017 Filter: Efetua um filtro para selecionar apenas as colunas onde o operador Compute Scalar com o case gerou um valor = 0 ou seja, valores que sofreram alteração;

· clip_image018 Collapse: Agrupa todos os inserts e deletes que utilizam o mesmo valor da chave do índice cluster. Se o SQL encontrar algum insert ou delete para uma mesma chave ele agrupa todos para um mesmo update para deixar a operação mais eficiente. Após este agrupamento o collapse passa os updates que serão executados para o Index Update;

· clip_image010[1] Index Update: Atualiza o índice com o update gerado pelo Collapse.

É importante destacar que mesmo com estes mecanismos para evitar que uma coluna tenha o valor duplicado, ainda assim pode acontecer de um valor repetir. Quando isso acontecer o SQL irá gerar uma exceção. Este comportamento apenas evita que falsas violações ocorram.

No caso de um unique índex:

· Uma violação ocorre quando um valor que já existe na coluna está sendo inserido novamente;

· Uma falsa violação ocorre quando uma coluna está recebendo um valor que já existe, mas que durante o processo de atualização dos dados, este valor já existente também será atualizado. Daí o termo, falsa violação. Ela é falsa pois no final do update a coluna não terá nenhum valor repetido.

O QO também é esperto o suficiente para identificar quando não tem como um update gerar uma falsa violação. Na Listagem 11 podemos ver alguns exemplos.

UPDATE TabTeste SET Val = 1

UPDATE TOP(1) TabTeste SET Val = Val + 1

UPDATE TabTeste SET Val = Val + 1 WHERE ID = 1

Listagem 11. Updates que não geram violação.

Por causa da presença do unique índex, nas três consultas da Listagem 11 o QO sabe que somente uma linha poderá ser atualizada com o valor 1.

No primeiro update, se a tabela tiver mais de um registro o SQL irá gerar uma exceção, pois neste caso o unique index obriga que os valores não repitam. Não há necessidade de prever uma possível falsa violação, pois se todas as linhas da tabela receberem o mesmo valor não existe falsa violação.

O segundo update é igual ao update da Listagem 9, porém, desta vez ele contém a cláusula TOP 1, o que força que apenas uma linha seja atualizada, fazendo com que não seja possível ocorrer uma falsa violação.

O terceiro update contém uma condição que dá certeza de que apenas uma linha será atualizada. A coluna ID é chave primária da tabela, ou seja, o QO sabe que só pode existir uma linha com o valor 1. Isto novamente impede uma falsa violação.

Em nenhum dos updates da Listagem 11 o SQL perde tempo gerando o Split e etc. Ele simplesmente gera um “Narrow Update Plan” que atualiza o índice cluster e o nonclustered.

Conclusão

Analisar os planos de execução é uma tarefa muito interessante e que desperta bastante interesse na maioria dos usuários de SQL.

Existem vários itens que sempre devem ser levados em consideração na construção de um comando SQL, com destaque para as notas deste artigo. Todas estas informações ajudam na decisão de qual comando devemos utilizar e qual será a estrutura das tabelas do banco de dados.

Vimos neste artigo mais um pouco de como o QO trabalha. Também analisamos como UPDATES são executados pelo banco, e como eles são influenciados pela presença de índices nas tabelas.

Os três links apresentados como referência são essenciais para quem quer se aprofundar ainda mais no assunto. Deixo uma questão. Fora os Hints, o que mais pode influenciar nas ações do Query Optimizer?

Seletividade: Podemos definir seletividade como o grau de um valor dentro de um determinado grupo, por exemplo, os jogadores da seleção de futebol são selecionados dentre milhares de opções que um técnico tem. Aplicando este conceito a banco de dados teríamos, em uma tabela de Clientes a coluna Tipo de Pessoa, pode ser J(Jurídica) ou F(Física), podemos dizer que o grau de seletividade é baixo, pois só há duas variantes do valor dentre um todo. Por outro lado, uma coluna CPF ou CNPJ será altamente seletiva, pois irá retornar apenas um registro dentre toda a tabela.

Densidade: O termo densidade vem da física, e é calculado pela divisão entre massa e o volume; uma representação matemática seria como na Figura 6. Por exemplo, estamos um pouco mais acostumados a ouvir o termo densidade geográfica. A densidade geográfica do Brasil é calculada pela quantidade de habitantes dividida pelo tamanho da área geográfica, o que seria 187.000.000 dividido por 8.514.215,3 km2 que é igual a 21,96 habitantes por km2.

No SQL Server podemos traduzir isso para: Quanto mais densa for uma coluna, mais registros ela retorna. Repare que é exatamente o contrário de seletividade, que quanto mais seletiva for uma coluna menos registros ela retorna.

Para calcular a densidade de uma coluna, o SQL efetua a query da Listagem 12.

clip_image020

Figura 6. Calculo matemático de densidade.

SELECT (1.0 / Count(Distinct <Column>))

Listagem 12. Calculo de densidade de uma coluna.

SARG(Search Arguments): São as condições, ou filtros que você aplica em sua consulta; o valor utilizado na cláusula where. O QO usa esta informação para estimar quantas linhas serão retornadas, e qual plano utilizar para acessar os dados. Com base no argumento de consulta (SARG) o SQL consegue identificar uma série de informações que serão de extrema importância para criação do plano de consulta.

Cardinalidade: Cardinalidade é utilizada para medir o número de linhas que satisfaçam uma condição. Por exemplo, imagine uma tabela com 500 linhas e uma consulta com a clausula “where Nome = ‘Joao’”, o SQL acessa as estatísticas da coluna “Nome” e lê no histograma que o valor ‘Joao’ representa 5% da tabela, portanto a cardinalidade é de (5% * 500) = 25.

Nos planos de execução podemos considerar a cardinalidade como o “estimated number of rows” que é apresentado nas dicas dos operadores. Não preciso dizer que uma má interpretação na cardinalidade pode gerar um plano ineficiente, certo?

sys.dm_db_index_operational_stats: Esta view do sistema retorna várias informações em relação à vida de um objeto no banco de dados. Você pode utilizar esta view para identificar se seu índice está sendo bem utilizado, podendo identificar uma possível contenção em uma determinada área do índice. Consulte o help do SQL Server para ver os detalhes de cada coluna.

Links

Query processing for SQL updates

http://portal.acm.org/citation.cfm?id=1007665

Maintaining Unique Indexes

http://blogs.msdn.com/craigfr/archive/2007/09/06/maintaining-unique-indexes.aspx

Non updating updates

http://blogs.msdn.com/queryoptteam/archive/2006/07/07/659453.aspx

Categorias:Não categorizado

SQL – Query Processor – Introdução

4 de setembro de 2009 3 comentários

Introdução ao Query Processor

Autor – Fabiano Neves Amorim

Início

Escolher o melhor caminho para chegar a determinado lugar pode ser considerado para muitos uma arte, isso porque sempre existem vários caminhos que levam ao mesmo destino. Executar uma tarefa da forma mais eficiente possível requer que o caminho percorrido seja o melhor dentre as centenas de variáveis que podem influenciar na escolha do melhor percurso.

No SQL Server o responsável por calcular a maneira mais eficiente de acesso aos dados é chamado de Query Processor, ele é dividido em duas partes, o Query Optimizer e o Query Execution Engine. Veremos neste artigo como o Query Optimizer funciona e quais os passos necessários para execução de um comando T-SQL.

Entender como funciona e como interpretar o trabalho do Query Optimizer é uma das melhores maneiras de aprimorar seus conhecimentos em SQL Server. Esse conhecimento será de grande valor quando você precisar fazer algum trabalho de tunning em banco de dados.

Ambiente

Para melhor entendimento dos exemplos deste artigo criaremos uma tabela, com alguns dados e uma visão, que servirão como base para os testes que serão apresentados. A Listagem 1 contém o script para criação destes objetos.

O Script cria a tabela Funcionarios com algumas informações (ID, Nome, Salário, Telefone e Cidade) e, em seguida, são inseridos alguns registros. Logo após, uma view (vw_Funcionarios) é criada. A grosso modo, podemos dizer que Views são tabelas virtuais definidas por uma consulta T-SQL. A nossa view, criada na Listagem 1, retorna o nome e o salário de todos os funcionários que ganham mais de R$ 900,00.

CREATE TABLE Funcionarios(ID Int IDENTITY(1,1) PRIMARY KEY,

Nome VarChar(30),

Salario Numeric(18,2),

Telefone VarChar(15),

Cidade VarChar(80));

INSERT INTO Funcionarios(Nome, Salario, Telefone, Cidade)

VALUES(‘José’, 850.30, ’11-55960015′, ‘São Paulo’);

INSERT INTO Funcionarios(Nome, Salario, Telefone, Cidade)

VALUES(‘Antonio’, 950, ’11-81115544′, ‘São Paulo’);

INSERT INTO Funcionarios(Nome, Salario, Telefone, Cidade)

VALUES(‘João’, 1200, ’11-44123321′, ‘São Paulo’);

CREATE VIEW vw_Funcionarios

AS

SELECT Nome, Salario FROM Funcionarios

WHERE Salario > 900

Listagem 1. Script para criação dos objetos de teste

Query Optimizer

Quando um comando T-SQL é executado no SQL Server o Query Processor entra em ação para gerar um plano de execução. Este plano dirá qual é a melhor maneira de acessar os dados gastando menos recursos e com o desempenho mais eficiente possível.

Podemos observar na Figura 1 a ação do Query Optimizer (em vermelho) e uma série de passos para compilar e executar um comando T-SQL. Vamos analisar melhor este comportamento.

clip_image002

Figura 1. Fluxograma de passos necessários para gerar um plano de execução

Supondo que um SELECT simples, por exemplo, SELECT * FROM Funcionarios, seja enviado ao servidor, a primeira tarefa que o Query Processor fará com o comando é verificar se o mesmo está no Cache Plan (mais informações sobre o Cache Plan no final do artigo). Caso ele não esteja em cache, o Query Processor enviará o comando para os processos de Parse e Bind.

O Parse/Bind executa um processo conhecido como Algebrizer. Durante este processo o SQL tenta encontrar possíveis erros de escrita na sintaxe e lógica do comando. Por exemplo, o comando “select id from tab1 group by nome” gera uma exceção, pois a coluna id não pertence ao group by e não está utilizando uma função de agregação (SUM, COUNT, …). O Algebrizer também expande as definições do comando, isso significa que ele troca “select *” por “select col1, col2, col3…”, ou “select col1 from View” pelo nome das tabelas envolvidas na view.

Sempre que uma view é referenciada em uma consulta, o SQL Server acessa as tabelas que contém os dados. Na Figura 2, por exemplo, podemos visualizar que o SQL acessa a tabela Funcionarios para ler os valores das colunas Nome e Salario.

clip_image004

Figura 2. Ilustração de uma View acessando uma tabela.

Outro passo será resolver os nomes e tipos de objetos envolvidos na consulta. Pode acontecer de haver um sinônimo para uma determinada tabela que está em outro servidor. Quando isso acontece, o SQL precisa identificar que este sinônimo faz referência a um objeto que está em outro banco de dados, e este banco pode estar até mesmo ligado a outro servidor utilizando um Linked Server.

Após estas análises o Parse/Bind retorna um binário chamado Query Processor Tree, que é uma representação lógica dos passos necessários para a execução do comando SQL. O Query Processor Tree é enviado para o próximo passo da execução da consulta, que é a análise do Query Optimizer.

É importante destacar que nem sempre um comando é enviado para a análise do Query Optimizer. Por exemplo, alguns comandos DDL, tais como o CREATE Table, que são de definição das estruturas dos dados, não têm necessidade de uma análise do Query Optimizer, pois só há uma forma de o SQL executar esta operação.

Quando o Query Optimizer recebe o Query Processor Tree, ele dará inicio a uma série de análises a fim de encontrar qual é a maneira mais eficiente de acessar os dados desejados.

O Query Optimizer trabalha baseado no custo de cada operador de acesso a dados, ou seja, ele tenta encontrar a maneira que gastará menos recursos para retornar os dados. Também é levada em consideração a velocidade de execução da consulta, por exemplo, ele pode decidir por utilizar paralelismo a fim de retornar os dados mais rapidamente. Ao optar por este recurso, o SQL utiliza mais de um processador para executar uma consulta. Dessa forma, o gasto com recursos se torna maior, já que mais processadores serão utilizados, porém, o tempo de retorno tende a ser menor comparado a execução em apenas um processador.

Durante a fase de análise o Query Optimizer realiza algumas tarefas, entre elas: identificar todos os possíveis argumentos de pesquisa que podem estar especificados na cláusula WHERE e verificar se existem Joins entre tabelas que devem ser otimizados. Baseado nestas informações, ele analisa quais as opções de acesso aos dados, quais índices deve utilizar, em qual ordem os Joins devem ser realizados e qual o melhor algoritmo de Join para cada operação.

Na análise do Query Optimizer também existe um processo chamado “Simplification”, que é executado logo no inicio da otimização. Uma das tarefas do “Simplification” é conhecida como “Predicate PushDown”, onde ele tenta reescrever sua consulta jogando os argumentos de pesquisa para a cláusula where (veja a Listagem 2).

SELECT * FROM Funcionarios

INNER JOIN Funcionarios AS Funcionarios2

ON Funcionarios.ID = Funcionarios2.ID

AND Funcionarios.Nome = ‘Antonio’

WHERE Funcionarios.ID > 1

Listagem 2. Consulta com argumentos de pesquisa especificados no join

Com base na consulta da Listagem 2, quando o comando for executado, durante a fase de Simplification, o SQL irá reescrever a segunda consulta deslocando o argumento de pesquisa (Funcionarios.Nome = ‘Antonio’) para a cláusula where, como pode ser visto na Listagem 3.

SELECT * FROM Funcionarios

INNER JOIN Funcionarios AS Funcionarios2

ON Funcionarios.ID = Funcionarios2.ID

WHERE Funcionarios.ID > 1

AND Funcionarios.Nome = ‘Antonio’

Listagem 3. Consulta com argumentos de pesquisa especificados no where

Na Figura 3 temos uma simples representação gráfica de como o Query Optimizer funciona. Como pode ser visto, o resultado da análise será o Query Plan, ou plano de execução.

clip_image006

Figura 3. Query Optimizer

Na Figura 4 podemos visualizar a representação gráfica de um plano de execução simples.

Com base na estrutura da tabela Funcionarios, podemos verificar na Figura 4 que para o comando SELECT * FROM Funcionarios, o Query Optimizer gerou um plano de execução que acessa a tabela Funcionarios utilizando o operador Clustered Index Scan.

Durante a criação de uma tabela o SQL Server automaticamente cria um índice clustered para as colunas definidas como primary key. Este é o comportamento default, e pode ser alterado definindo a primary key como nonclustered no comando de criação da tabela. O SQL utilizou este índice clustered para ler os dados da tabela Funcionarios.

Veremos mais informações sobre operadores na seção Operadores.

clip_image008

Figura 4. Execution Plan gerado pelo Query Optimizer

Visualizando o Plano de execução

Existem três maneiras de visualizar um plano de execução: o modo gráfico, modo texto e XML. Vamos analisar os dois modos mais utilizados para análise: gráfico e texto.

Para visualizar o plano de execução em modo gráfico, basta digitar um código T-SQL no SQL Server Management Studio e pressionar ctrl+l. Neste modo diversas informações complementares são exibidas. Estas informações são chamadas de hints. Os hints contêm informações importantes sobre o objeto que está sendo acessado ou sobre a operação que será executada, por exemplo, dados sobre a utilização de memória, CPU e custo de execução de cada operador dentro de todo o plano de execução. Também são apresentados dados informando se o resultado do operador está ordenado ou não, número estimado de linhas que serão retornadas e etc.

Para exibir os hints de um determinado operador, basta posicionar o cursor do mouse sobre o operador desejado.

Na Figura 5, por exemplo, podemos analisar diversas informações em relação aos custos gerados pelo operador Clustered Index Scan. Entre elas:

  • Actual Number of Rows: Número de linhas retornadas pelo operador;
  • Estimated I/O Cost: Valor relativo ao custo de I/O necessário para executar o operador. É desejado que este valor seja o menor possível;
  • Estimated CPU Cost: Valor relativo ao custo de CPU necessário para executar o operador. É desejado que este valor seja o menor possível;
  • Estimated Operator Cost: Este é o custo do operador dentro de todo o plano de execução. Um percentual de custo é exibido entre parênteses.

Uma lista completa com a descrição de cada valor exibido pode ser encontrada no Help do SQL Server.

clip_image010

Figura 5. Hint com dados do plano de execução

Como podemos perceber, o valor exibido nas informações de Cost é um número sem um real sentido, pois não é o custo em milissegundos, nem o número de IO. Este valor é utilizado internamente pelo Query Optimizer para identificar qual será o melhor operador a ser utilizado.

O modo gráfico de visualização é a maneira mais simples de analisar planos de execução. Seguem alguns pontos que são importantes de serem destacados:

  • Possui uma interface que facilita a visualização dos hints e a compreensão do plano de execução;
  • Fácil de encontrar possíveis pontos de contenção, isto é, operadores que possuem alto valor de custo. Cada operador possui um valor de 0 a 100%, onde é apresentada a porcentagem de custo de execução de cada um em relação a todo o plano de execução. Ao analisar o plano, podemos facilmente identificar quais são estes operadores;
  • Pode ser salvo em formato XML e aberto no SQL Server Management Studio (somente SQL Server 2005 e posterior).

Outra maneira de visualizar os planos de execução é utilizando o modo texto. Para habilitar a exibição do plano de execução neste modo é necessário ativar uma das opções de ShowPlan, são elas:

  • SET SHOWPLAN_TEXT ON: Retorna o plano de execução de cada comando do batch. Os comandos não são executados, portanto o plano de execução é o estimado, e não o atual;
  • SET SHOWPLAN_ALL ON: Semelhante ao SHOWPLAN_TEXT, a diferença entre eles é que o SHOWPLAN_ALL retorna mais informações sobre o plano;
  • SET STATISTICS PROFILE ON: Retorna o plano de execução completo, semelhante ao SHOWPLAN_ALL, com a diferença de que o PROFILE executa os comandos do batch, gerando o plano atual;
  • SET SHOWPLAN_XML ON: Retorna o plano de execução em um XML formatado. Não executa os comandos do batch.
  • SET STATISTICS XML ON: Semelhante ao SHOWPLAN XML, com a diferença de que o STATISTICS XML executa o batch.

Como exemplo, execute o comando SET SHOWPLAN_TEXT ON e depois execute a consulta SELECT * FROM Funcionarios. O resultado será a exibição do plano de execução em modo texto, como pode ser visto Listagem 4.

SELECT * FROM Funcionarios

|–Clustered Index Scan(OBJECT:( [dbo].[Funcionarios].[ PK_Funciona__3214EC277D430130]))

Listagem 4. Exemplo de consulta e plano de execução em modo texto

Este plano é exatamente o mesmo que visualizamos na representação gráfica da Figura 4. Ele representa uma simples leitura dos dados da tabela Funcionarios, utilizando operador Clustered Index Scan, que acessa o índice PK__Funciona__3214EC277D430130.

Na Listagem 5 podemos visualizar um plano de execução em modo texto para um update na tabela Funcionarios. Observe que o texto apresentado contém muitas funções de processos internos do SQL Server, o que acaba fazendo com que o plano fique complexo de entender. Entretanto, muitas vezes ele é a melhor maneira de se analisar o que o SQL está fazendo. Sobre este modo, os pontos que merecem destaque são:

  • Mais fácil para analisar grandes planos de execução, pois o plano fica bem organizado em uma árvore hierárquica. A área de visualização dos planos em modo gráfico exige que tenhamos que ficar arrastando as barras de rolagem de um lado para o outro, tornando a análise um pouco mais difícil;
  • Todas as informações já são exibidas, não precisa dos hints;
  • Fácil para exportar para Excel ou enviar por e-mail.

UPDATE Funcionarios SET Nome = 50

WHERE ID = ’10’

UPDATE [Funcionarios] set [Nome] = @1 WHERE [ID]=@2

|–Clustered Index Update(OBJECT:( [dbo].[Funcionarios].[PK…]), SET:( [dbo].[Funcionarios].[Nome] = [Expr1003]), DEFINE:([Expr1003]=CONVERT_IMPLICIT(varchar(30),[@1],0)), WHERE:( [dbo].[Funcionarios].[ID]=CONVERT_IMPLICIT(int,[@2],0)))

Listagem 5. Exemplo de consulta e plano de execução em modo texto

Operadores

Os ícones que são exibidos nos planos de execução (Figura 6) são conhecidos por operadores. Eles são os responsáveis por executar as operações necessárias para rodar um comando T-SQL.

Existem diversos operadores e ao longo dos artigos sobre o Query Optimizer veremos mais alguns deles. Dessa forma, vamos entender um pouco mais sobre como eles se comportam, quando e porque são necessários.

Operadores executam e calculam tarefas geradas pelo plano de execução, por exemplo, um operador de Nested Loop executa um Join. Operadores de Index Seek fazem leitura de um range de valores em um índice, por exemplo, uma consulta que deseja retornar os dados de uma tabela onde o valor da coluna Código esteja entre 5 e 10, em uma tabela que contém registros de 0 a 100, o SQL pode usar o operador de Index Seek para ler apenas as páginas de dados do índice que contém as linhas com os valores de 5 a 10. Operações de agregação como MAX, MIN, SUM, COUNT e AGV são calculadas pelo operador de Stream Aggregate. O Stream Aggregate agrupa todas as linhas de uma tabela por uma ou mais colunas, calculando o valor do comando de agregação utilizado na consulta. Por exemplo, uma consulta que deseja retornar quantos pedidos os clientes fizeram em 2008 agrupado pelo nome do cliente; neste caso, o SQL irá utilizar o Stream Aggregate para agrupar todos os clientes em uma linha e calcular o SUM da quantidade de pedidos.

Como já mencionado, para produzir o melhor plano de execução o Query Optimizer analisa qual é o melhor tipo de operador para executar determinada tarefa, tentando encontrar qual deles executará o processo mais rápido e/ou utilizando a menor quantidade de recursos.

Todo operador executa os métodos Open(), GetRow() e Close(). Devido a esta característica, os operadores são independentes, e, por isso, o SQL consegue gerenciá-los com maior facilidade. Como não existe dependência, um operador SORT pode ser utilizado para ordenar um resultado ou pode ser utilizado para ordenar uma coluna, a fim de saber qual é o maior valor (MAX) da tabela. Isso faz com que o Query Optimizer tenha mais opções de escolha sobre qual operador utilizar.

Uma característica importante a ser destacada é que existem duas categorias de operadores: os operadores “nonblocking” e os operadores “blocking” ou “stop-and-go”:

  • Os operadores nonblocking são aqueles que lêem as linhas do input e produzem o output ao mesmo tempo. No método conhecido por GetRow(), conforme a leitura das linhas vai acontecendo, o operador executa sua tarefa e retorna o output para o próximo operador fazer seu trabalho. O operador de Nested Loop é um bom exemplo desse comportamento. Assim que a primeira linha é recebida (GetRow()) o SQL faz o Join com a tabela do outer, e caso o relacionamento seja verdadeiro a linha é retornada no output. Este processo se repete até o fim da leitura da tabela do Join;
  • Já os operadores “blocking” necessitam primeiro ler todas as linhas (normalmente no método Open()) do input para depois retorná-las para o output. Um exemplo clássico de operadores blocking é o Sort, que necessita primeiro ler todas as linhas do input para fazer a ordenação e, por fim, retornar as linhas ordenadas.

Existem alguns cenários onde cada tipo de operador é melhor aproveitado, por exemplo, operadores nonblocking são preferíveis quando utilizamos as cláusulas TOP, Hint FAST N ou mesmo um Exists. Como exemplo vamos utilizar o comando Exists. Este comando é utilizado para validar se um valor existe dentro de um contexto. Normalmente ele é utilizado junto com subqueries para validar se um valor existe em uma tabela.

Durante a execução do Exists, se ao menos uma linha for retornada para o output, isso já caracteriza uma condição verdadeira. Ou seja, se houve algum retorno, isso significa que o valor existe, e neste caso o Query Execution (responsável por executar os planos gerados pelo Query Optimizer) encerra o processo de leitura (GetRow).

Apenas uma linha é o suficiente para saber se o valor procurado existe ou não. Se tivermos que esperar todas as linhas serem lidas para depois retornar o output, como os operadores blocking fazem, esta vantagem de encerrar o processo assim que encontrar alguma linha não seria aproveitada.

Existem pouco mais de 100 operadores, entre eles: Scan, Compute Scalar, Aggregate, Sort, Spools e Key Lookup, apresentados na Figura 6.

clip_image012

Figura 6. Alguns operadores do plano de execução

Para finalizar esta seção é importante citar que não existe o melhor operador para fazer um Join ou uma Agregação. Cada operador é melhor para uma determinada situação.

Cache

Como podemos observar, o processo de geração de um plano de execução é bem complexo e requer um tempo extra na execução da consulta. Quanto maior e mais complexa for sua consulta, mais tempo o Query Processor leva para analisar todas as variantes, e por fim gerar um plano de execução para o comando T-SQL. Toda essa operação de gerar o plano de execução causa delay e normalmente faz alto uso de CPU. Por isso, o SQL Server usa um mecanismo para evitar que toda vez que uma consulta for enviada para o banco, o Query Processor tenha que gerar um novo plano de execução. É justamente este o papel do Cache Plan: evitar que uma consulta que já tenha passado pelo Query Optimizer seja enviada novamente para otimização.

Conforme podemos observar no fluxograma (Figura 1), o primeiro processo que o Query Processor faz é procurar no cache se já existe um plano de execução para a consulta enviada ao servidor. Isso significa que quando um plano de execução é gerado para uma determinada consulta, o SQL grava este plano em uma área de memória a fim de reutilizar o mesmo plano de execução para consultas semelhantes.

A área de memória utilizada para armazenamento dos planos de execução é exatamente a quantidade de memória disponível para uso do servidor SQL Server. No SQL Server 7.0 havia uma área específica para o armazenamento dos planos, chamada “procedure cache”. Porém, a partir da versão 2000 isso foi alterado para que o SQL pudesse utilizar toda a memória disponível ao SQL Server. Na verdade, no SQL Server 7.0 só eram armazenados planos de execução de procedures (por isso se chamava “procedure cache”), consultas ad-hoc não tinham o plano armazenado em cache.

Reutilizando planos de consulta

Em muitas ocasiões, reutilizar o plano de execução pode ser uma grande vantagem, já que economizamos todo o tempo que seria gasto pelo Query Processor para analisar um novo plano. Entretanto, reutilizar um plano de execução que está armazenado em cache pode ser um problema, pois nem sempre a mesma consulta retorna a mesma quantidade de dados. Por isso, o SQL Server é bem cauteloso quando estamos falando de reutilização de planos de execução, já que pode haver situações onde um mesmo plano não seja o ideal para consultas parecidas. Por exemplo, vamos imaginar que uma consulta que retorna apenas 10 linhas seja enviada pela primeira vez para o SQL Server, e o Query Processor gerou um plano de execução que utiliza um índice nonclustered mais um bookmark para ler uma pequena quantidade de registros. Nestas condições o Query Engine executa este plano e depois o armazena em cache.

Agora imagine que uma nova consulta, igual à primeira, seja enviada para o SQL Server, mas desta vez a condição de busca informada no where requeira que milhares de linhas sejam retornadas. Se o SQL optar por reutilizar o plano de execução isso não vai ser bom, pois é bem provável que seja melhor fazer um Scan em toda a tabela do que utilizar o índice mais o bookmark. O cenário descrito é bastante comum de acontecer.

Outro fator importante de ser observado é que algumas operações podem deixar um plano que está em cache desatualizado. Por exemplo, pode acontecer do schema que envolve a consulta ser alterado, ou um novo índice ser criado ou até mesmo apagado. Nestas situações o SQL não conseguirá reutilizar o plano de execução, o que resulta em uma nova análise do Query Processor.

Existem diversas maneiras de saber se um plano de execução foi reutilizado ou não. Para isso abra uma consulta no SQL Server Management Studio e execute o código da Listagem 6.

Na primeira linha é executado um comando (DBCC FREEPROCCACHE) para limpar a área de cache atual, apagando todos os planos de consulta que estão armazenados em memória. Feito isso, são executadas três simples consultas para retornar os dados de um determinado funcionário especificado na cláusula where. Por fim, selecionamos os registros da sys.dm_exec_cached_plans, que é uma view do sistema que retorna os planos de execução que estão em cache. Esta view retorna uma coluna chamada plan_handle que é do tipo binário. Para converter este valor binário para um texto, o SQL disponibiliza uma function do sistema chamada sys.dm_exec_sql_text. Ela lê uma entrada em binário e retornar uma coluna chamada text, que contém o plano em modo texto.

DBCC FREEPROCCACHE;

GO

SELECT * FROM Funcionarios WHERE Nome = ‘José’

GO

SELECT * FROM Funcionarios WHERE Nome = ‘Antonio’

GO

SELECT * FROM Funcionarios WHERE Nome = ‘José’

GO

SELECT usecounts, cacheobjtype, objtype, "text" AS Comando_SQL

FROM sys.dm_exec_cached_plans

CROSS APPLY sys.dm_exec_sql_text (plan_handle)

WHERE cacheobjtype = ‘Compiled Plan’

AND "text" NOT LIKE ‘%dm_exec%’;

Listagem 6. Exemplo de consulta e acesso ao cache plan

O resultado do comando acima deve ser parecido com os registros da Tabela 1.

UseCounts

CacheObjType

Objtype

Comando_SQL

1

Compiled Plan

Adhoc

SELECT * FROM Funcionarios WHERE Nome = ‘Antonio’

2

Compiled Plan

Adhoc

SELECT * FROM Funcionarios WHERE Nome = ‘José’

Tabela 1. Valores que estão em cache retornados pela DMV sys.dm_exec_cached_plans

A DMV (Dynamic Management Views) sys.dm_exec_cached_plans retorna todos os planos de execução que estão armazenados no cache do SQL Server. Você pode utilizar esta view para saber se um determinado plano foi reutilizado ou não, a quantidade de memória que um plano está utilizando e também para verificar quantas vezes um plano foi reutilizado.

A coluna UseCounts retorna a quantidade de vezes em que o plano de execução foi reutilizado. A coluna CacheObjType retorna o tipo do objeto em cache. Ela pode apresentar os seguintes valores:

· Compiled Plan: Representa um plano de execução completo;

· Compiled Plan Sub: Existe uma propriedade do banco chamada “optimize for ad hoc workloads”. Quando habilitada, assim que uma consulta AdHoc é enviada pela primeira vez ao SQL, ele grava um Plan Sub no cache, não com o plano completo, mas somente com um Plan Sub. Isso vai permitir que o SQL reconheça que um plano já foi gerado para esta consulta. A partir da segunda execução deste comando, o SQL substitui este plano pelo plano completo;

· Parse Tree: Representa o parse de uma view, function, etc.;

· Extended Proc: Grava o plano para extended procedures, por exemplo, a xp_cmdShell;

· CLR Compiled Func: Funções criadas utilizando CLR;

· CLR Compiled Proc: Procedures criadas utilizando CLR.

A coluna Objtype retorna qual o tipo do objeto que está armazenado, por exemplo, pode ser um comando Adhoc, proc, view, trigger, entre outros.

Observe na Tabela 1 que a coluna UseCounts contém o valor 2 para a consulta SELECT * FROM Funcionarios WHERE Nome = ‘José’, mostrando que o SQL Server utilizou duas vezes o mesmo plano de execução.

Uma observação importante em relação ao cache plan é que para consultas adhoc, que não estejam parametrizadas (ou seja, iguais a que utilizamos acima), para que o Query Processor consiga fazer proveito do plano que está em cache as consultas precisam ser escritas exatamente iguais. Inclusive os valores no where terão que ser os mesmos. Repare que no exemplo da Listagem 6 o SQL só reutilizou o plano de execução em cache porque a terceira consulta enviada para o servidor é idêntica à primeira consulta, inclusive o WHERE Nome = ‘José’.

Isso significa que cada caractere será analisado e comparado com o comando em cache. Uma simples quebra de linha ou uma letra maiúscula ou até mesmo um código de comentário já será o suficiente para inviabilizar o uso do plano em cache, acarretando na criação de um novo plano de execução.

Parametrização

Sempre que possível o SQL Server tenta alterar sua consulta para torná-la apta à reutilização dos planos de execução. Este comportamento chama-se parametrização. Consultas parametrizadas têm mais chances de terem seu plano de execução reutilizado.

No SQL Server 2005 existem dois tipos de parametrização, o Simple Parameterization (conhecido como auto-parameterization, no SQL Server 2000) e Forced Parameterization. Ao utilizar a parametrização Simple, o SQL é mais cauteloso em relação às quais consultas parametrizar, já a opção forced tenta parametrizar a maior quantidade de consultas possível.

Quando um banco de dados é criado o padrão de parametrização é o Simple. Para alterar esta opção acesse as propriedades do banco de dados e selecione a opção Forced na propriedade Parameterization.

Quando a opção Forced está selecionada o SQL tenta parametrizar a maior quantidade de consultas possíveis. Existe uma lista de instruções que impedem o uso de parametrização. Esta lista pode ser acessada no help do SQL Server.

Durante o processo de parametrização o SQL Server tenta alterar o valor no where por uma variável, e futuramente ele altera este valor pelo novo valor informado na consulta.

O SQL Server é bem cauteloso em relação a quando parametrizar uma consulta. Este processo somente será adotado para consultas que são consideradas seguras. Uma consulta é segura quando o plano selecionado não muda caso os parâmetros (filtros) mudem. Vejamos um exemplo de consulta considerada segura:

  • Para uma consulta que busca por um valor em uma tabela, passando um filtro para uma coluna que contém um índice único, o SQL sabe que apenas um valor será retornado. Independente do valor do parâmetro recebido, pois a presença do índice único obriga que exista apenas um registro com o mesmo valor em toda a tabela.

Vejamos um exemplo de consulta considerada não segura:

  • Fazer um seek, ou seja, ler um range de informação a partir de um índice pode ser uma ótima escolha. Por outro lado, utilizar este mesmo plano para uma consulta que retorna várias linhas pode não ser a melhor opção.

Vamos analisar um exemplo de parametrização e reutilização do plano de execução. Para isso execute o código da Listagem 7. Neste código, executamos duas simples consultas que retornam os dados dos funcionários, com base no ID especificado na cláusula where. Como a coluna ID é a chave primária da tabela funcionários, os valores de ID não podem se repetir. Isso caracteriza esta consulta como segura para reutilização do plano de execução.

Podemos observar na Tabela 2 que o SQL reutiliza o mesmo plano para as duas consultas.

SELECT * FROM Funcionarios

WHERE ID = 1

GO

SELECT * FROM Funcionarios

WHERE ID = 2

GO

SELECT usecounts, cacheobjtype, objtype, "text" AS Comando_SQL

FROM sys.dm_exec_cached_plans

CROSS APPLY sys.dm_exec_sql_text (plan_handle)

WHERE cacheobjtype = ‘Compiled Plan’

AND "text" NOT LIKE ‘%dm_exec%’;

Listagem 7. Exemplo de consulta parametrizada

O resultado de execução da Listagem 7 deve ser parecido com os registros da Tabela 2.

UseCounts

CacheObjType

Objtype

Comando_SQL

1

Compiled Plan

Adhoc

SELECT * FROM Funcionarios WHERE ID = 2

1

Compiled Plan

Adhoc

SELECT * FROM Funcionarios WHERE ID = 1

2

Compiled Plan

Prepared

(@1 tinyint)SELECT * FROM [Funcionarios] WHERE [ID]=@1

Tabela 2. Valores que estão em cache retornados pela DMV sys.dm_exec_cached_plans

Como podemos observar nas colunas UseCounts e Objtype, o SQL Server gerou um plano de execução “prepared” e ele foi utilizado duas vezes.

As outras consultas continuam aparecendo, mas elas não contem o plano de execução, elas apontam para a linha que tem o plano de execução “prepared”. Infelizmente com as DMVs e recursos disponibilizados pelo SQL Server, não é possível visualizar esta ligação entre os planos Adhoc e o plano Prepared, este é um código interno do SQL. Estas consultas Adhoc são conhecidas como Shell Queries, elas são armazenadas para agilizar o processo de reutilização do plano. Veja na Figura 7 um exemplo de como estas consultas podem ajudar na performance da execução do comando.

clip_image014

Figura 7. Fluxograma de procura por plano de execução em cache

Nesta figura podemos observar que quando um mesmo comando for executado, caso ele contenha um shell query no cache, o SQL pula uma etapa do processo de localização do plano.

Observe na Tabela 2 que a linha 3 possui o plano de execução parametrizado, que é o plano prepared. O plano prepared foi gerado baseado nas consultas executadas pelo código da Listagem 7. Como podemos observar, o SQL Server trocou o valor no where por uma variável @1 e declarou a variável como datatype tinyint. Ao parametrizar uma consulta o SQL tenta encontrar qual é a melhor opção de variável a ser utilizada, neste caso, ele optou pelo tinyint por ela ocupar apenas 1 byte de armazenamento.

O datatype tinyint consegue tratar valores de 0 a 255, isto é, caso o valor informado no where seja 300 o SQL não conseguirá utilizar este plano de execução, causando uma nova recompilação do comando.

clip_image015

Figura 8. Exemplo de consulta parametrizada

Na Figura 8 podemos visualizar como funciona o conceito de parametrização. Ao parametrizar o valor de where Prikey, o SQL consegue reutilizar o mesmo plano de execução para as três consultas apenas alterando o valor de “?”.

Conclusão

Vimos neste artigo quais os métodos que o SQL Server utiliza para executar uma consulta SQL e como o Query Processor trabalha para acessar os dados armazenados nos arquivos de banco de dados. Ao acessar os dados, estudamos um pouco dos operadores de acesso a dados, bem como os tipos de operadores.

Também vimos como analisar um plano de execução e as análises em modo gráfico e modo texto. Por último foi apresentado o conceito de Cache Plan e parametrização de consultas.

Linked Server: É um mecanismo que permite que o SQL Server execute comandos em outro banco de dados OLE DB. Ao criar um linked Server o SQL cria uma conexão com o banco de dados destino, permitindo que os dados que estão armazenados no banco destino sejam acessados a partir da própria instancia do SQL Server. Por exemplo, é possível abrir uma consulta no SSMS e fazer um update em uma tabela do SQL Server lendo os dados de uma base Oracle.

O banco utilizado no Linked Server pode ser remoto ou local. Podemos criar um linked Server com um banco de dados Access, Oracle, MySQL, ou qualquer banco de dados que contenha um driver OLE DB.

OLE DB: (Object Linking and Embedding, Database) é um pacote que contém as informações necessárias para conexão e gerenciamento da leitura dos registros armazenados em uma fonte de dados (arquivos txt, xls, entre outros) ou bancos de dados.

Categorias:Não categorizado

Transaction Log for tempdb is full…

6 de julho de 2009 5 comentários

Essa não foi tão difícil mas pode ser que nem todos sigam a mesma linha de raciocínio que eu, então segue ai o que fiz pra resolver o problema.

Cheguei hoje cedo e um de nossos clientes havia me enviado um e-mail na sexta dizendo que não estavam conseguindo rodar um processo de exportação de dados no servidor de homologação. Segue a mensagem de erro do aplicativo responsável pela carga…

clip_image002

Se você procurar bem, verá que existe uma mensagem bem clara do problema…

Bom, vendo isso, fui dar uma olhada no espaço ocupado pelo Tempdb…

Rodei um sp_helpdb tempdb e obtive o seguinte retorno:

name

fileid

filename

filegroup

size

maxsize

growth

usage

tempdev

1

E:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\DATA\tempdb.mdf

PRIMARY

10439680 KB

Unlimited

10%

data only

templog

2

E:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\DATA\templog.ldf

NULL

149696 KB

Unlimited

10%

log only

10gb de espaço pro tempdb dependendo do cenário até que é normal… mas e o espaço no disco E: como está?

Rodei a xp_fixeddrives para ver o espaço livre…

drive

MB free

C

2229

E

126

F

13577

Umm, 126 mb livre…

Temos varias formas de resolver o problema.

1. Reiniciar o servidor pro SQL recriar o tempdb

2. Rodar um shrink no TempDB

3. Criar mais um arquivo de Log e de Dados para o TempDB no disco F que tem mais espaço livre.

Como é um servidor de homologação e não sou eu que administro, optei por apenas fazer o shrink no tempdb. Mesmo sabendo que possivelmente este problema volte a ocorrer, por enquanto pra mim só isso resolve…

Se fosse meu servidor, com certeza a disposição dos arquivos de dados estariam bem diferentes…

Falow…

Categorias:Não categorizado

Em um projeto não muito distante….

1 de julho de 2009 1 comentário

Galera, a alguns meses atrás escrevi que estava participando de um projeto bem interessante e que logo eu postaria algumas novidades em relação a ele.

Bom, faz tempo que estou querendo escrever este post… tenho certeza que muitos que estão lendo vão se identificar com as situações que encontramos em nosso dia a dia.

Coisas que rolaram no projeto

Frases que ouvi:

– Eu não mexi em nada! Só apertei o OFF. (Essa foi f. Então mexeu caramba, rs…)

– Faz um Cola e Copia.(Umm? Não seria Copia e Cola? rs)

– Mas posso fazer o backup com os usuários logados na base? Não vai derrubar todo mundo?

DataCenter que ganha mais de 150 reais hora pra administrar o banco SQL liga de madrugada. – Esta faltando espaço no disco do servidor SQL. Vou dar uma olhada, …. Arquivo de paginação no C: com 16 GB, detalhe, era o tamanho padrão…. 5 GB de dados na lixeira WTF…

Como mania, rodo um DBCC SHOWCONTIG em uma de nossas maiores tabelas só pra ver como andam as coisas… resultado?

Densidade da Verificação [Melhor Contagem : Contagem Real]…….: 12.14% [1521:15869]

Havia um script rodando a mais de 2 dias e meio, daí vieram me falar… – Ou aquele script que enviaram esta rodando no servidor a mais de 2 dias e não termina. Na hora já falei, – Para meu!, ta em LOOP infinito. Fui ver… batata, ainda estava no primeiro registro, hahuauauhua.

Não pude usar SSIS e criptografia MD5(aqui até tive que concordar um pouco).

Reuniões e mais reuniões com pessoas que não decidem, para decidir qualquer coisa que não precisava ser decidida naquela hora, e que futuramente, seria decidido que seria feito de maneira diferente do que foi decidido. Com pessoas que falam assim:

– Anyway, pelo feedback de nossos customers, na reunião do GO to GO vamos ter que adiar o esclarecimento com os PMOs para a próxima meeting.

Em uma das conversões, fiquei 36 horas online direto no cliente só na base do café :-). E no fim de tudo, a conversão não deu certo e escutamos um sermão(com razão) de nosso Diretor que havia ido pro cliente pra ver como estava o projeto.

Coisas que aprendi, e outras que deram certo.

Fizemos um script de mais de 4 mil linhas que quando executado lia os dados de duas bases e gerava uma outra base com a informação das duas empresas, ou seja, Le os dados do Banco x e insere no banco y. Resultado, banco final do Grupo que comprou os bancos x e y.

Conheci profissionais acima da média. Tive contato com Diretores e Superintendentes que dava pra perceber fácil porque ocupavam estes cargos.

Entendi porque algumas pessoas ganham muito mais que as outras.

Frase de meu superior direto. – Precisamos conversar com a equipe, não possível. A impressão que tenho é que enquanto alguns estão na Formula1, outros estão no Parque do Hopi Hari.

Para trabalhar com clientes grandes, não tem jeito. Se você não documentar tudo, vai sofrer as conseqüências, e elas são severas.

Cuidado, existem mais pessoas interessadas em crescer a qualquer custo do que você pensa. Esteja sempre preparado.

Não pretendo sair do interior tão cedo. Não há dinheiro que pague o valor de uma vida com qualidade.

È galera comecei a escrever e vi que este post também foi mais pro lado pessoal do que eu pensava rs. Ainda tem muita coisa pra rolar e vou tentar escrever mais conforme as coisas vão surgindo, daí não esqueço de nada.

Falow

Obs.: Todos os fatos acima mencionados são meramente ficticios ou nunca aconteceram, ou não vão acontecer, ou não fui eu quem viu, ou me contaram, ou eu li, ou eu ouvi, resumindo: Se precisar, Eu nego!

Categorias:Não categorizado

SQL Magazine edição nº 65

22 de junho de 2009 1 comentário

Galera finalmente o artigo que escrevi pra SQL Magazine foi publicado na edição deste mês.

clip_image002

Escrevi falando sobre o Query Processor e estamos com outro artigo de continuação no forno… aguardem pra ver…

Abraço

Categorias:Não categorizado

Produto Cartesiano e QO 2

29 de abril de 2009 Deixe um comentário

Pessoal para quem achou interessante meu post sobre Produto
Cartesiano e Query Optimizer
, ontem o Craig escreveu um pouco sobre isso no blog dele,
segue o link:

 

Implied Predicates and Query Hints

 

Abraço.

Categorias:Não categorizado