Tenho uma tabela com cerca de 17 milhões de linhas:
mysql> describe humans_we_respect;
+---------------------+-------------------------------------------------------------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------------------+-------------------------------------------------------------------------+------+-----+---------+-------+
| id | bigint(20) | NO | PRI | NULL | |
| name | varchar(63) | YES | | NULL | |
| address | varchar(127) | YES | | NULL | |
| city | varchar(63) | YES | | NULL | |
| state | varchar(3) | YES | MUL | NULL | |
| zip | varchar(15) | YES | | NULL | |
| country | varchar(15) | YES | | NULL | |
| email | varchar(127) | YES | | NULL | |
| website | varchar(127) | YES | | NULL | |
| area_code_state | varchar(3) | YES | MUL | NULL | |
| timezone | set('other','pacific','mountain','central','eastern','alaska','hawaii') | YES | | other | |
+---------------------+-------------------------------------------------------------------------+------+-----+---------+-------+
12 rows in set (0.01 sec)
Devido à estrita natureza de apenas contactar quem manifestou interesse numa newsletter, e à estrita natureza de nunca contactar alguém que pediu para não ser contactado, antes de um mailing adicionei um campo para o expressed_interest (tinyint) deafult null
qual mudo 1
para quem manifestou interesse, e depois mude para null
para aqueles que pediram para não serem contatados.
A consulta a seguir, na qual 10.000 linhas são atualizadas por consulta, leva muito tempo para ser executada (eliminada após meia hora):
UPDATE humans_we_respect SET expressed_interest=1 WHERE id IN (1,...,10000);
No entanto, a seguinte consulta é concluída em segundos:
INSERT INTO humans_we_respect (id) VALUES (1),...,(10000) ON DUPLICATE KEY UPDATE expressed_interest=1;
Em que condições será ON DUPLICATE KEY UPDATE
mais rápido do que UPDATE
? Eu gostaria de saber isso para uso futuro com tabelas grandes como esta.
Isso está no MySQL 5.5.33 em execução no Amazon RDS .
Eu sei que não é fácil obter um plano de execução para uma atualização do MySQL, pois ele fornece apenas as
SELECT
instruções on. Mas a pista pode estar na ordem em que os registros são atualizados, na avaliação de umWHERE
que contém umIN
com grande quantidade de dados estáticos , bem como na quantidade de leituras e gravações conectadas, cache intermediário, associado a isso.A declaração
é um tipo de instrução que tentamos evitar ao atualizar bancos de dados maiores, pois o analisador parece enlouquecer com eles de tempos em tempos.
IN ( a,b,c,...,ZZZZ )
para mim, tornou-se um estilo de codificação adequado apenas para números de itens muito pequenos nosIN
dados. Estou trabalhando em um projeto de código aberto onde frequentemente me deparo com o que chamo de "junção de mente remota", a segunda metade geralmente se parece exatamente com o seu problema.Enquanto a primeira parte geralmente é executada na velocidade da luz, a segunda parte leva uma eternidade, que é o que você descreve também. Essas consultas geralmente podem ser aceleradas EXTREMAMENTE reescrevendo-as como:
Nós também usamos
que teve um desempenho melhor que o original, mas não tão bom quanto minha versão sugerida.
Isso tudo pressupõe que você use índices adequados com primário no ID e índices combinados de várias colunas, onde várias colunas são frequentemente usadas juntas ou têm um bom significado juntas e geralmente estão presentes em suas consultas.
A pista é que grandes quantidades de valores estáticos em
IN
cláusulas aumentam o tempo de execução quase exponencialmente em consultas com muitos registros correspondentes, uma vez que basicamente NÃO USAM NENHUM ÍNDICE OU OTIMIZAÇÃO e geralmente terminam em varreduras completas de tabelas, nas quais IMHO a execução examinará cada registro/linha em sua tabela comparando-o com cada item em suaIN()
lista UM por UM.A declaração como
porém está usando o índice para localizar o registro e depois atualiza-lo, mesmo assim não destinado a este uso, ele rodará muito melhor devido ao uso de um índice no ID se houver e só fará uma pesquisa de índice para o registro em vez de milhares de comparações! Trabalhar diretamente com duas tabelas pode, no entanto, ser mais rápido se a lista de IDs for derivada de outra tabela no mesmo servidor, há mais otimizações que podem ser usadas e você não precisa transferir dados de e para o processo do servidor mysql.
Assim como algumas informações extras:
é uma boa técnica para otimizar
IN()
consultas com contagens muito baixas de elementos, pois criará uma consulta de índice paralela para cada elemento, o que é ótimo para os primeiros elementos, mas diminuirá muito o desempenho com mais elementos e, em algum momento, atingirá o limite do analisador para otimização (IMHO, pode haver 255 elementos em uma consulta), ponto em que ele voltará a um ritmo lento ...Apenas um palpite:
IN
cláusula.Se for esse o caso, você pode tentar reescrever a primeira consulta para
UPDATE... WHERE id <= 10000
, mas isso só funcionará se você realmente precisar atualizar 10.000 linhas com ids consecutivos. Talvez você também possa tentarUPDATE ... WHERE id = 1 OR id = 2 OR ...
. Mas isso só seria rápido, se o mysql pudesse otimizar isso internamente.