web-dev-qa-db-fra.com

Pourquoi MySQL InnoDB insère-t-il si lentement?

J'utilise de grands nombres aléatoires comme clés (provenant d'un autre système). Les insertions et les mises à jour sur des tables relativement petites (comme dans quelques millions de lignes) prennent beaucoup plus de temps que ce qui est raisonnable.

J'ai distillé un test très simple pour illustrer. Dans la table de test, j'ai essayé de le rendre aussi simple que possible. mon vrai code n'a pas une telle mise en page et a des relations et des index supplémentaires et autres. Cependant, une configuration plus simple montre des performances équivalentes.

Voici les résultats:

creating the MyISAM table took 0.000 seconds
creating 1024000 rows of test data took 1.243 seconds
inserting the test data took 6.335 seconds
selecting 1023742 rows of test data took 1.435 seconds
fetching 1023742 batches of test data took 0.037 seconds
dropping the table took 0.089 seconds
creating the InnoDB table took 0.276 seconds
creating 1024000 rows of test data took 1.165 seconds
inserting the test data took 3433.268 seconds
selecting 1023748 rows of test data took 4.220 seconds
fetching 1023748 batches of test data took 0.037 seconds
dropping the table took 0.288 seconds

L’insertion de rangées 1M dans MyISAM prend 6 secondes; dans InnoDB prend 433 secondes!

