É 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.filesort
seleção foi decidida pelo Query Optimizer por um motivo simples: falta conhecimento prévio dos dados que você possui. Se a sua escolha de usar dicas de índice (com base no problema nº 2) lhe proporcionar um tempo de execução satisfatório, faça o que for necessário. A resposta que forneci foi apenas um exercício acadêmico para mostrar como o MySQL Query Optimizer pode ser temperamental, além de sugerir cursos de ação.Adaptado (com permissão) da resposta de Denis para outra pergunta no SO:
Como todos os registros (ou quase todos) serão buscados pela consulta, você geralmente fica melhor sem nenhum índice. A razão para isso é que, na verdade, custa algo para ler um índice.
Ao percorrer a tabela inteira, a leitura seqüencial da tabela e a classificação de suas linhas na memória pode ser o seu plano mais barato. Se você precisar apenas de algumas linhas e a maioria corresponder à cláusula where, buscar o menor índice fará o truque.
Para entender o porquê, imagine a E / S do disco envolvida.
Suponha que você queira a tabela inteira sem um índice. Para fazer isso, você lê dados_página1, dados_página2, dados_página3, etc., visitando as várias páginas do disco envolvidas em ordem, até chegar ao final da tabela. Você então classifica e retorna.
Se você deseja as 5 principais linhas sem um índice, leia sequencialmente a tabela inteira como antes, enquanto classifica em heap as 5 principais linhas. É certo que é muita leitura e classificação para algumas linhas.
Suponha, agora, que você queira a tabela inteira com um índice. Para fazer isso, você lê index_page1, index_page2, etc., sequencialmente. Isso leva você a visitar, digamos, data_page3, data_page1, data_page3 novamente, data_page2, etc., em uma ordem completamente aleatória (aquela pela qual as linhas classificadas aparecem nos dados). O IO envolvido torna mais barato apenas ler toda a bagunça sequencialmente e classificar a sacola na memória.
Se você deseja apenas as 5 principais linhas de uma tabela indexada, pelo contrário, o uso do índice se torna a estratégia correta. Na pior das hipóteses, você carrega 5 páginas de dados na memória e segue em frente.
Um bom planejador de consultas SQL, btw, tomará a decisão de usar ou não um índice com base na fragmentação dos seus dados. Se buscar linhas em ordem significa aumentar e retroceder a tabela, um bom planejador pode decidir que não vale a pena usar o índice. Por outro lado, se a tabela estiver agrupada em cluster usando o mesmo índice, é garantido que as linhas estejam em ordem, aumentando a probabilidade de utilização.
Porém, se você juntar a mesma consulta a outra tabela e essa outra tabela tiver uma cláusula where extremamente seletiva que pode usar um índice pequeno, o planejador poderá decidir que é realmente melhor, por exemplo, buscar todos os IDs de linhas marcadas como
foo
hash junte-se às tabelas e monte-as na memória.fonte