Eu corro um EXPLAIN
:
mysql> explain select last_name from employees order by last_name;
+----+-------------+-----------+------+---------------+------+---------+------+-------+----------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-----------+------+---------------+------+---------+------+-------+----------------+
| 1 | SIMPLE | employees | ALL | NULL | NULL | NULL | NULL | 10031 | Using filesort |
+----+-------------+-----------+------+---------------+------+---------+------+-------+----------------+
1 row in set (0.00 sec)
Os índices na minha tabela:
mysql> show index from employees;
+-----------+------------+---------------+--------------+---------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-----------+------------+---------------+--------------+---------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| employees | 0 | PRIMARY | 1 | subsidiary_id | A | 6 | NULL | NULL | | BTREE | | |
| employees | 0 | PRIMARY | 2 | employee_id | A | 10031 | NULL | NULL | | BTREE | | |
| employees | 1 | idx_last_name | 1 | last_name | A | 10031 | 700 | NULL | | BTREE | | |
| employees | 1 | date_of_birth | 1 | date_of_birth | A | 10031 | NULL | NULL | YES | BTREE | | |
| employees | 1 | date_of_birth | 2 | subsidiary_id | A | 10031 | NULL | NULL | | BTREE | | |
+-----------+------------+---------------+--------------+---------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
5 rows in set (0.02 sec)
Há um índice em last_name, mas o otimizador não o utiliza.
Então eu faço:
mysql> explain select last_name from employees force index(idx_last_name) order by last_name;
+----+-------------+-----------+------+---------------+------+---------+------+-------+----------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-----------+------+---------------+------+---------+------+-------+----------------+
| 1 | SIMPLE | employees | ALL | NULL | NULL | NULL | NULL | 10031 | Using filesort |
+----+-------------+-----------+------+---------------+------+---------+------+-------+----------------+
1 row in set (0.00 sec)
Mas ainda assim o índice não é usado! O que eu estou fazendo errado aqui?
Isso tem a ver com o fato de que o índice é NON_UNIQUE
? Entre o last_name éVARCHAR(1000)
Atualização solicitada por @RolandoMySQLDBA
mysql> SELECT COUNT(DISTINCT last_name) DistinctCount FROM employees;
+---------------+
| DistinctCount |
+---------------+
| 10000 |
+---------------+
1 row in set (0.05 sec)
mysql> SELECT COUNT(1) FROM (SELECT COUNT(1) Count500,last_name FROM employees GROUP BY last_name HAVING COUNT(1) > 500) A;
+----------+
| COUNT(1) |
+----------+
| 0 |
+----------+
1 row in set (0.15 sec)
SELECT COUNT(DISTINCT last_name) DistinctCount FROM employees;
2)SELECT COUNT(1) FROM (SELECT COUNT(1) Count500,last_name FROM employees GROUP BY last_name HAVING COUNT(1) > 500) A;
. Qual é o resultado de cada contagem?SELECT COUNT(1) FullTableCount FROM employees;
e 2)SELECT * FROM (SELECT COUNT(1) Count500,last_name FROM employees GROUP BY last_name HAVING COUNT(1) > 500) A LIMIT 10;
.Respostas:
PROBLEMA # 1
Olhe para a consulta
Não vejo uma cláusula WHERE significativa, nem o MySQL Query Optimizer. Não há incentivo para usar um índice.
PROBLEMA # 2
Olhe para a consulta
Você forneceu um índice, mas o Query Opitmizer assumiu. Eu já vi esse comportamento antes ( como forçar um JOIN a usar um índice específico no MySQL? )
Por que isso deveria acontecer?
Sem uma
WHERE
cláusula, o Query Optimizer diz o seguinte para si mesmo:WHERE
cláusula?O Query Optimizer escolheu o caminho de menor resistência.
Você ficará um pouco chocado, mas aqui está: Você sabia que o Query Optimizer manipulará o MyISAM de maneira bastante diferente?
Você provavelmente está dizendo HUH ???? COMO ????
O MyISAM armazena os dados em um
.MYD
arquivo e todos os índices no.MYI
arquivo.A mesma consulta produzirá um plano EXPLAIN diferente porque o índice reside em um arquivo diferente dos dados. Por quê ? Aqui está o porquê:
last_name
coluna) já estão ordenados no.MYI
last_name
do índiceComo pode ter tanta certeza disso? Eu testei essa teoria de trabalho sobre como o uso de um armazenamento diferente irá gerar um plano EXPLAIN diferente (às vezes um melhor): Um índice deve cobrir todas as colunas selecionadas para ser usado no ORDER BY?
fonte
Na verdade, o problema aqui é que isso parece um índice de prefixo. Não vejo a definição da tabela na pergunta, mas
sub_part
= 700? Você não indexou a coluna inteira, portanto, o índice não pode ser usado para classificação e também não é útil como índice de cobertura. Só poderia ser usado para encontrar as linhas que "podem" corresponder aWHERE
e a camada do servidor (acima do mecanismo de armazenamento) precisaria filtrar ainda mais as linhas correspondentes. Você realmente precisa de 1000 caracteres para um sobrenome?atualização para ilustrar: Eu tenho uma tabela de teste de tabela com um pouco mais de 500 linhas, cada uma com o nome de domínio de um site em uma coluna
domain_name VARCHAR(254) NOT NULL
e sem índices.Com a coluna completa indexada, a consulta usa o índice:
Então, agora, vou largar esse índice e apenas indexar os 200 primeiros caracteres de domain_name.
Voila.
Observe também que o índice, com 200 caracteres, é maior que o valor mais longo da coluna ...
... mas isso não faz nenhuma diferença. Um índice declarado com um comprimento de prefixo só pode ser usado para pesquisas, não para classificação e não como um índice de cobertura, pois não contém o valor completo da coluna, por definição.
Além disso, as consultas acima foram executadas em uma tabela InnoDB, mas executá-las em uma tabela MyISAM produz resultados praticamente idênticos. A única diferença nesse caso é que a contagem de InnoDB para
rows
está um pouco desligada (541) enquanto o MyISAM mostra o número exato de linhas (563), o que é um comportamento normal, pois os dois mecanismos de armazenamento manipulam mergulhos de índice de maneira muito diferente.Eu ainda afirmaria que a coluna last_name provavelmente é maior que o necessário, mas ainda é possível indexar a coluna inteira, se você estiver usando o InnoDB e executando o MySQL 5.5 ou 5.6:
fonte
varchar(1000)
, mas isto é para além do máximo permitido para o índice que é ~ 750EXPLAIN SELECT ...
, bem comoSHOW CREATE TABLE ...
eSELECT @@VERSION;
desde alterações ao otimizador entre as versões podem ser relevantes.Eu respondi porque um comentário não suporta a formatação e o RolandoMySQL DBA falou sobre gen_clust_index e innodb. E isso é muito importante em uma tabela baseada em innodb. Isso vai além do conhecimento normal do DBA, porque você precisa poder analisar o código C.
SEMPRE SEMPRE crie uma CHAVE PRIMÁRIA ou ÚNICA se estiver usando o Innodb. Se você não usar, o innodb usará seu próprio ROW_ID gerado, o que pode causar mais mal do que bem.
Vou tentar explicar fácil, porque a prova é baseada no código C.
Primeiro problema
mutex_enter (& (dict_sys-> mutex));
Essa linha garante que apenas um encadeamento possa acessar dict_sys-> mutex ao mesmo tempo. E se o valor já tiver sido mutexado ... sim, um encadeamento tem que esperar para que você obtenha algo como um recurso aleatório agradável como bloqueio de encadeamento ou se você tiver mais tabelas sem sua própria PRIMARY KEY ou UNIQUE KEY, então você teria um recurso interessante com innodb ' table locking ' não é este o motivo pelo qual o MyISAM foi substituído pelo InnoDB, devido ao recurso interessante chamado bloqueio baseado em registro / linha.
Segundo problema
(0 == (id% DICT_HDR_ROW_ID_WRITE_MARGIN))
os cálculos do módulo (%) são lentos, não são bons se você estiver inserindo em lote porque precisa ser recalculado toda vez ... e porque DICT_HDR_ROW_ID_WRITE_MARGIN (valor 256) é uma potência de dois, isso pode ser feito muito mais rápido.
(0 == (id & (DICT_HDR_ROW_ID_WRITE_MARGIN - 1))))
Nota lateral: se o compilador C foi configurado para otimizar e é um bom otimizador, o otimizador C corrigirá o código "pesado" na versão mais leve
o lema da história sempre crie sua própria PRIMARY KEY ou verifique se você possui um índice ÚNICO ao criar uma tabela desde o início
fonte
UNIQUE
seja suficiente - ele também precisa incluir apenas colunas que não sejam NULL para que o índice exclusivo seja promovido ao PK.INSERT
gasto em uma função nesta função. Eu suspeito que é insignificante. Contrastar o esforço para colunas pá em volta, fazer operações BTREE, incluindo um bloco-split ocasional, vários semáforos na buffer_pool, coisas mudança tampão, etc.