Arquivo

Archive for the ‘Virtual PASS BR’ Category

Pensar set-based, porque é tão difícil?

20 de setembro de 2012 11 comentários

Cá estou, no avião voltando pra casa depois de mais um trabalho de Tuning no SQL Server, processo que demorava 10 horas agora demora 24 minutos, cliente feliz e eu voltando pra casa pra curtir filho lindo e esposa maravilhosa. #SrNimbusRocks

 

Estou lendo um livro do Joe Celko chamado “Thinking in Sets” (pensando em sets) e eu gostaria de falar um pouco sobre isso.

Uma das dificuldades mais comuns para desenvolvedores que querem escrever códigos SQL que tenham boa performance, é abandonar o pensamento de linguagens procedurais (onde você diz tudo o que quer fazer) e começar a pensar em códigos declarativos (onde você apenas diz o que quer).

Desde pequenos (no meu caso com 16 anos) aprendemos que em programação devemos fazer tudo passo a passo. Quem lembra de quando seu professor de lógica de programação (se é que existe lógica nessa bagaça) disse: – “Boa noite turma, hoje vamos escrever nosso primeiro código!” e ele continuou dizendo: “- Quero que você pegue uma folha e escreva todos os passos necessários para trocar um pneu, ou fritar um ovo (no meu caso foi a do fritar o ovo)”. Eu tive essa aulinha no curso técnico de processamento de dados, lembro que escrevi algo parecido com:

 

1.       Pegar o ovo

2.       Abrir o ovo

3.       Colocar na frigideira

4.       Fritar o ovo

5.       Pegar o ovo da frigideira

6.       Colocar no prato

 

Escrevemos estes passos e achamos que estamos abafando… Dai o professor pega o papel e diz… ué, mas vai pegar o ovo da onde? Tá na geladeira? O ovo tá onde? Dai você diz… aaa … é verdade, vou corrigir me da ai… dai você adiciona:

 

1.       Pegar o ovo da geladeira

2.      

 

Dai o professor pega a folha e diz… ué, na sua casa você deixa a geladeira aberta?… Dai você diz… caramba (já começando a ficar irritado) é verdade.

 

1.       Abrir a geladeira

2.       Pegar o ovo da geladeira

3.      

 

Dai o professor pega a folha e diz… ué, e se não tiver mais ovo na geladeira? E se a geladeira estiver quebrada e o ovo estragado, e se a geladeira tiver um cadeado? E se você estiver com a mão ocupada com alguma coisa? Como vai pegar o ovo? … Dai você diz… Professor, na boa, vai a merda! Smile with tongue out E aprende a lição! Tem que prever TUDO, e dizer passo a passo o que deve ser feito.

 

É assim que aprendemos a programar, linha a linha, passo a passo! Porém, quando estamos falando de banco de dados, precisamos pensar que vamos trabalhar com um set, ou seja, um conjunto de linhas (melhor dizendo, um conjunto de elementos). É de responsabilidade do banco de dados decidir qual será a melhor maneira para ler os dados. Ainda que eles serão lidos linha a linha, quem decide a melhor maneira de fazer isso é o banco, não você. Por exemplo, o SQL Server pode optar por fazer uma soma, utilizando um algoritmo de hash, ou um join utilizando um merge e etc…

Lembre-se de que cada instrução (comando SQL) enviada para o banco de dados passa por uma série de validações (parse, binding, optimize…) e isso tem um custo. Por isso cursores e loops são tão ruims em relação a performance. Uma frase sensacional do Celko sobre cursores é a seguinte:

 

“A melhor técnica que você pode utilizar para melhorar a performance de um cursor é não usar cursor.”

Sarcastic smile Sensacional.

 

Quando você vê uma tabela chamada População você acha que nela existem populações, mas na verdade existem pessoas. Uma tabela de Floresta não contem florestas contem árvores… Temos que pensar em um todo, não no individuo. Ao invés de criar uma tabela chamada Abelhas para armazenar dados sobre abelhas, crie uma tabela chamada Colmeia! (é sério esse foi o único coletivo que eu conhecia quando estava escrevendo este artigo kkk Smile with tongue out)

 

Quer exemplos de como nós programadores pensamos em código procedural e não em sets? Vejamos…

 

Se eu disser, nesta mesa eu tenho 8 caixas de ovos (de galinha e codorna) fechadas e de tamanhos variados, quero que você olhe cada caixa e me entregue todas as caixas que contêm a mesma quantidade de ovos.

O que pensamos em fazer?

 

·    Contar quantos ovos tem em cada caixa, separar as caixas que tem a mesma quantidade e me entregar.

 

O que seria mais eficiente?

 

·    Olhar a quantidade exibida na embalagem, separar as caixas que tem a mesma quantidade e me entregar.

 

Reparou na diferença? … Uma solução conta todos os ovos, e a outra usa uma agregação… Uma solução vê os ovos e outra vê as caixas (um todo).

 

Vejamos outro exemplo que usa o caso clássico das tabelas de Pedidos e Itens de Pedidos.

 

Se eu disser, quero uma consulta que retorne todos os pedidos em que a quantidade de itens comprados é igual para todos os itens.

 

Uma alternativa seria escrever uma consulta que varre a tabela de “Itens de Pedidos” e executa uma subquery para verificar se existe algum item de pedido diferente da quantidade do item lido… Algo mais ou menos assim:

 

select a.CodigoPedido from itens_pedidos a

where not exists (select * from itens_pedidos b

                             where a.CodigoPedido = b.CodigoPedido

                                 and a.quantidade <> b.quantidade)

 

Ou seja, se existir algum pedido com quantidade diferente, ele será desconsiderado da consulta. Eu poderia escrever um cursor para responder a consulta, mas não vou fazer isso Smile.

 

Outra forma mais interessante seria o seguinte:

 

select CodigoPedido from itens_pedidos

