Aqui está o que está acontecendo.
The SELECT COUNT (...) icd_index where icd='25000'
usará o índice, que é um BTree separado dos dados. Mas ele escaneia desta forma:
- Encontre a primeira entrada com icd='25000'. Isso é quase instantâneo.
- Faça a varredura para frente até encontrar uma alteração no icd. Isso fará a varredura apenas no índice, sem tocar nos dados. De acordo com o EXPLAIN, haverá cerca de 910.104 entradas de índice para verificar.
Agora vamos olhar para o BTree para esse índice. Com base nos campos no índice, cada linha terá exatamente 22 bytes, além de haver alguma sobrecarga (estimativa de 40%). Um bloco de índice MyISAM tem 1 KB (cf. 16 KB do InnoDB). Eu estimaria 33 linhas por bloco. 910.104/33 diz que cerca de 27K blocos precisam ser lidos para fazer o COUNT. (Observe
COUNT(core_id)
precisa verificar core_id
por ser nulo, COUNT(*)
não; esta é uma pequena diferença.) Ler 27K blocos em um disco rígido normal leva cerca de 270 segundos. Você teve sorte de fazê-lo em 60 segundos. A segunda execução encontrou todos esses blocos no key_buffer (assumindo que key_buffer_size tenha pelo menos 27 MB), então não precisou esperar pelo disco. Por isso foi muito mais rápido. (Isso ignorando o cache de consulta, que você teve a sabedoria de liberar ou usar SQL_NO_CACHE.)
5.6 passa a ser irrelevante (mas obrigado por mencioná-lo), já que esse processo não mudou desde 4.0 ou antes (exceto que utf8 não existia; mais sobre isso abaixo).
Mudar para o InnoDB ajudaria de duas maneiras. A PRIMARY KEY seria 'agrupada' com os dados, não armazenada como um BTree separado. Assim, uma vez que os dados ou o PK são armazenados em cache, o outro fica imediatamente disponível. O número de blocos seria mais como 5K, mas seriam blocos de 16KB. Eles podem ser carregados mais rapidamente se o cache estiver frio.
Você pergunta "Eu preciso de um índice apenas no icd?" -- Bem, isso reduziria o tamanho do MyISAM BTree para cerca de 21 bytes por linha, então o BTree teria cerca de 21/27 do tamanho, não muito melhor (pelo menos para o situação de cache frio).
Outro pensamento é, se
icd
é sempre numérico e sempre numérico, para usar MEDIUMINT UNSIGNED
, e adicione ZEROFILL
se pode ter zeros à esquerda. Ops, não notei o CHARACTER SET. (Eu consertei os números acima, mas deixe-me elaborar.)
- CHAR(5) permite 5 caracteres .
- ascii ocupa 1 byte por caractere .
- utf8 ocupa até 3 bytes por caracteres .
- Então, CHAR(5) CHARACTER SET utf8 leva 15 bytes sempre .
Alterando a coluna para
CHAR(5) CHARACTER SET ascii
reduziria para 5 bytes. Mudar para MEDIUMINT UNSIGNED ZEROFILL reduziria para 3 bytes.
A redução dos dados aceleraria a E/S em uma quantidade aproximadamente proporcional (depois de permitir outros 6 bytes para os outros dois campos.