1. Como o otimizador decide
O otimizador do DB2 é baseado em custo — ele estima o número de operações de I/O e CPU para cada estratégia de acesso possível e escolhe a de menor custo. Para isso, usa estatísticas armazenadas no catálogo DB2 geradas pelo RUNSTATS.
O plano de acesso é definido em tempo de BIND — não em tempo de execução. Isso significa que:
- Criar um índice novo não muda o plano de um programa já compilado — é preciso fazer REBIND
- Estatísticas coletadas após o BIND não são usadas até o próximo REBIND
- O mesmo SQL pode ter planos diferentes em subsistemas diferentes se as estatísticas divergirem
2. PLAN_TABLE — onde o EXPLAIN grava
O EXPLAIN não mostra o plano na tela — ele insere linhas em uma tabela chamada PLAN_TABLE no schema do usuário. Você precisa criar essa tabela uma vez antes de usar o EXPLAIN.
CREATE TABLE USERID.PLAN_TABLE ( QUERYNO INTEGER NOT NULL, QBLOCKNO SMALLINT NOT NULL, APPLNAME CHAR(8) NOT NULL, PROGNAME CHAR(8) NOT NULL, PLANNO SMALLINT NOT NULL, METHOD SMALLINT NOT NULL, CREATOR CHAR(8) NOT NULL, TNAME CHAR(18) NOT NULL, TABNO SMALLINT NOT NULL, ACCESSTYPE CHAR(2) NOT NULL, MATCHCOLS SMALLINT NOT NULL, ACCESSCREATOR CHAR(8) NOT NULL, ACCESSNAME CHAR(18) NOT NULL, INDEXONLY CHAR(1) NOT NULL, SORTN_UNIQ CHAR(1) NOT NULL, SORTN_JOIN CHAR(1) NOT NULL, SORTN_ORDERBY CHAR(1) NOT NULL, SORTN_GROUPBY CHAR(1) NOT NULL, SORTC_UNIQ CHAR(1) NOT NULL, SORTC_JOIN CHAR(1) NOT NULL, SORTC_ORDERBY CHAR(1) NOT NULL, SORTC_GROUPBY CHAR(1) NOT NULL, TSLOCKMODE CHAR(3) NOT NULL, TIMESTAMP CHAR(16) NOT NULL, REMARKS VARCHAR(254) NOT NULL, PREFETCH CHAR(1) NOT NULL, COLUMN_FN_EVAL CHAR(1) NOT NULL, MIXOPSEQ SMALLINT NOT NULL, VERSION VARCHAR(64) NOT NULL, COLLID CHAR(18) NOT NULL, ACCESS_DEGREE SMALLINT, JOIN_DEGREE SMALLINT, SORTC_PGROUP_ID SMALLINT, SORTN_PGROUP_ID SMALLINT, PARALLELISM_MODE CHAR(1), MERGE_JOIN_COLS SMALLINT, CORRELATION_NAME CHAR(18), PAGE_RANGE CHAR(1) NOT NULL, JOIN_TYPE CHAR(1) NOT NULL, GROUP_MEMBER CHAR(8) NOT NULL, IBM_SERVICE_DATA VARCHAR(254) );
3. Executando o EXPLAIN
-- QUERYNO identifica o resultado na PLAN_TABLE EXPLAIN PLAN SET QUERYNO = 1 FOR SELECT C.NOME, SUM(T.VL_TRANSACAO) AS TOTAL FROM CLIENTES C JOIN TRANSACOES T ON C.COD_CLIENTE = T.COD_CLIENTE WHERE T.DT_TRANSACAO BETWEEN '2026-06-01' AND '2026-06-30' GROUP BY C.NOME ORDER BY TOTAL DESC FETCH FIRST 10 ROWS ONLY;
SELECT QUERYNO , QBLOCKNO , PLANNO , TNAME , ACCESSTYPE , MATCHCOLS , ACCESSNAME , INDEXONLY , PREFETCH , SORTN_JOIN , SORTC_JOIN , SORTN_ORDERBY , SORTC_ORDERBY FROM USERID.PLAN_TABLE WHERE QUERYNO = 1 ORDER BY PLANNO;
4. ACCESSTYPE — o tipo de acesso
É a coluna mais importante do EXPLAIN. Indica como o DB2 vai acessar cada tabela do plano.
| ACCESSTYPE | Significado | Performance |
|---|---|---|
I | Index scan — acessa dados via índice | Boa |
I1 | One-fetch index scan — retorna no máximo 1 linha | Excelente |
N | Index scan sem acesso à tabela (index only) | Excelente |
R | Tablespace scan — lê a tabela inteira | Ruim para tabelas grandes |
M | Multi-index scan — combina vários índices com list prefetch | Variável |
MX | Multiple index ORing — une resultados de índices diferentes | Variável |
5. Colunas-chave do PLAN_TABLE
| Coluna | O que indica | O que observar |
|---|---|---|
TNAME | Nome da tabela acessada neste passo | Qual tabela e em qual ordem |
ACCESSTYPE | Tipo de acesso (I, R, N...) | Preferir I ou N; evitar R em tabelas grandes |
MATCHCOLS | Nº de colunas do índice usadas como matching | Quanto maior, melhor a filtragem |
ACCESSNAME | Nome do índice usado | Confirmar que é o índice esperado |
INDEXONLY | 'Y' se a query é respondida só pelo índice | 'Y' é o ideal — sem acesso à tabela |
PREFETCH | Tipo de leitura antecipada ativa | S ou L = bom; branco = I/O individual |
METHOD | Método de JOIN (0=primeira tabela, 1=nested loop, 2=merge scan, 4=hybrid) | Entender como tabelas são combinadas |
TSLOCKMODE | Modo de lock no tablespace | IS/IX=compartilhado; S=exclusivo de leitura |
6. Lendo um plano completo
Veja um exemplo real de resultado do EXPLAIN para a query da seção 3:
QUERYNO PLANNO TNAME ACCESSTYPE MATCHCOLS ACCESSNAME INDEXONLY PREFETCH SORTC_JOIN SORTC_ORDERBY
------- ------ ----------- ---------- --------- ------------ --------- -------- ---------- -------------
1 1 TRANSACOES I 1 XTRANS01 N S N N
1 2 CLIENTES I 1 XCLIEN01 N L Y N
1 3 (sort) - - - - - - Y
Interpretando linha a linha:
- PLANNO 1 — TRANSACOES: acesso via índice XTRANS01, 1 matching column (DT_TRANSACAO no range), prefetch sequencial ativo. Bom — a data estava no clustering index.
- PLANNO 2 — CLIENTES: nested loop sobre as linhas de TRANSACOES, acessa CLIENTES via índice XCLIEN01, 1 matching column (COD_CLIENTE). Prefetch de lista ativo. SORTC_JOIN='Y' significa que o DB2 ordenou o resultado de CLIENTES para o join — custo extra mas esperado.
- PLANNO 3 — sort: operação de ORDER BY final (SORTC_ORDERBY='Y'). Inevitável neste caso pois ORDER BY é sobre o SUM calculado.
7. INDEX ONLY — o acesso mais eficiente
INDEXONLY = 'Y' significa que todas as colunas que a query precisa estão no índice — o DB2 nunca precisa acessar as páginas de dados da tabela. É o cenário de melhor performance possível para leituras.
-- Índice em (COD_AGENCIA, DT_TRANSACAO, VL_TRANSACAO) -- Query usa apenas essas três colunas → sem acesso à tabela SELECT COD_AGENCIA , COUNT(*) , SUM(VL_TRANSACAO) FROM TRANSACOES WHERE COD_AGENCIA = '001' AND DT_TRANSACAO BETWEEN '2026-06-01' AND '2026-06-30' GROUP BY COD_AGENCIA;
8. PREFETCH — leitura antecipada
Prefetch é a capacidade do DB2 de requisitar páginas ao gerenciador de buffer antes de precisar delas, eliminando tempo de espera de I/O.
| Valor | Tipo | Quando ocorre |
|---|---|---|
S | Sequential prefetch | Tablespace scan ou clustering index scan — lê páginas contíguas em lote |
L | List prefetch | Non-clustering index — monta lista de RIDs, ordena e faz I/Os agrupados |
D | Dynamic prefetch | DB2 detecta padrão sequencial em tempo de execução e ativa prefetch |
' ' | Sem prefetch | Acessos aleatórios pontuais — cada página lida individualmente |
9. Operações de SORT
Colunas de SORT na PLAN_TABLE usam o prefixo N (new table — tabela sendo acessada) ou C (composite — resultado do join até o momento).
| Coluna | Quando é 'Y' | Custo |
|---|---|---|
SORTN_JOIN | Sort na tabela nova antes do join | Médio |
SORTC_JOIN | Sort no resultado composto para o join | Médio |
SORTN_ORDERBY | Sort na tabela nova para ORDER BY | Baixo a médio |
SORTC_ORDERBY | Sort no resultado composto para ORDER BY | Médio a alto |
SORTN_GROUPBY | Sort na tabela nova para GROUP BY | Médio |
SORTC_GROUPBY | Sort no resultado composto para GROUP BY | Médio a alto |
SORTN_UNIQ | Sort para eliminar duplicatas (DISTINCT) | Médio |
Sorts são caros porque consomem memória de trabalho (work files em DSNDB07) e CPU. Para eliminar sorts de ORDER BY, o clustering index deve estar alinhado com a cláusula ORDER BY da query.
-- Clustering index em DT_TRANSACAO ASC -- Esta query elimina o sort de ORDER BY — dados já estão na ordem certa SELECT COD_CONTA, VL_TRANSACAO FROM TRANSACOES WHERE DT_TRANSACAO = '2026-06-15' ORDER BY DT_TRANSACAO; -- alinhado ao clustering → SORTC_ORDERBY = N -- Esta query FORÇA sort — ORDER BY em coluna diferente do clustering SELECT COD_CONTA, VL_TRANSACAO FROM TRANSACOES WHERE DT_TRANSACAO = '2026-06-15' ORDER BY VL_TRANSACAO; -- diferente → SORTC_ORDERBY = Y
10. Como melhorar o plano
Depois de ler o EXPLAIN, estas são as ações mais comuns para melhorar o plano de acesso:
| Problema no EXPLAIN | Causa provável | Ação |
|---|---|---|
| ACCESSTYPE = 'R' em tabela grande | Sem índice adequado ou predicado impede uso | Criar índice ou reescrever predicado |
| MATCHCOLS = 0 com índice existente | Função na coluna ou coluna errada no predicado | Reescrever para predicado stage 1 |
| Muitos SORT = 'Y' | ORDER BY / GROUP BY sem apoio de índice | Alinhar clustering index ou criar índice cobrindo o ORDER BY |
| INDEXONLY = 'N' em query simples | SELECT inclui coluna fora do índice | Considerar covering index |
| PREFETCH = ' ' em scan grande | Cluster ratio baixo ou acesso muito aleatório | REORG tablespace ou rever estratégia de acesso |
| Ordem errada das tabelas no JOIN | Estatísticas desatualizadas | RUNSTATS + REBIND |
-- 1. Executar EXPLAIN da query problemática EXPLAIN PLAN SET QUERYNO = 99 FOR SELECT ... /* query suspeita */; -- 2. Consultar resultado SELECT PLANNO, TNAME, ACCESSTYPE, MATCHCOLS, ACCESSNAME, INDEXONLY, PREFETCH, SORTC_JOIN, SORTC_ORDERBY FROM USERID.PLAN_TABLE WHERE QUERYNO = 99 ORDER BY PLANNO; -- 3. Verificar quando as estatísticas foram coletadas SELECT CREATOR, NAME, CARDF, NPAGES, PCTROWCOMP, STATSTIME FROM SYSIBM.SYSTABLESTATS WHERE CREATOR = 'SCHEMA1' AND NAME = 'TRANSACOES'; -- 4. Verificar cluster ratio dos índices SELECT CREATOR, NAME, TBNAME, CLUSTERING, CLUSTERRATIO, FULLKEYCARDF, FIRSTKEYCARDF FROM SYSIBM.SYSINDEXES WHERE TBCREATOR = 'SCHEMA1' AND TBNAME = 'TRANSACOES'; -- 5. Executar RUNSTATS se necessário, depois REBIND -- 6. Rodar EXPLAIN novamente e comparar