group by CodigoPedido

having min(quantidade) = max(quantidade)

 

Repararam na diferença?… Eu sei que é mais difícil pensar assim, encontrar soluções deste tipo não é fácil, mas acreditem, é possível.

 

Estudem e entendam as “window functions” no SQL Server, entendam como usar funções de agregação e evitem cursores, sempre.

 

Outra coisa interessante que sempre falo, é em relação a formatação de dados via SQL e ordenação.

 

Não faça formatação no SQL, please, deixe para formatar alguma coisa, na aplicação, banco de dados sofre para incluir uma “mascara” no CNPJ, faça isso na aplicação e não no banco.

Para ordenação eu digo o mesmo. Ordenação é na aplicação e não no banco! Você sabia que a clausula ORDER BY não faz parte do SELECT ? Pois é, no padrão ANSI/ISO da linguagem SQL (structured query language) não existe order by no select, no ISO o order by faz parte da sintaxe de cursores. Os fabricantes de bancos de dados criam uma extensão dessa opção para que você a utilize nos selects. Fica a dica, quase sempre podemos ordenar do lado da aplicação com pouquíssimo trabalho.

 

Fecho o artigo com um ditado Turco bem interessante:

“Não importa o quão longe você foi por uma estrada errada, volte!”

 

Abraços e bom código!

Desafio SQL Server – Performance

17 de setembro de 2012 29 comentários

UPDATE 18-09-2012: Este desafio é baseado em um cenário real que trabalhei, porém eu não me atendei a um fato importantíssimo… No cenário original o group by era efetuado em 23 colunas. Isso significa que se você tentasse criar um índice com base em todas as colunas do group by, o SQL batia o limite de 16 colunas como chave de um índice. O que quero dizer com isso é que não vale criar uma view indexada para ajudar nesta solução Smile

Quando criei este cenário de exemplo eu não me atentei a este ponto extremamente importante.

De qualquer forma, meus parabéns para o Evandro Camara que me mandou a solução que roda em menos de 1 segundo utilizando a view indexada. Vou considerar a resposta dele como válida, e estou mudando o desafio para… Não vale usar a view indexada… (ou seja, igual ao cenário original que trabalhei)… Eu poderia mudar o cenário para usar mais de 16 colunas no group by… daria na mesma…

Na solução do desafio falo melhor em relação ao beneficio da view indexada neste caso.

————————————————————————————————

Galera, depois do desafio de segurança da semana passada, vamos para um desafio sobre performance!

 

Este vai ser um pouco mais simples e vai ser no mesmo esquema do último desafio, ou seja, ganhador vai levar uma cópia do meu livro impressa.

 

A ideia do desafio é simples, temos uma consulta, e precisamos efetuar as modificações necessárias para melhorar a performance, você pode usar qualquer recurso disponível no SQL Server 2008 R2, a ideia é ter performance na LEITURA, ou seja, se precisar criar 50 índices (por favor, não crie!) pode criar, reescrever a consulta também vale, contanto é claro que os resultados sejam os mesmos.

É obrigatório usar MAXDOP 1 para evitar paralelismo.

 

Vejamos o cenário:

 

USE tempdb

GO

— Prepara ambiente

— Aproximadamente 2 minutos para rodar

— Tab com 287368 KB

IF OBJECT_ID(‘OrdersBig’) IS NOT NULL

  DROP TABLE OrdersBig

GO

CREATE TABLE OrdersBig (OrderID int NOT NULL IDENTITY(1, 1),

                        CustomerID int NULL,

                        OrderDate date NULL,

                        Value numeric (18, 2) NOT NULL)

GO

ALTER TABLE OrdersBig ADD CONSTRAINT xpk_OrdersBig PRIMARY KEY CLUSTERED  (OrderID)

GO

— Tab com 5 milhões de linhas

INSERT INTO OrdersBig(CustomerID, OrderDate, Value)

SELECT TOP 5000000

       ABS(CONVERT(Int, (CheckSUM(NEWID()) / 10000000))),

       CONVERT(Date, GetDate() ABS(CONVERT(Int, (CheckSUM(NEWID()) / 10000000)))),

       ABS(CONVERT(Numeric(18,2), (CheckSUM(NEWID()) / 1000000.5)))

  FROM sysobjects a, sysobjects b, sysobjects c, sysobjects d

GO

ALTER TABLE OrdersBig ADD CountCol VarChar(20)

GO

UPDATE TOP (50) PERCENT OrdersBig SET CountCol = ‘Count’

WHERE CountCol IS NULL

GO

UPDATE TOP (50) PERCENT OrdersBig SET CountCol = ‘CountDistinct’

WHERE CountCol IS NULL

GO

UPDATE OrdersBig SET CountCol = ‘CountDistinct_1’

WHERE CountCol IS NULL

GO

CHECKPOINT

GO

 

O comando acima criamos uma tabela chamada OrdersBig com 5 milhões de linhas, a consulta é a seguinte:

 

— Problema

/*

  Consulta demora 1 minutos e 41 segundos para rodar e o uso dos recursos são:

  CPU: 96503

  Reads: 75902646

  Writes: 100350

  Duration (ms): 101522

*/

CHECKPOINT

DBCC DROPCLEANBUFFERS

DBCC FREEPROCCACHE

GO

SET STATISTICS IO ON

SELECT a.CustomerID,

       a.CountCol,

       CASE a.CountCol

         WHEN ‘Count’ THEN COUNT(1)

         WHEN ‘CountDistinct’ THEN COUNT(DISTINCT a.OrderDate)

         WHEN ‘CountDistinct_1’ THEN COUNT(DISTINCT 1)

         ELSE NULL

       END AS Cnt,

       CASE (SELECT AVG(b.Value)

               FROM OrdersBig b

              WHERE b.CustomerID = a.CustomerID)

            WHEN 1000 THEN ‘Média = 1 mil’

            WHEN 2000 THEN ‘Média = 2 mil’

            WHEN 3000 THEN ‘Média = 3 mil’

            WHEN 4000 THEN ‘Média = 4 mil’

            WHEN 5000 THEN ‘Média = 5 mil’

            ELSE ‘Não é número exato’

       END AS Sts

  FROM OrdersBig AS a

 GROUP BY a.CustomerID, a.CountCol

 ORDER BY a.CustomerID

