1. O que é um índice e como funciona

Um índice no DB2 é uma estrutura de dados separada, organizada em árvore B+ (B-tree), que mantém as chaves de uma ou mais colunas em ordem e aponta para as páginas físicas onde as linhas estão armazenadas.

🦕 Analogia: pense no índice remissivo de um livro técnico. Em vez de ler o livro inteiro para achar "SQLCODE", você vai ao índice, vê "SQLCODE — página 347" e vai direto lá. Sem índice, o DB2 leria a tabela inteira — isso se chama tablespace scan.

O custo de um índice tem dois lados:

OperaçãoCom índiceSem índice
SELECT por chaveRápido — percorre a árvoreLento — lê tudo
SELECT com rangeRápido se clusteringLento — lê tudo
INSERTMais lento — atualiza o índiceMais rápido
UPDATE na chaveMais lento — reorganiza B-treeMais rápido
DELETEMais lento — remove do índiceMais rápido

2. Tipos de índice no DB2

TipoCaracterísticaUso
UniqueNão permite valores duplicados na(s) coluna(s)Chave primária, CPF, número de conta
Non-uniquePermite duplicatasColunas de busca frequente sem unicidade
ClusteringDetermina a ordem física das linhas no tablespaceColuna mais usada em range queries
Non-clusteringSó aponta para as linhas — não afeta ordem físicaBuscas pontuais por outras colunas
PartitionedUm índice por partição do tablespaceTabelas muito grandes (bilhões de linhas)
Regra de ouro: cada tabela deve ter exatamente um clustering index — aquele baseado na coluna pela qual a tabela é acessada com mais frequência em faixas de valores (BETWEEN, >, <). Geralmente é a chave primária ou a data de referência.

3. Clustering index — o mais importante

O clustering index é o único que influencia a ordem física dos dados no tablespace. Quando os dados estão fisicamente ordenados pela chave do clustering index, o DB2 pode ler faixas de linhas em muito menos operações de I/O.

SQLExemplo: tabela de transações com clustering por data
-- Sem clustering por data: uma query de range lê páginas espalhadas
SELECT * FROM TRANSACOES
 WHERE DT_TRANSACAO BETWEEN '2026-06-01' AND '2026-06-30';
-- → pode precisar de milhares de I/Os aleatórios

-- Com clustering por DT_TRANSACAO: todas as linhas do mês estão
--   juntas fisicamente → I/Os sequenciais, prefetch ativo → muito mais rápido

Cluster ratio

O cluster ratio mede o quanto os dados físicos ainda seguem a ordem do clustering index — de 0 a 1 (ou 0% a 100%). Com o tempo, INSERTs fora de ordem e DELETEs deixam o ratio cair.

Cluster ratioSituaçãoAção
0,95 – 1,00ExcelenteNenhuma
0,80 – 0,94AceitávelMonitorar
0,60 – 0,79DegradadoPlanejar REORG
< 0,60CríticoREORG urgente
⚠️ Um clustering index por tablespace: se você criar dois índices com CLUSTER, o DB2 aceita mas só o primeiro mantém efeito real na ordem física. O segundo vira non-clustering automaticamente.

4. Criando índices — sintaxe

SQLCREATE INDEX — variações principais
-- Índice único (chave primária é criada automaticamente com unique index)
CREATE UNIQUE INDEX SCHEMA1.XCONTA01
    ON SCHEMA1.CONTAS (COD_CONTA);

-- Clustering index por data de transação
CREATE INDEX SCHEMA1.XTRANS01
    ON SCHEMA1.TRANSACOES (DT_TRANSACAO)
    CLUSTER;

-- Índice composto — ordem importa (ver seção 5)
CREATE INDEX SCHEMA1.XTRANS02
    ON SCHEMA1.TRANSACOES (COD_AGENCIA, DT_TRANSACAO);

-- Índice com coluna descendente
CREATE INDEX SCHEMA1.XTRANS03
    ON SCHEMA1.TRANSACOES (DT_TRANSACAO DESC);

-- Remover índice
DROP INDEX SCHEMA1.XTRANS03;
Convenção de nomes: em ambientes mainframe é comum prefixar índices com X seguido do nome da tabela e um número sequencial — ex: XCONTA01, XCONTA02. Facilita identificar índices nas telas do DB2 e nos relatórios de EXPLAIN.