Qu'est-ce que je fais mal? Qu'est-ce qui est mal configuré? (MySQL est une installation normale d'Ubuntu avec des valeurs par défaut)

Voici le code de test:

import sys, time, random
import MySQLdb as db

# usage: python script db_username db_password database_name

db = db.connect(Host="127.0.0.1",port=3306,user=sys.argv[1],passwd=sys.argv[2],db=sys.argv[3]).cursor()

def test(engine):

    start = time.time() # fine for this purpose
    db.execute("""
CREATE TEMPORARY TABLE Testing123 (
k INTEGER PRIMARY KEY NOT NULL,
v VARCHAR(255) NOT NULL
) ENGINE=%s;"""%engine)
    duration = time.time()-start
    print "creating the %s table took %0.3f seconds"%(engine,duration)

    start = time.time()
    # 1 million rows in 100 chunks of 10K
    data = [[(str(random.getrandbits(48)) if a&1 else int(random.getrandbits(31))) for a in xrange(10*1024*2)] for b in xrange(100)]
    duration = time.time()-start
    print "creating %d rows of test data took %0.3f seconds"%(sum(len(rows)/2 for rows in data),duration)

    sql = "REPLACE INTO Testing123 (k,v) VALUES %s;"%("(%s,%s),"*(10*1024))[:-1]
    start = time.time()
    for rows in data:
        db.execute(sql,rows)
    duration = time.time()-start
    print "inserting the test data took %0.3f seconds"%duration

    # execute the query
    start = time.time()
    query = db.execute("SELECT k,v FROM Testing123;")
    duration = time.time()-start
    print "selecting %d rows of test data took %0.3f seconds"%(query,duration)

    # get the rows in chunks of 10K
    rows = 0
    start = time.time()
    while query:
        batch = min(query,10*1024)
        query -= batch
        rows += len(db.fetchmany(batch))
    duration = time.time()-start
    print "fetching %d batches of test data took %0.3f seconds"%(rows,duration)

    # drop the table
    start = time.time()
    db.execute("DROP TABLE Testing123;")
    duration = time.time()-start
    print "dropping the table took %0.3f seconds"%duration


test("MyISAM")
test("InnoDB")
55
Will

InnoDB ne gère pas bien les clés primaires "aléatoires". Essayez une clé séquentielle ou une incrémentation automatique, et je pense que vous obtiendrez de meilleures performances. Votre "véritable" champ de clé peut toujours être indexé, mais dans le cas d'une insertion en bloc, il vaudrait mieux supprimer et recréer cet index en un seul clic après l'insertion. Serait intéressé de voir vos points de repère pour cela!

Quelques questions connexes

42
Paul Dixon

InnoDB prend en charge les transactions; vous n’utilisez pas de transactions explicites, innoDB doit donc effectuer une validation après chaque instruction ( "effectue un vidage du journal sur le disque pour chaque insertion" ).

Exécutez cette commande avant votre boucle:

START TRANSACTION

et cela après avoir bouclé

COMMIT
61
flo

J'ai eu besoin de tester simultanément une application comportant beaucoup d'inserts dans MyISAM et InnoDB. Un paramètre unique résolvait les problèmes de vitesse que je rencontrais. Essayez de définir les paramètres suivants:

innodb_flush_log_at_trx_commit = 2

Assurez-vous de bien comprendre les risques en lisant à propos du réglage ici .

Voir aussi https://dba.stackexchange.com/questions/12611/is-it-safe-to-use-innodb-flush-log-at-trx-commit-2/12612 et https://dba.stackexchange.com/a/29974/9405

21
Philip Koshy

J'obtiens des résultats très différents sur mon système, mais cela n'utilise pas les valeurs par défaut. Vous êtes probablement engorgé sur innodb-log-file-size, qui est 5M par défaut. À innodb-log-file-size = 100M, les résultats sont les suivants (tous les chiffres sont en secondes):

                             MyISAM     InnoDB
create table                  0.001      0.276
create 1024000 rows           2.441      2.228
insert test data             13.717     21.577
select 1023751 rows           2.958      2.394
fetch 1023751 batches         0.043      0.038
drop table                    0.132      0.305

Augmenter le innodb-log-file-size accélérera cela de quelques secondes. Abandonner les garanties de durabilité en définissant innodb-flush-log-at-trx-commit=2 ou 0 améliorera également les numéros d'insertion.

6
Andrew

La valeur par défaut pour InnoDB est en fait assez mauvaise. InnoDB est très RAM dépendant, vous obtiendrez peut-être de meilleurs résultats si vous peaufinez les paramètres. Voici un guide que j'ai utilisé optimisation InnoDB de base

5
Kien Truong

C'est un sujet ancien, mais fréquemment recherché. Tant que vous êtes conscient des risques (comme indiqué par @philip Koshy ci-dessus) de perdre des transactions validées au cours de la dernière seconde environ, avant des mises à jour massives, vous pouvez définir ces paramètres globaux.

innodb_flush_log_at_trx_commit=0
sync_binlog=0

puis rallumez-le (si vous le souhaitez) une fois la mise à jour terminée.

innodb_flush_log_at_trx_commit=1
sync_binlog=1

pour une conformité totale à ACID.

Les performances d'écriture/de mise à jour varient énormément lorsque ces deux options sont activées et désactivées. D'après mon expérience, d'autres éléments discutés ci-dessus font une différence, mais seulement marginale.

Une autre chose qui impacte update/insert est grandement index plein texte. Dans un cas, une table avec deux champs de texte ayant un index de texte intégral, l'insertion de lignes de 2 millions de dollars prenait 6 heures et la même chose ne prenait que 10 minutes après la suppression de l'index de texte intégral. Plus d'index, plus de temps. Ainsi, les index de recherche autres que les clés unique et primaire peuvent être supprimés avant les insertions/mises à jour massives.

2
Allen King

Quelle est la taille de votre pool de mémoire tampon innodb? Assurez-vous que vous avez défini 75% de votre RAM. Habituellement, les inserts sont meilleurs lorsqu'ils sont en ordre de clé primaire pour InnoDB. Mais avec une grande piscine, vous devriez voir de bonnes vitesses.

2
Ajay

choses qui accélèrent les insertions:

  • j'avais enlevé toutes les clés d'une table avant une grande insertion dans une table vide
  • puis constaté que j’avais un problème avec le fait que l’index n’entre pas dans la mémoire.
  • j'ai aussi trouvé sync_binlog = 0 (devrait être 1) même si binlog n'est pas utilisé.
  • également constaté que je n'ai pas défini innodb_buffer_pool_instances
1
Shimon Doodkin

Solution

  1. Créez une nouvelle clé UNIQUE identique à votre clé PRIMARY actuelle
  2. Ajouter une nouvelle colonne id est un entier non signé, auto_increment
  3. Créer une clé primaire sur la nouvelle colonne id

Bam, amélioration immédiate de l'insertion 10x +.

0
William Entriken