OPTION (MAXDOP 1)

SET STATISTICS IO OFF

GO

 

Antes de rodar a consulta estou rodando um FREEPROCCACHE, DROPCLEANBUFFERS e CHECKPOINT para forçar a compilação do plano e gerar leitura física.

 

A consulta cima roda em aproximadamente 1 minuto e 41 segundos e utiliza os recursos descritos acima (nos comentários).

 

O desafio consiste em melhorar a consulta para que ela rode no menor tempo possível e utilize menos recursos. A medida mais importante é, TEMPO, ou seja, quanto menos tempo para rodar a consulta melhor.

Eu consegui baixar o tempo da consulta acima para 7 segundos utilizando os seguintes recursos:

 

/*

  Consulta demora 7 segundos para rodar e o uso dos recursos são:

  CPU: 6552

  Reads: 22523

  Writes: 2

  Duration (ms): 7249

*/

 

Se você conseguir chegar próximo aos números acima me manda sua solução que vou testar e medir os resultados, os seus números com certeza serão um pouco diferente do meu já que crio os dados dinamicamente, os valores exibidos pra você podem ser diferentes dos exibidos pra mim.

Vou dar uma dica… que dica o que rapaz… se vira! kkk

Boa sorte e divirta-se.

Solução desafio de segurança no SQL Server

14 de setembro de 2012 16 comentários

Primeiramente quero agradecer a todos que se interessaram pelo desafio e perderam algum tempo tentando chegar na solução Smile. Eu gostei tanto que já tenho outro desafio preparado para publicação, mas vamos por partes…

 

Se você não sabe do que estou falando veja o desafio aqui.

 

A única pessoa que conseguiu responder o desafio mandou uma solução MUITO MELHOR que a minha Smile hahuahuuh… não era por menos, quem respondeu o desafio foi o grande (modo de dizer) Fabricio Catae (blog|twitter) o ninja!

Parabéns Catae você é o cara! No próximo desafio vou colocar um aviso do tipo “Aviso: Ninjas estão automaticamente desclassificados! huahuahua

 

Vamos analisar primeiro a minha solução, e depois veremos a do Catae.

 

Solução 1:

Minha solução consiste em trabalhar com as mensagens de erro do SQL Server para ver as informações que supostamente não deveríamos saber que existe. Isso é muito utilizado em SQL Injection, ou seja, se você não trata as mensagens de erro que envia para sua aplicação, tá muito errado, cuidado.

Primeiramente vejamos o plano de execução do select na view: (pra quem tá chorando nessa hora dizendo, aaaa mas não vale ver o plano e tals… não Sr. eu disse que vale tudo, ou seja, você conhece as tabelas, pode ver o plano logado com um usuário que te permissão para isso, mas para “ver” os dados que não poderíamos ver você precisa estar logado com o usuarioquenaopodenada)

 

clip_image001[4]

 

Vamos executar este plano passo a passo:

  1. Executa o Clustered Index Scan na tabela TabFuncionarios, ou seja, vai fazer um SCAN na tabela, pra quem ainda não entendeu, isso significa que o SQL vai passar por TODAS as linhas da tabela. É importante que você entenda isso.
  2. Nested Loops trabalhando como Anti Semi Join (left anti semi = not exists, right anti semi = exists). Um Anti Semi Join é um Join que retorna dados de apenas um dos lados do join, ou seja, neste caso apenas colunas da tabela TabFuncionarios serão retornadas.
  3. Para cada linha recebida pelo Scan na TabFuncionarios, o Nested Loops chama o Clustered Index Scan na tabela TabEsconderFuncionarios.
  4. Clustered Index Scan na tabela TabEsconderFuncionarios retorna as linhas da tabela TabEsconderFuncionarios para o Nested Loops para ver quem faz o join, e consequentemente eliminar as linhas que não queremos exibir (validação do not exisits).

Bom, agora que sabemos que a PRIMEIRA COISA que o plano faz é varrer a tabela TabFuncionarios, podemos tentar aplicar um filtro nesta tabela para tentar gerar um erro de conversão… Por exemplo:

O que acontece se eu rodar o seguinte comando?

SELECT *

  FROM vw_TabFuncionarios

WHERE CONVERT(Int, Nome) = 0

Msg 245, Level 16, State 1, Line 1

Conversion failed when converting the varchar value ‘Fabiano’ to data type int.

 

Plano da consulta:

clip_image002[4]

O que acontece é que o SQL tenta converter a coluna Nome para Int e gera um erro de conversão. Ótimo, estamos chegando perto…

Repare que no plano de execução da consulta acima o SQL Server executa (Predicate) o CONVERT(Int, Nome) = 0 antes de fazer o join, ou seja, o SQL vai tentar fazer a conversão para TODAS as linhas da tabela, no caso acima na primeira linha lida o erro já é exibido, porém podemos incluir mais um filtro no valor que já conhecemos para ignorar o “Fabiano” por exemplo:

SELECT *

  FROM vw_TabFuncionarios

WHERE CONVERT(Int, Nome) = 0

   AND Nome NOT IN (‘Fabiano’)

 

Msg 245, Level 16, State 1, Line 1

Conversion failed when converting the varchar value ‘Gilberto’ to data type int.

 

Plano da consulta:

clip_image003[4]

 

Repare que agora a conversão deu erro no Gilberto, vamos inclui-lo na lista do IN:

