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")
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
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
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
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.
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
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.
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.
choses qui accélèrent les insertions:
Solution
id
est un entier non signé, auto_incrementid
Bam, amélioration immédiate de l'insertion 10x +.