5. Índices compostos e matching columns

Um índice composto cobre mais de uma coluna. O conceito de matching columns define quantas colunas do índice o otimizador consegue usar para filtrar os dados.

🦕 Analogia: imagine um índice de agenda organizado por Sobrenome → Nome → Telefone. Você consegue buscar por sobrenome sozinho, ou por sobrenome + nome. Mas não consegue buscar só por nome sem saber o sobrenome — teria que ler a agenda inteira. A mesma lógica vale para índices compostos no DB2.
SQLÍndice: (COD_AGENCIA, COD_PRODUTO, DT_TRANSACAO)
-- 3 matching columns — usa o índice inteiro
WHERE COD_AGENCIA = '001'
  AND COD_PRODUTO = 'CC'
  AND DT_TRANSACAO = '2026-06-15'

-- 2 matching columns — usa as duas primeiras colunas do índice
WHERE COD_AGENCIA = '001'
  AND COD_PRODUTO = 'CC'

-- 1 matching column — usa só a primeira coluna
WHERE COD_AGENCIA = '001'

-- 0 matching columns — NÃO usa o índice (pula a primeira coluna)
WHERE COD_PRODUTO = 'CC'
  AND DT_TRANSACAO = '2026-06-15'
⚠️ A ordem das colunas no índice é crítica. Coloque primeiro as colunas usadas em predicados de igualdade (=), depois as de range (BETWEEN, >, <). Colunas usadas em ORDER BY no final podem eliminar sort operations.

6. Quando o DB2 usa o índice

O otimizador do DB2 decide automaticamente se usa ou não um índice baseado em estatísticas e estimativas de custo. Há situações em que ele ignora o índice mesmo existindo.

O DB2 tende a usar o índice quando:

  • O predicado usa a coluna-chave do índice com =, >, <, BETWEEN, IN
  • A coluna tem alta seletividade — muitos valores distintos (ex: CPF, número de conta)
  • As estatísticas estão atualizadas (RUNSTATS executado recentemente)
  • O resultado esperado é uma fração pequena da tabela

O DB2 tende a ignorar o índice quando:

SQLPredicados que impedem o uso do índice
-- Função aplicada na coluna indexada → índice não é usado
WHERE YEAR(DT_TRANSACAO) = 2026
-- Solução: usar range explícito
WHERE DT_TRANSACAO BETWEEN '2026-01-01' AND '2026-12-31'

-- LIKE com % no início → índice não é usado
WHERE NOME LIKE '%SILVA'
-- Funciona: % só no final
WHERE NOME LIKE 'SILVA%'

-- NOT ou != geralmente impedido
WHERE STATUS != 'C'
-- Solução: reformular com OR ou IN se possível
WHERE STATUS IN ('A', 'P', 'S')

-- Coluna com baixa seletividade → tablespace scan pode ser mais barato
WHERE STATUS = 'A'  -- se 90% das linhas são 'A', scan é mais eficiente
🟣 Stage 1 vs Stage 2: predicados processados diretamente pelo motor de acesso (Stage 1) são muito mais eficientes do que os aplicados depois da leitura (Stage 2). Funções na coluna indexada, IS NOT NULL em colunas nullable e NOT LIKE caem em Stage 2. O EXPLAIN mostra essa distinção na coluna STAGE do plano.

7. Quando criar e quando evitar

Crie um índice quando:

  • A coluna aparece frequentemente em cláusulas WHERE, JOIN ON ou ORDER BY
  • A coluna tem alta seletividade (muitos valores distintos)
  • A tabela é grande e as queries retornam poucos registros (< 5–10% das linhas)
  • A coluna é chave estrangeira usada em JOINs frequentes

Evite criar índice quando:

  • A tabela é pequena — tablespace scan já é rápido o suficiente
  • A coluna tem baixa seletividade (ex: STATUS com 3 valores possíveis)
  • A tabela sofre muitos INSERTs em lote — cada índice extra aumenta o tempo do batch
  • Já existe um índice composto que cobre o mesmo acesso como matching column
