quinta-feira, 29 de novembro de 2012

IDENTITY na PK - Fragmentação

Este é um dos posts sobre os exemplos apresentados na palestra sobre modelagem no 24 Hours of PASS - PT. Você pode ver a série de posts em Impactos da Modelagem na Performance do SQL Server.

Por default, no SQL Server uma coluna declarada como chave primária (constraint PRIMARY KEY) cria automaticamente um índice clustered (se você não está familiarizado com os tipos de índice do SQL Server, veja este vídeo sobre o assunto [ainda não disponível]). No índice clustered, como a ordem lógica dos registros em disco é mesma ordem da chave do índice, se os valores da chave nas linhas sendo inseridas são monotônicos (sempre crescente ou sempre decrescente) as inclusões não causam fragmentação na tabela. Se a chave não for monotônica a tabela sofrerá fragmentação.

O script a seguir demonstra isto. Copie ele para uma nova query no Management Studio (não esqueça de mudar para um banco de testes), vá executando os pedaços de código entre os comentários e veja os resultados. Qualquer dúvida posta uma pergunta!


  
  
/* ===============================================================================================================================
   1 - Chave não-monotônica: causa fragmentação    
   Neste script criamos uma tabela de testes (Pessoas) que tem uma PK na coluna EMail (varchar).
   Incluimos 8.000 linhas com valores de e-mails "aleatórios" (não ordenados), e vemos a fragmentação causada.
   Reconstruímos o índice base da PK e vemos a fragmentação ser eliminada pela reordenação dos registros e das páginas da tabela.
   Inserimos mais 8.000 linhas e novamente a tabela se fragmenta. 
   A PK não é monotônica.
   =============================================================================================================================== */

-- Criação de tabela com PK "natural" (E-mail)

DROP TABLE Pessoas
CREATE TABLE Pessoas
(
 Codigo int not null identity,
 Email varchar(200) not null,
 Nome varchar(50) not null, 
 Sobrenome varchar(50) not null,
 CPF char(11) not null,
 UF char(2)

 CONSTRAINT PK_Pessoas PRIMARY KEY (Email),
)
GO