SELECT *

  FROM vw_TabFuncionarios

WHERE CONVERT(Int, Nome) = 0

   AND Nome NOT IN (‘Fabiano’, ‘Gilberto’)

 

Msg 245, Level 16, State 1, Line 1

Conversion failed when converting the varchar value ‘Luciano’ to data type int.

 

Já conseguimos visualizar que existe um funcionário chamado Luciano Smile with tongue out, e pra saber qual é o salario dele? … Basta concatenar a coluna salario com o Nome e tentar converter tudo para Int…

SELECT *

  FROM vw_TabFuncionarios

WHERE CONVERT(Int, Nome + CONVERT(VarChar(30), Salario)) = 0

   AND Nome NOT IN (‘Fabiano’, ‘Gilberto’)

 

Msg 245, Level 16, State 1, Line 1

Conversion failed when converting the varchar value ‘Luciano25999.00’ to data type int.

 

Feito, conseguimos ver que o Luciano ganha R$25.999,00 por mês, tai o motivo dele estar na tabela EsconderFuncionarios J. Para ver quanto o Ivan ganha, basta incluir o Luciano na lista do IN…

Tem uma coisa importante que talvez você não tenha notado… Repare que para que isso tudo funcione o SQL Server primeiro tem que fazer a validação do NOT IN, e depois fazer a validação da conversão do nome para int = 0. Ou seja,

No WHERE, ele tem que executar primeiro isso “NomeNOTIN(‘Fabiano’,‘Gilberto’)” e depois “CONVERT(Int,Nome)= 0”, veja que ainda que nós tenhamos escrito o código especificando primeiro o CONVERT e depois o NOT IN, o SQL Server executou o NOT IN antes, vejamos isso no plano:

clip_image004[4]

O otimizador de consultas que decide quem ele deve executar primeiro, leia mais sobre isso neste post do Itzik. Ok, mas dai vem a pergunta, como forçar a ordem de execução das expressões e ter certeza de que nossa solução vai funcionar independente da versão do SQL Server que estou usando?  (No SQL Server 2000 a consulta acima não funciona).

Para forçar a ordem de execução das expressões e evitar (ou forçar) um erro devemos usar o CASE, portanto a consulta final ficaria assim:

— Rodar a consulta no escopo do UsuarioQueNaoPodeNada

EXEC AS USER = ‘UsuarioQueNaoPodeNada’

GO

— Quanto o Luciano ganha?

SELECT *

  FROM vw_TabFuncionarios

WHERE (CASE

          WHEN (Nome NOT IN (‘Fabiano’, ‘Gilberto’)) AND (CONVERT(Int, Nome + CONVERT(VarChar, Salario) ) = 0) THEN 0

          ELSE 1

        END) = 0

 

Solução 2 (by Fabrício Catae a.k.a. Ninja):

A solução do Catae é sensacional porque ela não retorna os dados utilizando as mensagens de erro do SQL. A ideia é simples (depois eu você vê é claro), ela consiste em gerar um lock exclusivo nas linhas da tabela TabEsconderFuncionarios e depois ler a view utilizando o hint ReadPast.

Pra quem não conhecia, o hint READPAST é similar ao famoso NOLOCK, a ideia é não ficar bloqueado por uma sessão que está com lock exclusivo nas linhas que você esta tentando ler. Porém diferentemente do NOLOCK (quer permite leitura suja para não ficar bloqueado) o READPAST “pula” as linhas que estão com lock por linha (KEY LOCK). Voltando ao nosso cenário, se as linhas da tabela TabEsconderFuncionarios estiverem bloqueadas com xLock (lock exclusivo) e RowLock (lock por linha) ao ler os dados da view, o SQL Server “entende” que não existe nada na tabela e consequentemente retorna tudo.

Vejamos na prática:

 

— Sessão 1

BEGIN TRAN

— Gerar lock exclusivo em tudo o que lê, ou seja,

— nas linhas Fabiano e Gilberto da TabFuncionarios

— e nas linhas ID 3 e 4 da TabEsconderFuncionarios

SELECT * FROM vw_TabFuncionarios WITH (xlock, rowlock)

WHERE Nome IN (‘Fabiano’, ‘Gilberto’)

GO

Em uma sessão eu inicio uma transação, e leio a view utilizando dois hints, o xlock para forçar a o lock exclusivo, e o rowlock para forçar o lock por linha (lembre-se o READPAST só “ignora” locks de linha).

Podemos consultar os locks gerado pela consulta na view utilizando o seguinte código:

— Sessão 1

— Consultar locks

— Gerou os locks esperados

SELECT OBJECT_NAME(partitions.OBJECT_ID) AS ObjectName,

       dm_tran_locks.resource_type,

       dm_tran_locks.resource_description,

       dm_tran_locks.request_mode

  FROM sys.dm_tran_locks

  LEFT OUTER JOIN sys.partitions

    ON partitions.hobt_id = dm_tran_locks.resource_associated_entity_id

WHERE request_session_id = @@spid

   AND resource_type = ‘KEY’

ORDER BY 1

 

clip_image005[4]

Conforme podemos observar o select na view gerou 2 locks na tabela TabFuncionarios (para os IDs 1 e 2, repare no where por nome) e 2 locks na tabela TabEsconderFuncionarios (nos IDs 3 e 4)

Se eu abrir outra sessão e tentar rodar um select na vw_TabFuncionarios eu vou ficar bloqueado pro causa dos locks exclusivos gerados na sessão 1, porém se eu rodar o código com o hint READPAST, o SQL vai ler as linhas de IDs 3 e 4 da tabela TabFuncionarios e ao tentar fazer o join com a TabEsconderFuncionarios ele não vai encontrar nada lá (porque ele “pula” as linhas bloqueadas), e consequentemente retorna os dados… Vejamos:

— Sessão 2

