Introdução
O MySQL disponibiliza o comando EXPLAIN, uma ferramenta poderosa para analisar instruções SELECT e fornecer detalhes sobre sua execução. Essa análise permite que desenvolvedores identifiquem gargalos e otimizem suas consultas de forma mais eficaz.
A utilização do comando é bastante direta, bastando prefixar a consulta SELECT com a palavra-chave EXPLAIN:
EXPLAIN SELECT * from dados_usuario WHERE id < 300;
Preparação do Ambiente
Para demonstrar o uso prático do EXPLAIN, criaremos duas tabelas de teste com estruturas ligeiramente diferentes das originais:
CREATE TABLE `dados_usuario` (
`codigo` BIGINT(20) NOT NULL AUTO_INCREMENT,
`nome` VARCHAR(50) NOT NULL DEFAULT '',
`idade` INT(11) DEFAULT NULL,
PRIMARY KEY (`codigo`),
KEY `idx_nome` (`nome`)
)
ENGINE = InnoDB
DEFAULT CHARSET = utf8
INSERT INTO dados_usuario (nome, idade) VALUES ('carlos', 28);
INSERT INTO dados_usuario (nome, idade) VALUES ('ana', 32);
INSERT INTO dados_usuario (nome, idade) VALUES ('bruno', 45);
INSERT INTO dados_usuario (nome, idade) VALUES ('diana', 19);
INSERT INTO dados_usuario (nome, idade) VALUES ('eduardo', 28);
INSERT INTO dados_usuario (nome, idade) VALUES ('fernanda', 32);
INSERT INTO dados_usuario (nome, idade) VALUES ('gustavo', 45);
INSERT INTO dados_usuario (nome, idade) VALUES ('helena', 19);
INSERT INTO dados_usuario (nome, idade) VALUES ('igor', 28);
INSERT INTO dados_usuario (nome, idade) VALUES ('julia', 32);
CREATE TABLE `registros_pedido` (
`codigo` BIGINT(20) NOT NULL AUTO_INCREMENT,
`cod_usuario` BIGINT(20) DEFAULT NULL,
`item_produto` VARCHAR(50) NOT NULL DEFAULT '',
`fabricante` VARCHAR(30) DEFAULT NULL,
PRIMARY KEY (`codigo`),
KEY `idx_usuario_item_fabricante` (`cod_usuario`, `item_produto`, `fabricante`)
)
ENGINE = InnoDB
DEFAULT CHARSET = utf8
INSERT INTO registros_pedido (cod_usuario, item_produto, fabricante) VALUES (1, 'produto1', 'WHH');
INSERT INTO registros_pedido (cod_usuario, item_produto, fabricante) VALUES (1, 'produto2', 'WL');
INSERT INTO registros_pedido (cod_usuario, item_produto, fabricante) VALUES (1, 'produto1', 'DX');
INSERT INTO registros_pedido (cod_usuario, item_produto, fabricante) VALUES (2, 'produto1', 'WHH');
INSERT INTO registros_pedido (cod_usuario, item_produto, fabricante) VALUES (2, 'produto5', 'WL');
INSERT INTO registros_pedido (cod_usuario, item_produto, fabricante) VALUES (3, 'produto3', 'MA');
INSERT INTO registros_pedido (cod_usuario, item_produto, fabricante) VALUES (4, 'produto1', 'WHH');
INSERT INTO registros_pedido (cod_usuario, item_produto, fabricante) VALUES (6, 'produto1', 'WHH');
INSERT INTO registros_pedido (cod_usuario, item_produto, fabricante) VALUES (9, 'produto8', 'TE');
Formato de Saída do EXPLAIN
A saída do comando EXPLAIN apresenta informações detalhadas sobre como a consulta será executada:
mysql> explain select * from dados_usuario where codigo = 2\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: dados_usuario
partitions: NULL
type: const
possible_keys: PRIMARY
key: PRIMARY
key_len: 8
ref: const
rows: 1
filtered: 100.00
Extra: NULL
1 row in set, 1 warning (0.00 sec)
Vamos analisar os significados das colunas principais:
- id: Identificador único para cada consulta SELECT na execução.
- select_type: Tipo de consulta SELECT.
- table: Tabela envolvida na consulta.
- partitions: Partição correspondente, se aplicável.
- type: Tipo de join.
- possible_keys: Índices que poderiam ser utilizados na consulta.
- key: Índice efetivamente utilizado.
- ref: Campo ou constante utilizado junto com o índice.
- rows: Número estimado de linhas a serem examinadas.
- filtered: Porcentagem de linhas filtradas pelas condições.
- extra: Informações adicionais sobre a execução.
Vamos nos concentrar nos campos mais importantes:
select_type
O campo select_type indica o tipo de consulta, com valores comuns como:
- SIMPLE: Consulta sem UNION ou subconsultas.
- PRIMARY: Consulta mais externa em consultas aninhadas.
- UNION: Segunda ou subsequente consulta em uma operação UNION.
- DEPENDENT UNION: Consulta UNION que depende de resultados externos.
- UNION RESULT: Resultado de uma operação UNION.
- SUBQUERY: Primeira SELECT em uma subconsulta.
- DEPENDENT SUBQUERY: Subconsulta que depende de resultados externos.
O tipo mais comum é SIMPLE, utilizado em consultas diretas sem subconsultas ou UNIONs:
mysql> explain select * from dados_usuario where codigo = 2\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: dados_usuario
partitions: NULL
type: const
possible_keys: PRIMARY
key: PRIMARY
key_len: 8
ref: const
rows: 1
filtered: 100.00
Extra: NULL
1 row in set, 1 warning (0.00 sec)
Para consultas UNION, a saída é diferente:
mysql> EXPLAIN (SELECT * FROM dados_usuario WHERE codigo IN (1, 2, 3))
-> UNION
-> (SELECT * FROM dados_usuario WHERE codigo IN (3, 4, 5));
+----+--------------+--------------+------------+-------+---------------------+---------+---------+------+------+----------+-----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------+--------------+------------+-------+---------------------+---------+---------+------+------+----------+-----------------+
| 1 | PRIMARY | dados_usuario | NULL | range | PRIMARY | PRIMARY | 8 | NULL | 3 | 100.00 | Using where |
| 2 | UNION | dados_usuario | NULL | range | PRIMARY | PRIMARY | 8 | NULL | 3 | 100.00 | Using where |
| NULL | UNION RESULT | <union1,2> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary |
+----+--------------+--------------+------------+-------+---------------------+---------+---------+------+------+----------+-----------------+
3 rows in set, 1 warning (0.00 sec)
type
O campo type é crucial para avaliar a eficiência da consulta. Ele indica se a consulta realiza varredura completa da tabela ou utiliza índices.
Principais tipos de acesso
Os valores mais comuns para type são:
system: Tabela contém apenas uma linha. Tipo especial deconst.const: Consulta em índice primário ou único com igualdade, retorna no máximo uma linha. Muito eficiente.
mysql> explain select * from dados_usuario where codigo = 2\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: dados_usuario
partitions: NULL
type: const
possible_keys: PRIMARY
key: PRIMARY
key_len: 8
ref: const
rows: 1
filtered: 100.00
Extra: NULL
1 row in set, 1 warning (0.00 sec)
eq_ref: Comum em joins multi-tabela, para cada linha da tabela anterior corresponde exatamente uma linha na tabela atual. Operações geralmente com=.
mysql> EXPLAIN SELECT * FROM dados_usuario, registros_pedido WHERE dados_usuario.codigo = registros_pedido.cod_usuario\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: registros_pedido
partitions: NULL
type: index
possible_keys: idx_usuario_item_fabricante
key: idx_usuario_item_fabricante
key_len: 314
ref: NULL
rows: 9
filtered: 100.00
Extra: Using where; Using index
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: dados_usuario
partitions: NULL
type: eq_ref
possible_keys: PRIMARY
key: PRIMARY
key_len: 8
ref: test.registros_pedido.cod_usuario
rows: 1
filtered: 100.00
Extra: NULL
2 rows in set, 1 warning (0.00 sec)
ref: Utilizado em joins com índices não únicos ou quando aplicada a regra do prefixo esquerdo do índice.
mysql> EXPLAIN SELECT * FROM dados_usuario, registros_pedido WHERE dados_usuario.codigo = registros_pedido.cod_usuario AND registros_pedido.cod_usuario = 5\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: dados_usuario
partitions: NULL
type: const
possible_keys: PRIMARY
key: PRIMARY
key_len: 8
ref: const
rows: 1
filtered: 100.00
Extra: NULL
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: registros_pedido
partitions: NULL
type: ref
possible_keys: idx_usuario_item_fabricante
key: idx_usuario_item_fabricante
key_len: 9
ref: const
rows: 1
filtered: 100.00
Extra: Using index
2 rows in set, 1 warning (0.01 sec)
range: Utiliza índice para busca por intervalo. Comum em operações como =, <>, >, >=, <, <=, IS NULL, <=>, BETWEEN, IN().
mysql> EXPLAIN SELECT *
-> FROM dados_usuario
-> WHERE codigo BETWEEN 2 AND 8 \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: dados_usuario
partitions: NULL
type: range
possible_keys: PRIMARY
key: PRIMARY
key_len: 8
ref: NULL
rows: 7
filtered: 100.00
Extra: Using where
1 row in set, 1 warning (0.00 sec)
index: Varredura completa do índice, sem acesso aos dados. Quando os dados necessários estão contidos no próprio índice.
mysql> EXPLAIN SELECT nome FROM dados_usuario \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: dados_usuario
partitions: NULL
type: index
possible_keys: NULL
key: idx_nome
key_len: 152
ref: NULL
rows: 10
filtered: 100.00
Extra: Using index
1 row in set, 1 warning (0.00 sec)
- ALL: Varredura completa da tabela. Tipo de acesso menos eficiente, deve ser evitado sempre que possível.
mysql> EXPLAIN SELECT idade FROM dados_usuario WHERE idade = 28 \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: dados_usuario
partitions: NULL
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 10
filtered: 30.00
Extra: Using where
1 row in set, 1 warning (0.00 sec)
Comparação de performance
A relação de performence entre os tipos de acesso é:
ALL < index < range ~ index_merge < ref < eq_ref < const < system
possible_keys
Indica os índices que o MySQL poderia utilizar para executar a consulta. Não garante que serão usados de fato.
key
Mostra o índice efetivamente utilizado na consulta.
key_len
Comprimento em byttes do índice utilizado. Útil para verificar se índices compostos estão sendo totalmente aproveitados.
Regras de cálculo:
- Strings:
- char(n): n bytes
- varchar(n): utf8 = 3n + 2 bytes; utf8mb4 = 4n + 2 bytes
- Numéricos:
- TINYINT: 1 byte
- SMALLINT: 2 bytes
- MEDIUMINT: 3 bytes
- INT: 4 bytes
- BIGINT: 8 bytes
- Data:
- DATE: 3 bytes
- TIMESTAMP: 4 bytes
- DATETIME: 8 bytes
- Atributos: NULL = 1 byte; NOT NULL = 0 bytes
Exemplos:
mysql> EXPLAIN SELECT * FROM registros_pedido WHERE cod_usuario < 3 AND item_produto = 'produto1' AND fabricante = 'WHH' \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: registros_pedido
partitions: NULL
type: range
possible_keys: idx_usuario_item_fabricante
key: idx_usuario_item_fabricante
key_len: 9
ref: NULL
rows: 5
filtered: 11.11
Extra: Using where; Using index
1 row in set, 1 warning (0.00 sec)
Neste caso, apenas o campo cod_usuario do índice composto é utilizado devido à busca por intervalo, resultando em key_len = 9 (8 para BIGINT + 1 para NULL).
mysql> EXPLAIN SELECT * FROM registros_pedido WHERE cod_usuario = 1 AND item_produto = 'produto1' \G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: registros_pedido
partitions: NULL
type: ref
possible_keys: idx_usuario_item_fabricante
key: idx_usuario_item_fabricante
key_len: 161
ref: const,const
rows: 2
filtered: 100.00
Extra: Using index
1 row in set, 1 warning (0.00 sec)
Aqui, key_len = 161 (8 para cod_usuario + 150 para item_produto + 3 para NULL).
rows
Número estimado de linhas que o otimizador precisa examinar para executar a cosnulta. Valores menores indicam consultas mais eficientes.
Extra
Contém informações adicionais importantes sobre a execução:
- Using filesort: Indica que uma ordenação extra é necessária, sem uso de índice. Deve ser evitada por consumir muitos recursos.
mysql> EXPLAIN SELECT * FROM registros_pedido ORDER BY item_produto \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: registros_pedido
partitions: NULL
type: index
possible_keys: NULL
key: idx_usuario_item_fabricante
key_len: 253
ref: NULL
rows: 9
filtered: 100.00
Extra: Using index; Using filesort
1 row in set, 1 warning (0.00 sec)
- Using index: "Varredura de índice de cobertura", os dados necessários estão no próprio índice, sem acesso à tabela.
- Using temporary: Indica uso de tabela temporária, comum em ordenações, agrupamentos e joins. Deve ser otimizado quando possível.