Aumentando a performance de consultas SQL adicionando índices e removendo Key Lookups

E aí galera, belezura?

Nesse artigo vamos fazer uma otimização de performance e consumo de recursos no SQL Server fazendo o uso de índices, com o adendo de nos atentarmos aos “Key Lookups”. Bora!

Antes de qualquer coisa, caso você não saiba o que é um índice de banco de dados, pode dar uma olhada nesse cara aqui.

A ideia, é fazer uma demonstração de maneira prática, se fossemos nos atentar a cada detalhe, e focar na teoria disso tudo, íamos nos estender muito e ficar bem cansativo, então vamos direto ao ponto.

Hora da prática!

Imagine que em um banco de dados qualquer, tenhamos uma tabela de Cliente, criada da seguinte maneira:


CREATE TABLE Cliente (
Id INT IDENTITY(1,1) NOT NULL,
Nome VARCHAR(50) NOT NULL,
Sobrenome VARCHAR(50) NOT NULL,
Email VARCHAR(200) NOT NULL,
CONSTRAINT Cliente_PK PRIMARY KEY (Id)
)

view raw

Cliente.sql

hosted with ❤ by GitHub

Simples, certo? Visualizando de maneira gráfica, a tabela fica assim:

001

Para realizarmos nossos testes, vamos popular essa tabela com 1.000.000 (um milhão) de registros. Para fazer isto rodamos o seguinte comando:


DECLARE @Contador INT = 1000000
WHILE @Contador > 0
BEGIN
INSERT INTO Cliente(Nome, Sobrenome, Email)
VALUES (
SUBSTRING(REPLACE(CONVERT(VARCHAR(250),NEWID()), '-', ''),1,15),
SUBSTRING(REPLACE(CONVERT(VARCHAR(250),NEWID()), '-', ''),1,15),
LOWER(SUBSTRING(REPLACE(CONVERT(VARCHAR(250),NEWID()), '-', ''),1,30)) + '@email.com'
)
SET @Contador = @Contador 1
END

Abaixo, vamos rodar um comando para verificar a quantidade de registros na tabela e também faremos a seleção dos 10 primeiros registros para verificarmos como ficaram nossos dados:


SELECT COUNT(*) AS QuantidadeRegistros FROM Cliente
SELECT TOP 10 * FROM Cliente

O resultado é o seguinte:

002

Com a base pupulada, vamos alterar um registro qualquer, somente para fins didáticos, para procurar por um e-mail mais “bonitinho” do que um combinado de caracteres, veja:


UPDATE Cliente
SET Nome = 'Vinicius',
Sobrenome = 'Mussak',
Email = 'vinicius.mussak@outlook.com'
WHERE Id = 333123

A consulta que vamos trabalhar é a que vou exibir a seguir, não vamos mexer em nada na query em si, somente nos índices, vejam a query:


SELECT Id,
Nome,
Sobrenome,
Email
FROM Cliente
WHERE Email = 'vinicius.mussak@outlook.com'

Primeiro problema

Logo de cara, temos o seguinte plano de execução:

003

Os detalhes:

004

Reparem na propriedade “Number of rows read“, que é 1.000.000, ou seja, todos os registros da tabela, isso significa que o banco de dados faz mais ou menos isso aqui:

010

O mecanismo percorre registro por registro “perguntando” se a linha em questão possui o e-mail que estamos buscando, e quando a condição é satisfeita, o registro é retornado para a consulta.

Vejam que para retornar esse registro localmente e sem concorrência, o banco de dados precisou de 280ms, e percorreu toda a tabela, gerando um custo de I/O consideravelmente alto.

Primeira solução

Para sanarmos esse problema, de percorrer a tabela toda, vamos criar um índice pelo campo “Email”, que funciona como um índice de um livro mesmo, mostrando fisicamente onde fica esse registro na tabela facilitando a busca. Para criar o índice, utilizamos o seguinte trecho de código:


CREATE INDEX IDX_ClienteEmail
ON Cliente(Email)

view raw

IndiceEmail.sql

hosted with ❤ by GitHub

A sintaxe é simples, um índice precisa ter um nome, uma tabela e um ou vários campos a serem indexados.

Se executarmos a mesma query:


SELECT Id,
Nome,
Sobrenome,
Email
FROM Cliente
WHERE Email = 'vinicius.mussak@outlook.com'

agora, o plano de execução fica dessa maneira:

005

Em detalhes:

Agora podemos observar que a consulta é realizada em duas partes.