— Abrir outra sessão e ler os dados da view com READPAST hint

— Como os IDs da TabEsconderFuncionarios estão com lock na linha (importante que lock de linha)

— ao fazer o join para ver quem NOT EXISTS o SQL não ve nada, ou seja, posso ver todas as linhas

SELECT * FROM vw_TabFuncionarios WITH(readpast)

 

clip_image006[4]

Conforme podemos observar, conseguimos visualizar todos os dados da tabela.

É importante que o SQL Server gere os locks POR LINHA, se na sessão 1 o SQL Server gerar os locks por página ou tabela, esquece que não vai funcionar…

Se você esta se perguntando, como evitar esse comportamento… a resposta é… você poderia criar view com with(nolock) nas tabelas e dai ao tentar ler a view utilizando o READPAST o SQL Server ira gerar a seguinte mensagem de erro:

 

Msg 650, Level 16, State 1, Line 5

You can only specify the READPAST lock in the READ COMMITTED or REPEATABLE READ isolation levels.

 

Bom, é isso, espero que tenham gostado, quer mais? … J

Fique de olho que na semana que vem vou publicar outro desafio, desta vez mais simples, porém relacionado a performance.

Abs.

Categorias:Desafio, Virtual PASS BR

Aviso: Desafio de segurança no SQL, amanhã (14-09) posto o resultado

13 de setembro de 2012 Deixe um comentário

 

Galera, aviso pra quem está participando do desafio que bloguei na semana passada:

https://blogfabiano.com/2012/09/04/desafio-de-segurana-no-mssql-voc-consegue/

Na sexta-feira (15-09) no horário do almoço, vou publicar a resposta ok? Smile Se você ainda não tentou, corre que da tempo.

Abs.

Categorias:Virtual PASS BR

Desafio de segurança no MSSQL … Você consegue?

4 de setembro de 2012 22 comentários

UPDATE 14-09-2012 a resposta para este desafio foi publicada aqui:  https://blogfabiano.com/2012/09/14/soluo-desafio-de-segurana-no-sql-server/

Pessoal bora participar de um desafio de segurança no SQL Server e concorrer a uma cópia impressa do meu livro? Deixa eu explicar melhor.

 

Um dos muitos benefícios em relação ao uso de views no SQL Server é que com elas conseguimos “esconder” as tabelas do usuário final, podemos por exemplo definir que determinado usuário só tem acesso a view e não as tabelas que a view acessa. Ou seja, ele consegue executar um “select * from vw_funcionarios“ (vw_funcionarios é a view) mas não consegue executar um “select * from tab_funcionarios“ (tab_funcionarios é a tabela).

 

Utilizando a view, também conseguimos definir quais colunas serão visíveis para o usuário garantindo que ele somente veja os dados que ele realmente pode ver.

 

O desafio que estou propondo é o seguinte:

Vou descrever um cenário que usa views para “esconder” dados sigilosos, aparentemente o cenário é seguro e quero que você “hackeie” o SQL Server para conseguir visualizar os dados que supostamente você não deveria ver.

 

Vamos lá, o ambiente é o seguinte, tenho duas tabelas uma chamada TabFuncionarios que contêm os dados de todos os funcionários de uma empresa, e tenho outra tabela chamada TabEsconderFuncionarios que contêm os IDs dos funcionários que não quero que sejam retornados pela view. Desta forma dinamicamente consigo criar uma tela para configurar quais usuários são visíveis pela view.

 

Vejamos o schema do banco:

 

USE tempdb
GO
IF OBJECT_ID('vw_TabFuncionarios') IS NOT NULL
  DROP VIEW vw_TabFuncionarios
GO
IF OBJECT_ID('TabEsconderFuncionarios') IS NOT NULL
  DROP TABLE TabEsconderFuncionarios
GO
IF OBJECT_ID('TabFuncionarios') IS NOT NULL
  DROP TABLE TabFuncionarios
GO
CREATE TABLE TabFuncionarios(ID      Int PRIMARY KEY,
                             Nome    VarChar(200),
                             Salario Numeric(18,2));
GO
CREATE TABLE TabEsconderFuncionarios(ID_Funcionario Int PRIMARY KEY REFERENCES TabFuncionarios(ID))
GO

Ou seja, tenho a TabFuncionarios que contem os dados dos funcionários, e a TabEsconderFuncionarios que contêm o ID correspondente a tabela TabFuncionarios.

 

Vamos inserir alguns dados de exemplo:

 

-- Inserir 4 funcionarios
INSERT INTO TabFuncionarios(ID, Nome, Salario)
VALUES(1, 'Fabiano', 1900),(2, 'Gilberto',2050),(3, 'Luciano', 25999),(4, 'Ivan', 9500)
GO
-- Esconder os funcionário "Luciano" e "Ivan" que ganham MUITO bem! 😉
INSERT INTO TabEsconderFuncionarios (ID_Funcionario) VALUES (3), (4)
GO

Ou seja, temos 4 funcionários, e os funcionários que quero esconder estão na tabela TabEsconderFuncionarios. Agora precisamos apenas criar uma view que somente retorne os usuários que não estão na tabela TabEsconderFuncionarios:

 

IF OBJECT_ID('vw_TabFuncionarios') IS NOT NULL
  DROP VIEW vw_TabFuncionarios
GO
-- Criar a view para exibir somente funcionários que ganham pouco
CREATE VIEW vw_TabFuncionarios
WITH SCHEMABINDING
AS
SELECT ID,
       Nome,
       Salario
  FROM dbo.TabFuncionarios
 WHERE NOT EXISTS(SELECT 1 
                    FROM dbo.TabEsconderFuncionarios
                   WHERE TabEsconderFuncionarios.ID_Funcionario = TabFuncionarios.ID)
GO

-- Consultar os dados da view
SELECT * 
  FROM vw_TabFuncionarios
