É realmente necessário que todas as colunas selecionadas sejam indexadas para que o MySQL opte por usar o índice?
Esta é uma pergunta carregada, porque existem fatores que determinam se vale a pena usar um índice.
FATOR 1
Para qualquer índice, qual é a população principal? Em outras palavras, qual é a cardinalidade (contagem distinta) de todas as tuplas registradas no índice?
FATOR 2
Qual mecanismo de armazenamento você está usando? Todas as colunas necessárias são acessíveis a partir de um índice?
QUAL É O PRÓXIMO ???
Vamos dar um exemplo simples: uma tabela que contém dois valores (masculino e feminino)
Vamos criar uma tabela com um teste para uso do índice
USE test
DROP TABLE IF EXISTS mf;
CREATE TABLE mf
(
id int not null auto_increment,
gender char(1),
primary key (id),
key (gender)
) ENGINE=InnODB;
INSERT INTO mf (gender) VALUES
('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
('M'),('M'),('M'),('M'),('F'),('F'),('M'),('M'),
('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
('F'),('M'),('M'),('M'),('M'),('M'),('M'),('M');
ANALYZE TABLE mf;
EXPLAIN SELECT gender FROM mf WHERE gender='F';
EXPLAIN SELECT gender FROM mf WHERE gender='M';
EXPLAIN SELECT id FROM mf WHERE gender='F';
EXPLAIN SELECT id FROM mf WHERE gender='M';
TEST InnoDB
mysql> USE test
Database changed
mysql> DROP TABLE IF EXISTS mf;
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE TABLE mf
-> (
-> id int not null auto_increment,
-> gender char(1),
-> primary key (id),
-> key (gender)
-> ) ENGINE=InnoDB;
Query OK, 0 rows affected (0.07 sec)
mysql> INSERT INTO mf (gender) VALUES
-> ('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
-> ('M'),('M'),('M'),('M'),('F'),('F'),('M'),('M'),
-> ('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
-> ('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
-> ('F'),('M'),('M'),('M'),('M'),('M'),('M'),('M');
Query OK, 40 rows affected (0.06 sec)
Records: 40 Duplicates: 0 Warnings: 0
mysql> ANALYZE TABLE mf;
+---------+---------+----------+----------+
| Table | Op | Msg_type | Msg_text |
+---------+---------+----------+----------+
| test.mf | analyze | status | OK |
+---------+---------+----------+----------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT gender FROM mf WHERE gender='F';
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 3 | Using where; Using index |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT gender FROM mf WHERE gender='M';
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 37 | Using where; Using index |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT id FROM mf WHERE gender='F';
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 3 | Using where; Using index |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT id FROM mf WHERE gender='M';
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 37 | Using where; Using index |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)
mysql>
TESTE MyISAM
mysql> USE test
Database changed
mysql> DROP TABLE IF EXISTS mf;
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE TABLE mf
-> (
-> id int not null auto_increment,
-> gender char(1),
-> primary key (id),
-> key (gender)
-> ) ENGINE=MyISAM;
Query OK, 0 rows affected (0.05 sec)
mysql> INSERT INTO mf (gender) VALUES
-> ('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
-> ('M'),('M'),('M'),('M'),('F'),('F'),('M'),('M'),
-> ('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
-> ('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
-> ('F'),('M'),('M'),('M'),('M'),('M'),('M'),('M');
Query OK, 40 rows affected (0.00 sec)
Records: 40 Duplicates: 0 Warnings: 0
mysql> ANALYZE TABLE mf;
+---------+---------+----------+----------+
| Table | Op | Msg_type | Msg_text |
+---------+---------+----------+----------+
| test.mf | analyze | status | OK |
+---------+---------+----------+----------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT gender FROM mf WHERE gender='F';
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 3 | Using where; Using index |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT gender FROM mf WHERE gender='M';
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 36 | Using where; Using index |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT id FROM mf WHERE gender='F';
+----+-------------+-------+------+---------------+--------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+-------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 3 | Using where |
+----+-------------+-------+------+---------------+--------+---------+-------+------+-------------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT id FROM mf WHERE gender='M';
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| 1 | SIMPLE | mf | ALL | gender | NULL | NULL | NULL | 40 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)
mysql>
Análise para InnoDB
Quando os dados foram carregados como InnoDB, observe que todos os quatro EXPLAIN
planos usaram o gender
índice. O terceiro e o quarto EXPLAIN
planos usavam o gender
índice, apesar dos dados solicitados id
. Por quê? Porque id
está no PRIMARY KEY
e todos os índices secundários têm ponteiros de referência de volta para PRIMARY KEY
(via gen_clust_index ).
Análise para MyISAM
Quando os dados foram carregados como MyISAM, observe que os três primeiros EXPLAIN
planos usaram o gender
índice. No quarto EXPLAIN
plano, o Query Optimizer decidiu não usar um índice. Optou por uma verificação completa da tabela. Por quê?
Independentemente do DBMS, o Query Optimizers opera com uma regra prática muito simples: se um índice estiver sendo exibido como candidato a ser usado para realizar a pesquisa, o Query Optimizer calcula que ele deve pesquisar mais de 5% do número total de linhas na tabela:
- uma verificação completa do índice é feita se todas as colunas necessárias para recuperação estiverem no índice selecionado
- uma verificação completa da tabela, caso contrário
CONCLUSÃO
Se você não possui índices de cobertura adequados ou se a população-chave de uma determinada tupla é superior a 5% da tabela, seis coisas devem acontecer:
- Venha para a conclusão de que você deve criar um perfil das consultas
- Localizar todos
WHERE
, GROUP BY
ea ordem BY` cláusulas dessas consultas
- Formule índices nesta ordem
WHERE
colunas de cláusula com valores estáticos
GROUP BY
colunas
ORDER BY
colunas
- Evitar verificações completas da tabela (consultas sem uma
WHERE
cláusula sensata )
- Evite populações de chaves ruins (ou pelo menos armazene em cache essas populações de chaves ruins)
- Decida o melhor mecanismo de armazenamento MySQL ( InnoDB ou MyISAM ) para as tabelas
Eu escrevi sobre essa regra prática de 5% no passado:
UPDATE 2012-11-14 13:05 EDT
Analisamos sua pergunta e a postagem original do SO . Então, pensei sobre o Analysis for InnoDB
que mencionei antes. Isso coincide com a person
mesa. Por quê?
Para ambas as tabelas mf
eperson
- O mecanismo de armazenamento é o InnoDB
- Chave primária é
id
- O acesso à tabela é por índice secundário
- Se a tabela fosse MyISAM, veríamos um
EXPLAIN
plano completamente diferente
Agora, olhe para a consulta da questão SO: select * from person order by age\G
. Como não há WHERE
cláusula, você exigiu explicitamente uma verificação completa da tabela . A ordem de classificação padrão da tabela seria por id
(PRIMARY KEY) devido ao seu auto_increment e ao gen_clust_index (também conhecido como Índice de Cluster) é ordenado pelo rowid interno . Quando você solicitou o índice, lembre-se de que os índices secundários do InnoDB têm o ID da linha anexado a cada entrada do índice. Isso produz a necessidade interna de acesso de linha completa a cada vez.
Configurando ORDER BY
uma tabela do InnoDB pode ser uma tarefa bastante assustadora se você ignorar esses fatos sobre como os índices do InnoDB são organizados.
Voltando à consulta SO, desde que você exigiu explicitamente uma verificação completa da tabela , IMHO, o MySQL Query Optimizer fez a coisa correta (ou pelo menos escolheu o caminho de menor resistência). Quando se trata do InnoDB e da consulta SO, é muito mais fácil executar uma varredura completa da tabela e, em seguida, algumas, em filesort
vez de fazer uma varredura completa do índice e uma pesquisa de linha através do gen_clust_index para cada entrada secundária do índice.
Eu não sou um defensor do uso de dicas de índice, porque ignora o plano EXPLAIN. Não obstante, se você realmente conhece seus dados melhor que o InnoDB, precisará recorrer às Dicas de índice, especialmente com consultas que não têmWHERE
cláusula.
UPDATE 2012-11-14 14:21 EDT
De acordo com o livro Entendendo o MySQL Internals
O parágrafo 7 diz o seguinte:
Os dados são armazenados em uma estrutura especial chamada índice clusterizado , que é uma árvore B com a chave primária atuando como valor da chave e o registro real (em vez de um ponteiro) na parte dos dados. Assim, cada tabela do InnoDB deve ter uma chave primária. Se um não for fornecido, uma coluna de ID de linha especial normalmente não visível para o usuário será adicionada para atuar como uma chave primária. Uma chave secundária armazenará o valor da chave primária que identifica o registro. O código da árvore B pode ser encontrado em innobase / btr / btr0btr.c .
É por isso que afirmei anteriormente: é muito mais fácil executar uma varredura completa da tabela e, em seguida, alguns arquivos, em vez de fazer uma varredura completa do índice e uma pesquisa de linha através do gen_clust_index para cada entrada secundária do índice . O InnoDB fará uma pesquisa de índice duplo toda vez . Isso parece meio brutal, mas esses são apenas os fatos. Novamente, leve em consideração a falta de WHERE
cláusula. Essa, por si só, é a dica para o MySQL Query Optimizer para fazer uma varredura completa da tabela.
FOR ORDER BY
(que é o caso específico desta pergunta). A pergunta afirmava que, nesse caso, o mecanismo de armazenamento eraInnoDB
(e a pergunta SO original mostra que as 10 mil linhas são distribuídas de maneira bastante uniforme em 8 itens, a cardinalidade também não deve ser um problema aqui). Infelizmente, não acho que isso responda à pergunta.