Mysql
 sql >> Base de Dados >  >> RDS >> Mysql

Acelere a instrução de atualização/inserção do MySQL


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 de SELECT + math + UPDATE .
  • Use um "UPSERT" em vez de SELECT então UPDATE ou INSERT

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.