GO

Temos o seguinte:

clip_image001[4]

 

Ou seja, funciona! Agora seria apenas o caso de criar um usuário que só tem permissão de select na view e pronto.

 

CREATE LOGIN UsuarioQueNaoPodeNada WITH PASSWORD = '102030', CHECK_POLICY = OFF
GO
CREATE USER UsuarioQueNaoPodeNada FOR LOGIN UsuarioQueNaoPodeNada;
GO
GRANT SELECT ON vw_TabFuncionarios TO UsuarioQueNaoPodeNada
GO

Portanto o desafio é, logando com o usuário “UsuarioQueNaoPodeNada” como descobrir os funcionários escondidos, e depois disso, quanto eles ganham?

As regras são: Não tem regras! Vale tudo! (contanto que você esteja logado com o UsuarioQueNaoPodeNada)

 

O ganhador será divulgado aqui no meu Blog e vou enviar a cópia do meu livro pelo correio (eu pago o frete é claro!), se você conseguir, me manda um e-mail no fabiano_amorim arroba(que não é do boi) bol ponto com ponto br.

 

O primeiro que me mandar o e-mail será o ganhador. Depois é claro, vou postar a solução, e explicar tudo com detalhes…

 

Boa sorte e have fun!

 

Abs.

Categorias:Virtual PASS BR

SQLSat147 em Recife-PE, Video e Arquivos…

29 de agosto de 2012 3 comentários
image

Pessoal, no sábado passado dia 25/08 aconteceu o SQL Saturday 147 em Recife-PE.

O evento foi muito bom, mesmo com a baixa no número de inscritos versus o número de participantes… No Brasil, infelizmente temos um problema grave de se inscrever em eventos e depois não se preocupar se realmente vamos participar ou não…

No caso do SQL Sat, muito gente se inscreve só porque é de graça, e somente depois de fazer a inscrição vai pensar se realmente vai participar ou não… Sou a favor de cobrar 15 reais para participar do evento, e reverter o dinheiro para alguma obra de caridade… Desta forma tentamos minimizar os malas que se inscrevem só por se inscrever…

Abaixo você pode fazer o download dos arquivos que utilizei na minha sessão:

https://skydrive.live.com/redir?resid=52EFF7477E74CAA6!2052

Caso você tenha perdido, pode assistir minha sessão online… Novamente o @leomatos_sql fez o GRANDE favor de gravar minha apresentação, e eu já subi pro youtoba… segue o vídeo abaixo:

SQLSat147–Fabiano Amorim

 

http://www.youtube.com/watch?v=edAUgQ7XBe0

Errata:

Durante minha apresentação, ao falar de Non-SARGs eu dei a entender que você pode usar a function SubString em uma coluna na clausula WHERE que o SQL Server mesmo assim conseguiria fazer um seek em um possível índice… Não é bem assim ok ? … Foldable expressions que eu mencionei, são expressões que o SQL Server executa em tempo de compilação para conseguir fazer uma estimativa mais precisa da cardinalidade (quantidade de linhas que serão retornadas) do filtro.

Em alguns casos o SQL Server consegue sim fazer algumas “mágicas” para conseguir usar um possível índice mesmo que você manipule a coluna utilizada para filtro… que é o caso de uso de CONVERT ou ISNULL (nem sempre QO consegue fazer o seek)… Continuo dizendo o seguinte… se mexer na coluna do WHERE, OLHE NO PLANO para ter certeza de que o SQL Server conseguiu fazer uso do índice…

Se ficar alguma dúvida, me avisa que explico isso melhor…

Abs.

Arquivos – WebCast DBA CheckList

28 de junho de 2012 2 comentários

Galera, conforme combinado segue o link para download dos arquivos que utilizei na WebCast da Sr.Nimbus onde falamos sobre tarefas que todo DBA deve analisar em um ambiente SQL Server.

https://skydrive.live.com/redir?resid=52EFF7477E74CAA6!2051

Para baixar o PPT de uma olhada aqui no post do Luti: http://luticm.blogspot.com.br/2012/06/dba-checklist-gravacao-e-material-do.html

Para quem perdeu a sessão ou não sabe do que eu estou falando, segue abaixo o video da sessão:

 

WebCast Sr.Nimbus–DBA CheckList

 

http://youtu.be/dfUGDfS1Qv0

Abs.

SQLSat 147 Recife dia 25/08 e TDC dia 06/07

27 de junho de 2012 3 comentários
image
image

