Mysql
 sql >> Base de données >  >> RDS >> Mysql

Accélérez la mise à jour MySQL/insérez une déclaration

Il y a un tas de problèmes de performances ici si vous devez le faire des millions de fois.

  • Vous préparez la même instruction SQL encore et encore, des millions de fois. Il serait préférable de le préparer une fois et de l'exécuter des millions de fois.

  • Vous vous déconnectez de la base de données à chaque appel de fonction après une seule requête. Cela signifie que vous devez vous reconnecter à chaque fois et que toutes les informations mises en cache sont supprimées. Ne faites pas ça, laissez-le connecté.

  • Vous vous engagez après chaque ligne. Cela ralentira les choses. Au lieu de cela, validez après avoir effectué un lot.

  • La sélection + mise à jour ou insertion peut probablement être effectuée en une seule mise à jour.

  • Le fait que vous insériez autant dans une table temporaire est probablement un problème de performances.

  • Si la table contient trop d'index, cela peut ralentir les insertions. Parfois, il est préférable de supprimer les index, de faire une grosse mise à jour par lots et de les recréer.

  • Parce que vous mettez des valeurs directement dans votre SQL, votre SQL est ouvert à une attaque par injection SQL .

Au lieu de cela...

  • Utiliser des instructions préparées et des paramètres de liaison
  • Laisser la base de données connectée
  • Effectuer des mises à jour groupées
  • Valider uniquement à la fin d'une série de mises à jour
  • Faire tous les calculs dans la UPDATE plutôt que SELECT + math + UPDATE .
  • Utilisez un "UPSERT" au lieu de SELECT puis UPDATE ou INSERT

Tout d'abord, des déclarations préparées. Ceux-ci permettent à MySQL de compiler l'instruction une fois, puis de la réutiliser. L'idée est que vous écriviez une déclaration avec des espaces réservés pour les valeurs.

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

Ensuite, vous exécutez cela avec les valeurs comme arguments, et non comme faisant partie de la chaîne.

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

Cela permet à la base de données de mettre en cache l'instruction préparée et de ne pas avoir à la recompiler à chaque fois. Cela évite également une attaque par injection SQL où un attaquant intelligent peut créer une valeur qui est en fait plus SQL comme " MORE SQL HERE " . C'est une faille de sécurité très, très, très courante.

Remarque, vous devrez peut-être utiliser le propre de MySQL Bibliothèque de base de données Python pour obtenir de véritables instructions préparées . Ne vous inquiétez pas trop, l'utilisation d'instructions préparées n'est pas votre plus gros problème de performances.

Ensuite, ce que vous faites essentiellement est d'ajouter à une ligne existante, ou s'il n'y a pas de ligne existante, d'en insérer une nouvelle. Cela peut être fait plus efficacement dans une seule instruction avec un UPSERT , un INSERT combiné et UPDATE . MySQL l'a comme INSERT ... ON DUPLICATE KEY UPDATE .

Pour voir comment cela se fait, nous pouvons écrire votre SELECT then UPDATE comme une seule UPDATE . Les calculs sont effectués dans le 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

Votre INSERT reste le même...

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

Combinez-les en un seul 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)

Cela dépend de la définition des clés de la table. Si vous avez unique( profile_id, landing_page, keyword ) alors cela devrait fonctionner de la même manière que votre code.

Même si vous ne pouvez pas faire l'upsert, vous pouvez éliminer le SELECT en essayant la UPDATE , en vérifiant s'il a mis à jour quelque chose et s'il n'a pas fait de INSERT .

Faites les mises à jour en masse. Au lieu d'appeler un sous-programme qui effectue une mise à jour et un commit, passez-lui une longue liste de choses à mettre à jour et travaillez dessus en boucle. Vous pouvez même profiter de executemany pour exécuter la même instruction avec plusieurs valeurs. Validez ensuite.

Vous pourrez peut-être faire le UPSERT en masse. INSERT peut prendre plusieurs lignes à la fois. Par exemple, cela insère trois lignes.

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

Vous pouvez probablement faire la même chose avec votre INSERT ON DUPLICATE KEY UPDATE réduisant la quantité de temps système pour parler à la base de données. Voir cet article pour un exemple (en PHP, mais il faut savoir s'adapter).

Cela sacrifie le retour de l'ID de la dernière ligne insérée, mais ce sont les pauses.