Há um monte de problemas de desempenho aqui se você precisar fazer isso milhões de vezes.
-
Você está preparando a mesma instrução SQL repetidamente, milhões de vezes. Seria melhor prepará-lo uma vez e executá-lo milhões de vezes.
-
Você está se desconectando do banco de dados em cada chamada de função após uma única consulta. Isso significa que você precisa se reconectar a cada vez e todas as informações armazenadas em cache são descartadas. Não faça isso, deixe-o conectado.
-
Você está cometendo após cada linha. Isso vai desacelerar as coisas. Em vez disso, confirme depois de fazer um lote.
-
O select + update ou insert provavelmente pode ser feito como um único upsert.
-
O fato de você estar inserindo tanto em uma tabela temporária provavelmente é um problema de desempenho.
-
Se a tabela tiver muitos índices que podem retardar as inserções. Às vezes, é melhor descartar índices, fazer uma grande atualização em lote e recriá-los.
-
Como você está colocando valores diretamente em seu SQL, seu SQL está aberto a um ataque de injeção de SQL .
Em vez de...
- Use instruções preparadas e parâmetros de vinculação
- Deixe o banco de dados conectado
- Faça atualizações em massa
- Somente confirme no final de uma série de atualizações
- Faça todas as contas no
UPDATE
em vez deSELECT + math + UPDATE
. - Use um "UPSERT" em vez de
SELECT
entãoUPDATE
ouINSERT
Primeiro, declarações preparadas. Isso permite que o MySQL compile a instrução uma vez e a reutilize. A ideia é você escrever uma declaração com espaços reservados para os valores.
select id, position, impressions, clicks, ctr
from temp
where profile_id=%s and
keyword=%s and
landing_page=%s
Então você executa isso com os valores como argumentos, não como parte da string.
self.cursor.execute(
'select id, position, impressions, clicks, ctr from temp where profile_id=%s and keyword=%s and landing_page=%s',
(profile_id, keyword, landing_page)
)
Isso permite que o banco de dados armazene em cache a instrução preparada e não precise recompilá-la a cada vez. Também evita um ataque de injeção de SQL em que um invasor inteligente pode criar um valor que é realmente mais SQL como
" MORE SQL HERE "
. É uma falha de segurança muito, muito comum. Observe que pode ser necessário usar o o próprio MySQL Biblioteca de banco de dados Python para obter declarações preparadas verdadeiras . Não se preocupe muito com isso, usar declarações preparadas não é seu maior problema de desempenho.
Em seguida, o que você está fazendo basicamente é adicionar a uma linha existente ou, se não houver uma linha existente, inserir uma nova. Isso pode ser feito de forma mais eficiente em uma única instrução com um
UPSERT
, um INSERT
combinado e UPDATE
. O MySQL tem como INSERT ... ON DUPLICATE KEY UPDATE
. Para ver como isso é feito, podemos escrever seu
SELECT then UPDATE
como um único UPDATE
. Os cálculos são feitos no SQL. update temp
set impressions = impressions + %s,
clicks = clicks + %s,
ctr = (ctr + %s / 2)
where profile_id=%s and
keyword=%s and
landing_page=%s
Seu INSERT continua o mesmo...
insert into temp
(profile_id, landing_page, keyword, position, impressions, clicks, ctr)
values (%s, %s, %s, %s, %s, %s, %s)
Combine-os em um INSERT ON DUPLICATE KEY UPDATE.
insert into temp
(profile_id, landing_page, keyword, position, impressions, clicks, ctr)
values (%s, %s, %s, %s, %s, %s, %s)
on duplicate key update
update temp
set impressions = impressions + %s,
clicks = clicks + %s,
ctr = (ctr + %s / 2)
Isso depende de como as chaves da tabela são definidas. Se você tiver
unique( profile_id, landing_page, keyword )
então deve funcionar da mesma forma que o seu código. Mesmo que você não possa fazer o upsert, você pode eliminar o
SELECT
tentando o UPDATE
, verificando se atualizou alguma coisa e se não fez um INSERT
. Faça as atualizações em massa. Em vez de chamar uma sub-rotina que faz uma atualização e confirma, passe uma grande lista de coisas a serem atualizadas e trabalhe nelas em um loop. Você pode até aproveitar
executemany
para executar a mesma instrução com vários valores. Então se comprometa. Você pode fazer o
UPSERT
a granel. INSERT
pode ter várias linhas ao mesmo tempo. Por exemplo, isso insere três linhas. insert into whatever
(foo, bar, baz)
values (1, 2, 3),
(4, 5, 6),
(7, 8, 9)
Você provavelmente pode fazer o mesmo com seu
INSERT ON DUPLICATE KEY UPDATE
reduzindo a quantidade de sobrecarga para falar com o banco de dados. Veja este post para um exemplo
(em PHP, mas você deve ser capaz de adaptar). Isso sacrifica o retorno do ID da última linha inserida, mas são as quebras.