Mysql
 sql >> Database >  >> RDS >> Mysql

Accelera la dichiarazione di aggiornamento/inserimento di MySQL

Ci sono un sacco di problemi di prestazioni qui se devi farlo milioni di volte.

  • Stai preparando la stessa istruzione SQL più e più volte, milioni di volte. Sarebbe meglio prepararlo una volta ed eseguirlo milioni di volte.

  • Ti stai disconnettendo dal database ad ogni chiamata di funzione dopo una singola query. Ciò significa che è necessario riconnettersi ogni volta e tutte le informazioni memorizzate nella cache vengono eliminate. Non farlo, lascialo connesso.

  • Ti stai impegnando dopo ogni riga. Questo rallenterà le cose. Invece, esegui il commit dopo aver eseguito un batch.

  • La selezione + aggiornamento o inserimento può probabilmente essere eseguita come un unico upsert.

  • Il fatto che tu stia inserendo così tanto in una tabella temporanea è probabilmente un problema di prestazioni.

  • Se la tabella ha troppi indici che possono rallentare gli inserimenti. A volte è meglio eliminare gli indici, eseguire un aggiornamento batch di grandi dimensioni e ricrearli.

  • Poiché stai inserendo valori direttamente nel tuo SQL, il tuo SQL è aperto a un attacco SQL injection .

Invece...

  • Utilizza istruzioni preparate e associa parametri
  • Lascia il database connesso
  • Esegui aggiornamenti in blocco
  • Effettuare il commit solo al termine di un'esecuzione di aggiornamenti
  • Fai tutti i calcoli nel UPDATE piuttosto che SELECT + math + UPDATE .
  • Usa un "UPSERT" invece di SELECT quindi UPDATE o INSERT

Prima di tutto, dichiarazioni preparate. Questi consentono a MySQL di compilare l'istruzione una volta e quindi di riutilizzarla. L'idea è di scrivere una dichiarazione con segnaposto per i valori.

select id, position, impressions, clicks, ctr
from temp
where profile_id=%s and
      keyword=%s and 
      landing_page=%s

Quindi lo esegui con i valori come argomenti, non come parte della stringa.

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)
)

Ciò consente al database di memorizzare nella cache l'istruzione preparata e di non doverla ricompilare ogni volta. Evita anche un attacco SQL injection in cui un utente malintenzionato può creare un valore che in realtà è più SQL come " MORE SQL HERE " . È una falla di sicurezza molto, molto, molto comune.

Nota, potresti dover utilizzare MySQL's own Libreria di database Python per ottenere vere istruzioni preparate . Non preoccuparti troppo, l'utilizzo di istruzioni preparate non è il tuo più grande problema di prestazioni.

Successivamente, ciò che stai facendo sostanzialmente è aggiungere a una riga esistente o, se non esiste una riga esistente, inserirne una nuova. Questo può essere fatto in modo più efficiente in una singola istruzione con un UPSERT , un INSERT combinato e UPDATE . MySQL ce l'ha come INSERT ... ON DUPLICATE KEY UPDATE .

Per vedere come è fatto, possiamo scrivere il tuo SELECT then UPDATE come un unico UPDATE . I calcoli vengono eseguiti in 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

Il tuo INSERT rimane lo stesso...

    insert into temp
        (profile_id, landing_page, keyword, position, impressions, clicks, ctr)
        values (%s, %s, %s, %s, %s, %s, %s)

Combinali in un INSERTO SU AGGIORNAMENTO CHIAVE DUPLICATA.

    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)

Questo dipende da come sono definite le chiavi della tabella. Se hai unique( profile_id, landing_page, keyword ) quindi dovrebbe funzionare come il tuo codice.

Anche se non puoi eseguire l'upsert, puoi eliminare il SELECT provando l'UPDATE , controllando se ha aggiornato qualcosa e se non ha eseguito un INSERT .

Esegui gli aggiornamenti in blocco. Invece di chiamare una subroutine che esegue un aggiornamento e si impegna, passale un grande elenco di cose da aggiornare e lavoraci su in un ciclo. Puoi anche sfruttare executemany per eseguire la stessa istruzione con più valori. Quindi impegna.

Potresti essere in grado di eseguire UPSERT all'ingrosso. INSERT può prendere più righe contemporaneamente. Ad esempio, questo inserisce tre righe.

insert into whatever
    (foo, bar, baz)
values (1, 2, 3),
       (4, 5, 6), 
       (7, 8, 9)

Probabilmente puoi fare lo stesso con il tuo INSERT ON DUPLICATE KEY UPDATE riducendo la quantità di sovraccarico per parlare con il database. Vedi questo post per un esempio (in PHP, ma dovresti essere in grado di adattarti).

Questo sacrifica la restituzione dell'ID dell'ultima riga inserita, ma sono le interruzioni.