O problema foi resolvido, pois o tempo de leitura foi para 0ms para cada parte da consulta, e o número de linhas lidas foi para 1, ou seja, já economizamos tempo e I/O do nosso banco de dados.

As duas etapas que são feitas na consulta são:

– Busca do registro na tabela utilizando o índice “IDX_ClienteEmail” (que é o que a gente queria que acontecesse, né?);

– Busca do registro utilizando a PK da tabela, que é o Id. (WTF?);

Segundo problema

Mas porque isso acontece?

Vou exemplificar abaixo para entendermos melhor, vejam:

011

Explicando a imagem acima:

Na primeira parte, que é a busca pelo índice que criamos, o banco de dados percorre cada nó para verificar se a condição que queremos é satisfeita (Email igual ao que estamos buscando), porém, o nosso índice não “encontra” todos os atributos da tabela, ele encontra apenas o campo “Email”, que é o campo indexado, e a PK da tabela, que é o campo Id.

Como em nossa query buscamos os campos: “Id”, “Nome”, “Sobrenome” e “Email”, o banco de dados precisa percorrer a tabela novamente para encontrar os demais atributos que precisamos buscar, chamado de “Key Lookup“. Ou seja, eu busco a chave primária pelo índice que eu preciso, e depois preciso buscar o resto dos dados olhando para a chave primária.

Só que dessa vez, não precisamos ir de linha a linha, pois temos o Id exato que queremos buscar. Por isso a busca pela PK, que é nosso índice clusterizado, e que retorna todos os atributos da tabela.

Deu pra entender até aqui? Espero que sim!

Solução final

Em certos casos, o banco de dados “prefere” nem utilizar o índice por conta do Key Lookup, pois é uma tarefa muito custosa.

Para removermos isso, e buscarmos todos os atributos que precisamos da tabela de uma vez só, vamos utilizar o comando “INCLUDE”, vejam:


CREATE INDEX IDX_ClienteEmail
ON Cliente(Email)
INCLUDE (Nome, Sobrenome)
WITH (DROP_EXISTING = ON)

Vejam que incluímos os campos que precisamos buscar dentro de “INCLUDE”, com isso conseguiremos remover o Key Lookup.

A termo “DROP_EXISTING = ON” é somente para excluirmos o índice que já existe e substituir pelo novo.

E quando executamos a query:


SELECT Id,
Nome,
Sobrenome,
Email
FROM Cliente
WHERE Email = 'vinicius.mussak@outlook.com'

Vejam como fica o plano de execução:

008

Em detalhes:

009

Ou seja, a nossa consulta agora tem apenas 1 etapa, que é somente a busca pelo índice “IDX_ClienteEmail” que busca dados da tabela “Cliente” pelo campo “Email”, lê apenas 1 linha em 0ms. Isso quer dizer que o consumo de recursos é mínimo e o tempo também.

Para exemplificar, esse índice funciona dessa maneira:

012

Não precisamos fazer uma busca pela PK, pois os atributos da tabela que precisamos já estão incluídos no próprio índice.

Fácil?

Os códigos utilizados estão disponíveis no GitHub: https://github.com/vmussak/performance-sql-index-key-lookup

Por hoje é só isso, qualquer dúvida ou sugestão, estou à disposição! Até mais 😀

Advertisement

5 thoughts on “Aumentando a performance de consultas SQL adicionando índices e removendo Key Lookups

  1. Douglas Karpinski 18/11/2019 / 10:44

    Foda! De extrema importância esse tema (indices) e muito didático. Parabens Brother!

    Gostar

  2. Luis Nascimento 01/04/2020 / 09:53

    Excelente Mussakera, muito interessante esse lance do include, não conhecia e também não sabia que era feito uma segunda query pela pk. Uma dúvida, como faria a seleção dos campo para criar index para uma base que será consumida pelo Power BI por exemplo?

    Gostar

    • viniciusmussak 06/04/2020 / 01:22

      Opa Luis! Valeu pelo feedback 🙂
      Cara, você pode criar os índices dentro da ferramenta do power BI mesmo, respondi sua pergunta? Se não, me chama e conversamos!
      Abraço!

      Gostar

      • Luis Nascimento 10/04/2020 / 16:29

        Boa Mussak, respondeu sim, obrigado brother

        Gostar

Deixe uma Resposta

Preencha os seus detalhes abaixo ou clique num ícone para iniciar sessão:

Logótipo da WordPress.com

Está a comentar usando a sua conta WordPress.com Terminar Sessão /  Alterar )

Facebook photo

Está a comentar usando a sua conta Facebook Terminar Sessão /  Alterar )

Connecting to %s