Galera, duas novidades rápidas, na próxima sexta-feira dia 06/07/2012 irei ministrar uma palestra no TDC (The Developers Conference), e no dia 25 de agosto irei ministrar a mesma sessão no SQL Saturday (#sqlsat147) que será em Recife.

O tema da sessão é a seguinte:

Ei dev, aprenda 7 maneiras diferentes para matar um DBA do coração

Descrição:

Nesta sessão o MVP em SQL Server Fabiano Amorim (@mcflyamorim) irá demonstrar 7 técnicas de desenvolvimento que irão fazer um DBA morrer do coração. Piores práticas para escrever uma consulta T-SQL (problemas com triggers, procedures, views e functions), piores práticas para desenvolver uma tela de consulta, desenvolvendo para não ter segurança. Performance? Não é preciso! Aprenda o que fazer e o que não fazer quando desenvolver para SQL Server.

Espero nos ver por lá Smile.

Abs.

Categorias:Evento, Virtual PASS BR

WebCast Sr.Nimbus–DBA CheckList dia 21/06/2012

1 de junho de 2012 11 comentários

Ok, já sei, demorei, desculpa. Graças a Deus as coisas na Sr.Nimbus estão indo MUITO bem, o que nos fez atrasar um pouco em relação a nossa prometida, esperada e votada webcast.

Para quem não sabe do que estou falando, segue o link do post que falei sobre a WebCast da Sr.Nimbus.

O resultado da enquete foi bem interessante, e me mostrou que sim, tem gente que realmente lê o que eu escrevo neste blog Smile. Segue abaixo o resultado:

clip_image001

Como vocês podem ver, e para minha surpresa a WebCast sobre DBA CheckList foi a grande campeã com 39% dos votos, seguido do tema relacionado a T-SQL Avançado com 24%. Digo que fiquei surpreso porque sempre achei que o público do meu blog era maior por parte dos desenvolvedores e não de DBAs…

Bom, vamos a boa notícia. A boa notícia é que a WebCast já tem data, e será na quinta-feira dia 21/06, segue um resumo abaixo:

    • Tema: DBA CheckList o que analisar?
    • Apresentadores: Fabiano Amorim e Luciano Caixeta Moreira (blog|twitter) (a.k.a. Luti)
    • Moderação: Um quando o outro estiver apresentando, e outro quando o um estiver apresentando. (e vice e versa)
    • Data: Quinta-feira dia 21/06/2012
    • Horário: Das 19:30 as 21:30
    • Onde: Online
    • Link para inscrição: http://tinyurl.com/WebCastSrNimbus

 

Caso qualquer problema ocorra antes/durante a apresentação, iremos atualizar este blogpost como mais detalhes.

Hypothetical indexes, making it easy to use…

Hi there, yes, I know, it’s being a while since I wrote something here… So, sorry about that.

Yesterday the guy I’m a big fan (if you already join one of my SQL trainings you know that Smile) Query Optimizer Guru Paul White (blog|twitter) answered a question on twitter (#sqlhelp hash tag) and he mentioned a post I wrote some time ago about hypothetical indexes on SQL Server. That is a very nice feature but unfortunately is not pretty to use because it requires you to use DBCC AUTOPILOT and set AUTOPILOT to ON…

The parameters you have to use are not straightforward to find and may discourage you to use it… I’ve created a procedure to make it a little easier to use.

Originally I created this procedure after a student question about how to make it easier to use hypothetical indexes on SQL Server. So I thought you may like it.

Unfortunately it relies on a CLR stored procedure to SET the AUTOPILOT, but if you don’t mind to use it in a develop environment (which is something normal to do) then you can use it, following is the CLR code, and if you are interested you can download the project code here:

-- CLR Proc
/*
using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;

public partial class StoredProcedures
{
    [Microsoft.SqlServer.Server.SqlProcedure]
    public static void CLR_GetAutoPilotShowPlan
    (
         SqlString SQL,
         out SqlXml PlanXML
    )
    {
        //Prep connection
        SqlConnection cn = new SqlConnection("Context Connection = True");

        //Set command texts
        SqlCommand cmd_SetAutoPilotOn = new SqlCommand("SET AUTOPILOT ON", cn);
        SqlCommand cmd_SetAutoPilotOff = new SqlCommand("SET AUTOPILOT OFF", cn);
        SqlCommand cmd_input = new SqlCommand(SQL.ToString(), cn);

        if (cn.State != ConnectionState.Open)
        {
            cn.Open();
        }

        //Run AutoPilot On
        cmd_SetAutoPilotOn.ExecuteNonQuery();

        //Run input SQL
        SqlDataAdapter da = new SqlDataAdapter();
        DataSet ds = new DataSet();

        da.SelectCommand = cmd_input;
        ds.Tables.Add(new DataTable("Results"));

        ds.Tables[0].BeginLoadData();
        da.Fill(ds, "Results");
        ds.Tables[0].EndLoadData();

        //Run AutoPilot Off
        cmd_SetAutoPilotOff.ExecuteNonQuery();

        if (cn.State != ConnectionState.Closed)
        {
            cn.Close();
        }

        //Package XML as output
        System.Xml.XmlDocument xmlDoc = new System.Xml.XmlDocument();
        //XML is in 1st Col of 1st Row of 1st Table
        xmlDoc.InnerXml = ds.Tables[0].Rows[0][0].ToString();
        System.Xml.XmlNodeReader xnr = new System.Xml.XmlNodeReader(xmlDoc);
        PlanXML = new SqlXml(xnr);
    }
};
*/

Following is the code to compile it on SQL Server and to create another procedure to simulate the hypothetical indexes:

-- Publishing Assembly
IF EXISTS(SELECT * FROM sys.assemblies WHERE name = 'CLR_ProjectAutoPilot')
BEGIN
  IF OBJECT_ID('st_CLR_GetAutoPilotShowPlan') IS NOT NULL
    DROP PROC st_CLR_GetAutoPilotShowPlan

  DROP ASSEMBLY CLR_ProjectAutoPilot
END
GO
CREATE ASSEMBLY CLR_ProjectAutoPilot FROM 'C:\ProjectAutoPilot\ProjectAutoPilot\bin\Release\ProjectAutoPilot.dll' WITH PERMISSION_SET = SAFE
GO

CREATE PROCEDURE st_CLR_GetAutoPilotShowPlan (@Query NVarChar(MAX), @ShowPlan XML OUTPUT)
AS
  EXTERNAL NAME CLR_ProjectAutoPilot.StoredProcedures.CLR_GetAutoPilotShowPlan
GO

IF OBJECT_ID('st_TestHipotheticalIndexes', 'p') IS NOT NULL
  DROP PROC dbo.st_TestHipotheticalIndexes
GO
CREATE PROCEDURE dbo.st_TestHipotheticalIndexes (@SQLIndex NVarChar(MAX), @Query NVarChar(MAX))
AS
BEGIN
  SET NOCOUNT ON;
  BEGIN TRY
    BEGIN TRAN
    DECLARE @CreateIndexCommand NVarChar(MAX),
            @IndexName NVarChar(MAX),
            @TableName NVarChar(MAX),
            @SQLIndexTMP NVarChar(MAX),
            @SQLDropIndex NVarChar(MAX),
            @SQLDbccAutoPilot NVarChar(MAX),
            @i Int,
            @QuantityIndex Int,
            @Xml XML

    IF SubString(@SQLIndex, LEN(@SQLIndex), 1) <> ';'
    BEGIN
      RAISERROR ('Last caracter in the index should be ;', -- Message text.
                 16, -- Severity.
                 1 -- State.
                 );
    END

    SET @SQLDropIndex = '';
    SET @QuantityIndex = LEN(@SQLIndex) - LEN(REPLACE(@SQLIndex, ';', ''))
    SELECT @SQLIndexTMP = SUBSTRING(@SQLIndex, 0, CharIndex(';', @SQLIndex))

    SET @i = 0
    WHILE @i < @QuantityIndex
    BEGIN
      SET @SQLIndexTMP = SUBSTRING(@SQLIndex, 0, CharIndex(';', @SQLIndex))
      SET @CreateIndexCommand = SUBSTRING(@SQLIndexTMP, 0, CharIndex(' ON ',@SQLIndexTMP))
      SET @IndexName = REVERSE(SubString(REVERSE(@CreateIndexCommand), 0, CharIndex(' ', REVERSE(@CreateIndexCommand))))
      SET @TableName = SUBSTRING(REPLACE(@SQLIndexTMP, @CreateIndexCommand + ' ON ', ''), 0, CharIndex(' ', REPLACE(@SQLIndexTMP, @CreateIndexCommand + ' ON ', '')))
      IF ISNULL(@TableName,'') = ''
        SET @TableName = SUBSTRING(REPLACE(@SQLIndexTMP, @CreateIndexCommand + ' ON ', ''), 0, CharIndex('(', REPLACE(@SQLIndexTMP, @CreateIndexCommand + ' ON ', '')))
      SET @SQLIndex = REPLACE(@SQLIndex, @SQLIndexTMP + ';', '')
      --SELECT @SQLIndex, @SQLIndexTMP, @CreateIndexCommand, @TableName, @IndexName

      -- Creating hypotetical index
      IF CharIndex('WITH STATISTICS_ONLY =', @SQLIndexTMP) = 0
      BEGIN
        SET @SQLIndexTMP = @SQLIndexTMP + ' WITH STATISTICS_ONLY = -1'
      END
      -- PRINT @SQLIndexTMP
      EXEC (@SQLIndexTMP)

      -- Creating query to drop the hypotetical index
      SELECT @SQLDropIndex = @SQLDropIndex + 'DROP INDEX ' + @TableName + '.' + @IndexName + '; '
      -- PRINT @SQLDropIndex

      -- Executing DBCC AUTOPILOT
      SET @SQLDbccAutoPilot = 'DBCC AUTOPILOT (0, ' + 
                                               CONVERT(VarChar, DB_ID()) + ', '+ 
                                               CONVERT(VarChar, OBJECT_ID(@TableName),0) + ', ' +
                                               CONVERT(VarChar, INDEXPROPERTY(OBJECT_ID(@TableName), @IndexName, 'IndexID')) + ')'

      EXEC (@SQLDbccAutoPilot)
      --PRINT @SQLDbccAutoPilot

      SET @i = @i + 1
    END

    -- Executing Query
    DECLARE @PlanXML xml

    EXEC st_CLR_GetAutoPilotShowPlan @Query = @Query, 
                                     @ShowPlan = @PlanXML OUT
    SELECT @PlanXML

    -- Droping the indexes
    EXEC (@SQLDropIndex)

    COMMIT TRAN
  END TRY
  BEGIN CATCH
    ROLLBACK TRAN
    -- Execute error retrieval routine.
    SELECT ERROR_NUMBER()    AS ErrorNumber,
           ERROR_SEVERITY()  AS ErrorSeverity,
           ERROR_STATE()     AS ErrorState,
           ERROR_PROCEDURE() AS ErrorProcedure,
           ERROR_LINE()      AS ErrorLine,
           ERROR_MESSAGE()   AS ErrorMessage;
  END CATCH;
END
GO

The proc st_TestHipotheticalIndexes expect two input parameters:

  • @SQLIndex: Here you should specify the command to create the index you and to try (the hypothetical indexes), if you want to try more than one index, just call it separating many “create index” commands by coma. For instance:
@SQLIndex = 'CREATE INDEX ix_12 ON Products (Unitprice, CategoryID, SupplierID) INCLUDE(ProductName);CREATE INDEX ix_Quantity ON Order_Details (Quantity);',
  • @Query: Here you should write the query you want to try.

Here is a sample of how to call it on :

-- Sample 1
EXEC dbo.st_TestHipotheticalIndexes @SQLIndex = 'CREATE INDEX ix ON Order_Details(Quantity);', 
@Query = 'SELECT * FROM Order_Details WHERE Quantity < 1'

The results of the query above is an XML with the query plan considering the suggested index:

clip_image001

Another sample:

— Sample 2

-- Sample 2
EXEC dbo.st_TestHipotheticalIndexes @SQLIndex = 'CREATE INDEX ix_12 ON Products (Unitprice, CategoryID, SupplierID) INCLUDE(ProductName);CREATE INDEX ix_Quantity ON Order_Details (Quantity);', 
                                    @Query = 'SELECT p.ProductName, p.UnitPrice, s.CompanyName, s.Country, od.quantity FROM Products as P INNER JOIN Suppliers as S ON P.SupplierID = S.SupplierID INNER JOIN order_details as od ON p.productID = od.productid WHERE P.CategoryID in (1,2,3) AND P.Unitprice < 20 AND S.Country = ''uk'' AND od.Quantity < 90'

clip_image002

Yep, now it is easier… Let me know what do you think and please don’t mind on the crappy code Smile  in the proc to get the tablename, indexname…

Have fun…