Regra prática para batch COBOL: programas de carga massiva (milhões de linhas por job) frequentemente desabilitam índices antes da carga e os reconstroem depois com REBUILD INDEX — é muito mais rápido do que manter o índice atualizado linha a linha.

8. Manutenção — RUNSTATS e REORG

Dois utilitários são essenciais para manter os índices eficientes:

RUNSTATS

Coleta estatísticas de tabelas e índices e as grava no catálogo DB2 (SYSIBM.SYSINDEXES, SYSTABLESTATS). O otimizador usa essas estatísticas para escolher o melhor plano de acesso. Estatísticas desatualizadas levam o otimizador a tomar decisões erradas.

JCLRUNSTATS em tabela e todos os índices
//RUNSTATS EXEC PGM=DSNUTILB,PARM='DB2P,RUNSTATS'
//SYSPRINT DD SYSOUT=*
//SYSIN    DD *
  RUNSTATS TABLESPACE SCHEMA1.TSCONTAS
    TABLE(SCHEMA1.CONTAS)
    INDEX(ALL)
    SHRLEVEL REFERENCE
/*

REORG INDEX

Reconstrói a estrutura B-tree do índice, eliminando páginas fragmentadas e restaurando o cluster ratio. Deve ser executado periodicamente ou quando o cluster ratio cai abaixo do limite aceitável.

JCLREORG de tablespace (reorganiza dados + clustering)
//REORG EXEC PGM=DSNUTILB,PARM='DB2P,REORG'
//SYSPRINT DD SYSOUT=*
//SYSIN    DD *
  REORG TABLESPACE SCHEMA1.TSCONTAS
    UNLOAD CONTINUE
    SHRLEVEL NONE
/*
⚠️ REORG e disponibilidade: SHRLEVEL NONE coloca o tablespace em modo exclusivo durante o REORG — sem acesso de leitura ou escrita. Em produção 24x7, use SHRLEVEL REFERENCE (permite leitura) ou SHRLEVEL CHANGE (permite leitura e escrita, com mais overhead). Combine sempre REORG + RUNSTATS para que as novas estatísticas reflitam o estado reorganizado.

9. Impacto no COBOL embutido

A escolha do índice afeta diretamente o desempenho de programas COBOL que usam SQL embutido. Algumas práticas importantes:

COBOLBoa prática — predicado que usa o índice
      * Índice em COD_CONTA (chave primária) — 1 matching column
           EXEC SQL
               SELECT SALDO, STATUS
                 INTO :WS-SALDO, :WS-STATUS
                 FROM CONTAS
                WHERE COD_CONTA = :WS-COD-CONTA
           END-EXEC
COBOLProblema — função na coluna impede uso do índice
      * EVITE: YEAR() na coluna indexada DT_ABERTURA
           EXEC SQL
               SELECT COUNT(*)
                 INTO :WS-QTD
                 FROM CONTAS
                WHERE YEAR(DT_ABERTURA) = :WS-ANO
           END-EXEC

      * PREFIRA: range explícito — usa o índice
           EXEC SQL
               SELECT COUNT(*)
                 INTO :WS-QTD
                 FROM CONTAS
                WHERE DT_ABERTURA BETWEEN :WS-DT-INI
                                      AND :WS-DT-FIM
           END-EXEC
COBOLCursor com ORDER BY — elimina sort quando alinhado ao clustering index
      * Clustering index em DT_TRANSACAO — ORDER BY aproveita a ordem física
      * e elimina operação de sort interna
           EXEC SQL
               DECLARE CURSOR-TRANS CURSOR FOR
               SELECT COD_CONTA, VL_TRANSACAO, DT_TRANSACAO
                 FROM TRANSACOES
                WHERE COD_AGENCIA = :WS-AGENCIA
                  AND DT_TRANSACAO BETWEEN :WS-DT-INI
                                       AND :WS-DT-FIM
                ORDER BY DT_TRANSACAO
           END-EXEC
🟣 BIND e plano de acesso: o plano de acesso é escolhido no momento do BIND — não em tempo de execução. Se o índice for criado ou removido depois do BIND, o programa continua usando o plano antigo até o próximo REBIND. Em produção, sempre execute REBIND após criar, dropar ou reorganizar índices relevantes.