-- Pega nome e sobrenome de AdventureWorks2012.Person.Person (download em http://msftdbprodsamples.codeplex.com/releases/view/93587) pra gerar dados de teste
-- O uso de um cursor na inserção é para simular uma aplicação transacional inserindo linhas na tabela
-- (Se usarmos um INSERT INTO ... SELECT ... o Query Optimizer ordena as linhas antes de fazer a inserção, e a fragmentação não ocorre)

-- Pega os 1os 8.000 registros do SELECT que gera os dados de teste e os insere na tabela

DECLARE @nome varchar(100), @sobrenome varchar(100)
DECLARE c CURSOR FOR 
 SELECT DISTINCT TOP 8000 FirstName, LastName = isnull(MiddleName,'') + LastName
 FROM AdventureWorks2012.Person.Person
 ORDER BY LastName ASC
OPEN c
FETCH NEXT FROM c INTO @nome, @sobrenome
WHILE @@FETCH_STATUS = 0
BEGIN
 INSERT INTO Pessoas VALUES (
  @nome + '.' + @sobrenome + '@srnimbus.com.br', -- Email
  @nome,      -- Nome
  @sobrenome,     -- Sobrenome
  LEFT(NEWID(), 11),    -- CPF
  LEFT(@nome,1) + LEFT(@sobrenome,1)  -- UF
 )
 FETCH NEXT FROM c INTO @nome, @sobrenome
END
CLOSE c
DEALLOCATE c
GO

SELECT COUNT(*) FROM Pessoas

-- PK EMail não é monotônica: fragmentação altíssima

DBCC SHOWCONTIG('Pessoas')
GO
-- Se reconstruirmos o índice a fragmentação é eliminada pela reordenação das páginas, mas isto é só temporário - novas
-- operações de inclusão e alteração irão fragmentar novamente a tabela (o que não ocorre numa PK IDENTITY)

ALTER INDEX PK_Pessoas ON Pessoas REBUILD
DBCC SHOWCONTIG('Pessoas')
GO

-- Inserção de mais 8.000 linhas fragmenta a tabela de novo

DECLARE @nome varchar(100), @sobrenome varchar(100)
DECLARE c CURSOR FOR 
 SELECT DISTINCT TOP 8000  FirstName, LastName = isnull(MiddleName,'') + LastName
 FROM AdventureWorks2012.Person.Person
 order by LastName DESC
OPEN c
FETCH NEXT FROM c INTO @nome, @sobrenome
WHILE @@FETCH_STATUS = 0
BEGIN
 INSERT INTO Pessoas VALUES (
  @nome + '.' + @sobrenome + '@srnimbus.com.br', -- Email
  @nome,      -- Nome
  @sobrenome,     -- Sobrenome
  LEFT(NEWID(), 11),    -- CPF
  LEFT(@nome,1) + LEFT(@sobrenome,1)  -- UF
 )
 FETCH NEXT FROM c INTO @nome, @sobrenome
END
CLOSE c
DEALLOCATE c
GO

DBCC SHOWCONTIG('Pessoas')




/* ===============================================================================================================================
   2 - Chave monotônica: não gera fragmentação.
   Neste script repetimos a sequencia do script anterior, com a diferença de que a PK agora é monotônica
   (uma coluna IDENTITY).
   Incluimos 8.000 linhas com valores de e-mails "aleatórios" (não ordenados), e não ocorre fragmentação, pois as chaves dos registros é 
   crescente.
   Inserimos mais 8.000 linhas e nenhuma fragmentação ocorre. 
   A PK é monotônica.
   =============================================================================================================================== */

-- Criação de tabela com PK artificial (Codigo - identity)

DROP TABLE Pessoas
CREATE TABLE Pessoas
(
 Codigo int not null identity,
 Email varchar(200) not null,
 Nome varchar(50) not null, 
 Sobrenome varchar(50) not null,
 CPF char(11) not null,
 UF char(2)

 CONSTRAINT PK_Pessoas PRIMARY KEY (Codigo),
)
GO

-- Pega os 1os 8.000 registros do SELECT que gera os dados de teste e os insere na tabela

DECLARE @nome varchar(100), @sobrenome varchar(100)
DECLARE c CURSOR FOR 
 SELECT DISTINCT TOP 8000 FirstName, LastName = isnull(MiddleName,'') + LastName
 FROM AdventureWorks2012.Person.Person
 ORDER BY LastName ASC
OPEN c
FETCH NEXT FROM c INTO @nome, @sobrenome
WHILE @@FETCH_STATUS = 0
BEGIN
 INSERT INTO Pessoas (Email, Nome, Sobrenome, CPF, UF)
 VALUES (
  @nome + '.' + @sobrenome + '@srnimbus.com.br', -- Email
  @nome,      -- Nome
  @sobrenome,     -- Sobrenome
  LEFT(NEWID(), 11),    -- CPF
  LEFT(@nome,1) + LEFT(@sobrenome,1)  -- UF
 )
 FETCH NEXT FROM c INTO @nome, @sobrenome
END
CLOSE c
DEALLOCATE c
GO

SELECT COUNT(*) FROM Pessoas

-- PK Codigo é monotônica: nenhuma fragmentação ocorre - inserção é sempre no "fim" da tabela

DBCC SHOWCONTIG('Pessoas')
GO

-- Inserção de mais 8.000 linhas: tabela se mantém compacta

DECLARE @nome varchar(100), @sobrenome varchar(100)
DECLARE c CURSOR FOR 
 SELECT DISTINCT TOP 8000  FirstName, LastName = isnull(MiddleName,'') + LastName
 FROM AdventureWorks2012.Person.Person
 order by LastName DESC
OPEN c
FETCH NEXT FROM c INTO @nome, @sobrenome
WHILE @@FETCH_STATUS = 0
BEGIN
 INSERT INTO Pessoas (Email, Nome, Sobrenome, CPF, UF)
 VALUES (
  @nome + '.' + @sobrenome + '@srnimbus.com.br', -- Email
  @nome,      -- Nome
  @sobrenome,     -- Sobrenome
  LEFT(NEWID(), 11),    -- CPF
  LEFT(@nome,1) + LEFT(@sobrenome,1)  -- UF
 )
 FETCH NEXT FROM c INTO @nome, @sobrenome
END
CLOSE c
DEALLOCATE c
GO

DBCC SHOWCONTIG('Pessoas')

Impactos da Modelagem na Performance do SQL Server

Oi Gente,

O pessoal da Sr. Nimbus apresentou 3 palestras no 1º dia do evento "24 Hours of PASS - Portuguese Edition". Minha palestra, juntamente com o Ivan, foi "Como (Não) Modelar Seu Banco de Dados". Nós buscamos mostrar situações nas quais uma modelagem pobre pode impactar severamente a performance do SQL Server, através do aumento de uso de recursos no armazenamento e na execução de consultas.

A palestra foi dividida em 3 "episódios": Chaves Primárias, Modelagem e o Otimizador, e Qual o Tamanho da Coluna?. Apresentei "Chaves Primárias" e "Qual o Tamanho da Coluna", e como os exemplos ficaram interessantes, irei publicá-los aqui em uma pequena série de posts:

  • Chaves Primárias:
    • Introdução aos tipos de índices no SQL Server [ainda não disponível]. Pré-requisito para os artigos sobre chaves primárias, que no SQL Server são baseadas por default em índices do tipo clustered.
    • IDENTITY na PK - Fragmentação. Uma chave não-monotônica pode causar fragmentação na sua tabela, o que aumenta o espaço de armazenamento e degrada o tempo de IO.
    • IDENTITY na PK - Espaço e IO. PK's "grandes" e passíveis de modificação incham a tabela e suas páginas de índice, e causam mais IO do que chaves "pequenas" e imutáveis.
  • Qual o Tamanho da Coluna?
    • TEXT x VARCHAR(MAX). Apesar de sintaticamente equivalentes, há uma característica de armazenamento que pode impactar (e muito) na performance de IO e no uso de RAM para consultas em tabelas que armazenam LOB's (Large Objects - informação > 8.000 bytes).
    • Memory Grant em varchar(8000). Cuidado com o espaço declarado para suas colunas de tamanho variável. Ela só ocupa o espaço necessário para o armazenamento dos dados, mas aquele "(8000)" pode trazer um uso desnecessário e pesado de memória, e até causar contenção entre as consultas sendo executadas.
Estes exemplos são legais porque nos cenários mostrados não há mensagens de erro, mas o uso exagerado de recursos devido a detalhes de modelagem podem afetar negativamente o uso de recursos e a performance do seu servidor.

A palestra será publicada pelo pessoal do PASS em http://www.sqlpass.org/Default.aspx?TabId=62

Espero que seja útil!

[]s,

GB

quarta-feira, 28 de novembro de 2012

SSMS 2012: Cadê o CTRL+R ?

No SQL Server 2012, o Management Studio (SSMS) é baseado na shell (a IDE "crua") do Visual Studio. A primeira coisa que notei foi que o atalho preferido de DBA's e desenvolvedores, o CTRL+R que serve pra mostrar e esconder o Result Pane, sumiu. Mas sem CTRL+R não dá. Simplesmente não dá.

Pra voltar ele pro ambiente:

  1. Vá em "Tools | Customize | Keyboard..."
  2. Selecione o comando "Window.ShowResultPane"
  3. Na lista "Use new shortcut in" selecione "SQL Query Editor"
  4. Coloque o cursor no campo "Press shortcut keys" e pressione "CTRL+R"
  5. Clique em "Assign" e "Ok".


Ufa. Alívio!

[]s,

GB

quinta-feira, 5 de julho de 2012

Recursos de Formação – TFS

Pra quem quer começar ou se especializar um pouco mais em TFS, aí vão alguns recursos gratuitos:

sexta-feira, 22 de junho de 2012

Como clonar VM's no Hyper-V

Biblioteca de VM's - ou biblioteca de VHD's - são excelente ferramentas para desenvolvedores criarem rapidamente um ambiente de testes. Mas você pode esbarrar em ferramentas instáveis e as famosas Blue Screen of Death se você não souber clonar corretamente os VHD's de sua biblioteca para a criação de novas VM's. A combinação de VHD's diferenciais com o sysprep reduz bastante o trabalho de subir corretamente clones de uma VM que precisam rodar em um domínio.

Contexto

Instalei um Windows 2008 R2 em uma VM do Hyper-V pra testes. Pra não ter que passar pelo tempo de instalação quando criasse novas VM's, separei o VHD dela, marquei como read-only, e comecei a criar novas VM's usando o recurso de discos diferenciais do Hyper-V, que permite criar discos baseado em um disco "pai". O disco "filho" - o disco diferencial - contém tudo o que o disco pai contém, e modificações nos arquivos são gravados no disco diferencial, deixando o disco pai intacto para servir de base para outros discos. (Se estiver curioso sobre discos diferenciais, dê uma olhada no artigo Virtual Hard Disk (VHD) Architecture explained).

Problema

Até o momento, eu tinha instalado tudo que eu queria testar em uma única VM. P.e., uma VM tinha SharePoint + SQL Server. Outra tinha um DC + Visual Studio + TFS + SQL Server. Eu testava tudo subindo só uma VM. Meu problema foi quando quis criar um domínio e subir várias VM's simultaneamente, usando o disco pai pra criar estas VM's.

O problema é que o Windows gera "coisas" durante a instalação que devem ser únicas em um domínio. Parece que existem várias destas "coisas", mas a que me deu problema foram os SID's das máquinas e dos usuários. Um SID é, basicamente, o identificador único de um objeto (máquina, usuário, grupo, etc) em um domínio. Se você simplesmente copia o VHD de uma VM e cria uma nova VM usando este VHD como disco, os SID's são duplicados. Isto causa problemas e erros quando estas VM's estão no mesmo domínio pois o Windows não espera encontrar SID's duplicados no domínio.

Solução

Como qualquer problema, tem várias formas de resolver isto. A que mais se adequou ao meu objetivo, que é criar uma biblioteca de VHD's para subir rapidamente ambientes Windows de desenvolvimento e testes que serão usados e depois descartados, foi:
  1. Criar uma VM e instalar o sistema operacional (Windows 2008 R2).
  2. Configurar opções comuns a todas as VM's que se basearão nesta instalação. Em particular: retirar a obrigatoriedade de troca de senha do administrador, customizar propriedades do Menu Iniciar, instalar o BgInfo para identificar facilmente as VM's.
  3. Executar o sysprep para colocar o Windows em um estado "acabei de ser instalado". O sysprep é uma ferramenta da Microsoft que prepara uma imagem de disco com uma instalação Windows para clonagem.
  4. Marcar o VHD como read-only.
  5. Criar novas VM's usando discos diferenciais baseados neste VHD "sysprep'd".
Para executar o passo 3 acima, após sua VM base estar completamente customizada, abra um command prompt, mude para C:\Windows\System32\Sysprep, e execute sysprep.exe. Selecione a opção "Enter System Out-of-Box Experience (OOB)", marque a opção "Generalize" (que faz com que o sysprep reinicialize tudo que tem que ser reinicializado - como os SID's), e selecione a opção "shutdown" para que a VM seja desligada após a execução do sysprep. Agora você tem um disco virtual com um Windows instalado e pronto para receber novos nome, SID, e as outras "coisas".


Quando você criar novas VM's que usam discos diferenciais baseados neste VHD, no primeiro boot será apresentado um "mini-setup" do Windows aonde ele pedirá somente algumas informações, gerará um nome para a nova máquina, e pronto - em menos de 2 minutos você tem uma nova VM limpinha pra trabalhar.

Observações

  1. O nome da nova máquina é auto-gerado, então geralmente é a primeira coisa que eu mudo.
  2. A máquina entra com IP auto-configurado ("APIPA" - 169.254.blablabla). Caso você esteja montando uma rede de VM's, tem que configurar o IP para ela entrar na rede.
  3. Dá dor de cabeça montar uma VM para fazer o sysprep se esta VM tem o SQL Server já instalado. Não é suportado até o SQL Server 2008. O que tenho feito é subir uma VM sysprep'd "limpa" - só o Windows -, colocar ela no domínio e instalar o SQL Server. Perco menos tempo assim do que ficar tentando solucionar problemas devido a fazer o syspred de uma VM já com SQL Server instalado. No SQL Server 2008 *R2* diz a MS que é suportado, mas nunca usei pra ver se dá muito trabalho ou não.
  4. Comecei a receber o erro "An attempt to resolve the DNS name of a domain controller in the domain being joined has failed" quando ia acrescentar uma nova VM no domínio - apesar de ter configurado o DC do domínio como o servidor DNS primário da nova VM, e conseguir pingar de uma máquina pra outra. Resumindo (muito), desabilitei o IPv6 em todas as máquinas e tudo voltou ao normal.
[]s,

GB

terça-feira, 8 de maio de 2012

Meu site SharePoint está com segurança integrada mas ainda aparece a janela de logon

Coloca ele como “Local Intranet” no IE que pára de aparecer.

(Forte concorrente a menor post do mundo se não fosse essa última frase)

quinta-feira, 3 de maio de 2012

SharePoint: Erro de deploy | Momento revolta :-P

Estava fazendo o deploy de uma solução no SharePoint e apareceu o erro "Error occurred in deployment step 'Activate Features': Operation is not valid due to the current state of the object.". Aí eu pensei "Hein?" e fui pro Google. E a primeira solução que encontrei foi:
1. Feche o VS2010
2. de um IISRESET no server
3. Abra o Task Manager e de um KILL em todos os processos w3wp.exe 
"Tu tá de sacanagem" eu pensei. Mas fiz isso e, sem mudar nada no código, o deploy passou a funcionar. Desenvolvimento no SharePoint é isso: faz sua estimativa, multiplica por 3 por causa dessas "peculiaridades", e reza pra ter alguém que já passou pela mesma coisa e postou na Internet. Se não chegasse tanta demanda nesse troço...