# COMANDO
01 SELECT * FROM PESSOA
02 SELECT * FROM PESSOA WHERE COD = 1
03 SELECT NOME FROM PESSOA
04 SELECT NOME FROM PESSOA WHERE NOME = ‘AGNALDO’
05 SELECT COD, NOME FROM PESSOA WHERE NOME = ‘AGNALDO’
06 SELECT * FROM PESSOA WHERE NOME = ‘AGNALDO’
COMANDO SEM ÍNDICE NON-CLUSTERED CLUSTERED e NON-CLUSTERED
01 TABLE SCAN TABLE SCAN CLUSTERED INDEX SCAN
02 TABLE SCAN TABLE SCAN CLUSTERED INDEX SEEK
03 TABLE SCAN INDEX SCAN INDEX SCAN
04 TABLE SCAN INDEX SEEK INDEX SEEK
05 TABLE SCAN TABLE SCAN INDEX SEEK
06 TABLE SCAN TABLE SCAN CLUSTERED INDEX SCAN
COMANDO SEM ÍNDICE NON-CLUSTERED CLUSTERED
e
NON-CLUSTERED
01 TABLE SCAN TABLE SCAN CLUSTERED INDEX SCAN
02 TABLE SCAN TABLE SCAN CLUSTERED INDEX SEEK
03 TABLE SCAN INDEX SCAN INDEX SCAN
04 TABLE SCAN INDEX SEEK INDEX SEEK
05 TABLE SCAN TABLE SCAN INDEX SEEK
06 TABLE SCAN TABLE SCAN CLUSTERED INDEX SCAN

Nesse ponto, temos que sabe o que é a operação:

  • SCAN: busca em TODOS os elementos da estrutura (que pode ser uma tabela ou um índice);
  • SEEK: busca binária nos elementos de um índice.

Devemos atentar para a seguinte observação: SCAN é usado quando a tabela não possui índices que atendam ao select ou quando a quantidade de registros que a query retorna (em percentual) é grande. O SEEK é usado quando existe um índice que é adequado e a quantidade de registros retornados é pequena, percentualmente falando.

As operações executadas nas tabelas/índices foram:

  • TABLE SCAN: Busca em todos os elementos da tabela, de forma seqüencial;
  • INDEX SCAN: Busca em todos os elementos de um índice non-clustered, de forma seqüencial;
  • CLUSTERED INDEX SCAN: Busca em todos os elementos de um índice clustered, de forma seqüencial;
  • INDEX SEEK: Busca binária num índice non-clustered;
  • CLUSTERED INDEX SEEK: Busca binária num índice non-clustered.

Agora, por que o SQL Server criou planos de execução tão diferentes? Porque ele “sabe” (usando as estatísticas - assunto que abordarei em outro post) qual tipo de acesso tem maior probabilidade de retornar os dados pedidos no menor tempo possível.

E como o SQL Server escreve os índices no disco? Vamos lá.

Para essa explicação, vamos dropar o database que estávamos usando e recriá-lo, junto com a tabela pessoa. Após isso, vamos inserir as 8 pessoas.

Lembro que, para dropar um banco, usamos os comandos:


USE MASTER
DROP DATABASE EXEMPLO_INDICE

Após a criação da tabela, fazemos um select na tabela indexes do schema sys (que vou chamar, a partir de agora, de sys.indexes):


SELECT *
FROM SYS.INDEXES
WHERE OBJECT_ID = OBJECT_ID('PESSOA')

Vemos que existe uma linha na sys.indexes, mesmo PESSOA não tendo índice. Na realidade, sys.indexes armazena dados dos índices (quando existem) e das tabelas. Sabemos que a tabela não possui índice clustered porque seu type_desc é HEAP.


DECLARE @DB_ID INT,
      @OBJECT_ID INT
SELECT @DB_ID = DB_ID(‘EXEMPLO_INDICE’),
      @OBJECT_ID = OBJECT_ID(‘PESSOA’)

DBCC IND(@DB_ID, @OBJECT_ID, -1)

Onde:

  • PagePID: é o número da página de dados onde a informação está escrita;
  • IndexID: é o tipo de estrutura:

    • 0 - página de dados;
    • 1 - índice clustered;
    • 2 a 255 - índices non-clustered;
  • PageType: é o tipo do dado armazenado:

    • 1 - página de dados;
    • 2 - página de índice;
    • 10 - mapa de alocação de índices;

    Executando o comando abaixo e fornecendo o valor de PagePID obtido com o script acima:

    
    DECLARE @DB_ID INT
    SELECT @DB_ID = DB_ID(‘EXEMPLO_INDICE’)
    
    DBCC TRACEON(3604)
    DBCC PAGE(@DB_ID, 1, 152, 3)
    DBCC TRACEOFF(3604)
    

    Temos a descrição do conteúdo das página em questão.

    Agora, recriamos o índice non-clustered, usando o mesmo comando do post anterior. Em seguida fazemos o select na sys.objects que, nesse momento, nos mostra a existência do índice non-clustered.

    Executando o DBCC IND (acima) vemos que foram alocadas páginas para o índice.

    Com o DBCC PAGE (acima) vemos o conteúdo da página - temos que substituir o valor 152 (página de dados da tabela) por 154 (página que armazena os dados do índice).

    Vemos, na figura acima, que existe uma coluna (HEAP RID) que aponta para o endereço do registro na página de dados.

    Recriamos o índice clustered. Em seguida executamos os mesmos comandos mostrados anteriormente (select na sys.indexes, DBCC IND e DBCC PAGE para o índice non-clustered, trocando o número da página, claro - no meu servidor, 158).

    Note que o type_desc do select mudou de HEAP para CLUSTERED.

    A coluna que aponta para endereço do registro não é mais o HEAP RID e sim COD, que é a chave do índice clustered. Por isso que uma consulta por cod, nome (o select número 5) na tabela pessoa faz um table scan quando a tabela somente tem o índice non-clustered e index seek quando a tabela tem índices clustered e non-clustered.