it-swarm.com.de

Warum ist das Einfügen von MySQL InnoDB so langsam?

Ich benutze große Zufallszahlen als Schlüssel (die von einem anderen System kommen). Einfügungen und Aktualisierungen in relativ kleinen Tabellen (wie in einigen Millionen Zeilen) dauern viel länger, als ich für sinnvoll halte.

Ich habe einen sehr einfachen Test zur Veranschaulichung destilliert. In der Testtabelle habe ich versucht, es so einfach wie möglich zu machen; Mein realer Code hat kein so einfaches Layout und hat Relationen und zusätzliche Indizes und so. Eine einfachere Konfiguration zeigt jedoch eine vergleichbare Leistung.

Hier sind die Ergebnisse:

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

Das Einfügen von 1 Million Zeilen in MyISAM dauert 6 Sekunden. in InnoDB dauert 433 Sekunden!

Was mache ich falsch? Was ist falsch konfiguriert? (MySQL ist eine normale Ubuntu-Installation mit Standardeinstellungen)

Hier ist der Testcode:

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 kommt mit 'zufälligen' Primärschlüsseln nicht gut zurecht. Versuchen Sie es mit einem sequenziellen Schlüssel oder einer automatischen Inkrementierung, und ich glaube, Sie werden eine bessere Leistung sehen. Ihr "echtes" Schlüsselfeld könnte noch indiziert sein, aber für eine Masseneinfügung ist es möglicherweise besser, diesen Index in einem Treffer nach der vollständigen Einfügung zu löschen und neu zu erstellen. Würde mich interessieren, eure Benchmarks dafür zu sehen!

Einige verwandte Fragen

42
Paul Dixon

InnoDB unterstützt Transaktionen, Sie verwenden keine expliziten Transaktionen, daher muss innoDB nach jeder Anweisung ein Commit ausführen ( "Führt für jede Einfügung einen Log-Flush auf die Festplatte aus" ).

Führen Sie diesen Befehl vor Ihrer Schleife aus:

START TRANSACTION

und das, nachdem Sie Schleife

COMMIT
61
flo

Ich musste eine insertlastige Anwendung in MyISAM und InnoDB gleichzeitig testen. Es gab eine einzige Einstellung, mit der die Geschwindigkeitsprobleme behoben wurden. Versuchen Sie Folgendes einzustellen:

innodb_flush_log_at_trx_commit = 2

Vergewissern Sie sich, dass Sie die Risiken verstanden haben, indem Sie die Einstellung hier lesen.

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

21
Philip Koshy

Ich erhalte sehr unterschiedliche Ergebnisse auf meinem System, aber dies verwendet nicht die Standardeinstellungen. Wahrscheinlich liegt ein Engpass bei der Größe der Innodb-Protokolldatei vor, die standardmäßig 5 MB beträgt. Bei innodb-log-file-size = 100M erhalte ich folgende Ergebnisse (alle Zahlen sind in Sekunden angegeben):

                             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

Erhöhung der innodb-log-file-size beschleunigt dies um einige Sekunden. Fallenlassen der Haltbarkeitsgarantien durch Einstellen von innodb-flush-log-at-trx-commit=2 oder 0 verbessert auch die Einfügungsnummern etwas.

6
Andrew

Der Standardwert für InnoDB ist eigentlich ziemlich schlecht. InnoDB ist sehr abhängig von RAM, Sie könnten ein besseres Ergebnis finden, wenn Sie die Einstellungen optimieren. Hier ist eine Anleitung, die ich verwendet habe InnoDB-Optimierung grundlegend

5
Kien Truong

Dies ist ein altes Thema, das aber häufig gesucht wird. Solange Sie sich des Risikos bewusst sind (wie oben von @philip Koshy angegeben), dass Sie festgeschriebene Transaktionen in der letzten Sekunde verlieren, können Sie diese globalen Parameter vor umfangreichen Aktualisierungen festlegen

innodb_flush_log_at_trx_commit=0
sync_binlog=0

schalten Sie sie dann nach Abschluss des Updates wieder ein (falls gewünscht).

innodb_flush_log_at_trx_commit=1
sync_binlog=1

für volle ACID-Konformität.

Es gibt einen großen Unterschied in der Schreib-/Aktualisierungsleistung, wenn beide deaktiviert und aktiviert sind. Nach meiner Erfahrung machen andere Dinge, die oben diskutiert wurden, einen Unterschied, aber nur einen geringen.

Eine andere Sache, die update/insert Stark beeinflusst, ist der Volltextindex. In einem Fall dauerte das Einfügen von 2-mil-Zeilen in eine Tabelle mit zwei Textfeldern mit Volltextindex 6 Stunden, und dieselbe dauerte nur 10 Minuten, nachdem der Volltextindex entfernt wurde. Mehr Indizes, mehr Zeit. Andere Suchindizes als der eindeutige Schlüssel und der Primärschlüssel werden möglicherweise vor umfangreichen Einfügungen/Aktualisierungen entfernt.

2
Allen King

Wie groß ist Ihr Innodb-Pufferpool? Stellen Sie sicher, dass Sie 75% Ihres Arbeitsspeichers eingestellt haben. Normalerweise sind Einfügungen in der Primärschlüsselreihenfolge für InnoDB besser. Aber mit einer großen Poolgröße sollten Sie gute Geschwindigkeiten sehen.

2
Ajay

dinge, die die Einsätze beschleunigen:

  • ich hatte alle Schlüssel von einer Tabelle entfernt, bevor große Einfügung in leere Tabelle
  • dann fand ich ein Problem, dass der Index nicht in den Speicher passte.
  • ich habe auch festgestellt, dass sync_binlog = 0 ist (sollte 1 sein), auch wenn binlog nicht verwendet wird.
  • ich habe auch festgestellt, dass ich innodb_buffer_pool_instances nicht gesetzt habe
1
Shimon Doodkin

Lösung

  1. Erstellen Sie einen neuen UNIQUE-Schlüssel, der mit Ihrem aktuellen PRIMARY-Schlüssel identisch ist
  2. Neue Spalte hinzufügen id ist eine Ganzzahl ohne Vorzeichen, auto_increment
  3. Erstellen Sie den Primärschlüssel in der neuen Spalte id

Bam, sofortige 10x + Insertverbesserung.

0