Was ist schneller, InnoDB oder MyISAM?

54

Wie kann MyISAM "schneller" als InnoDB sein, wenn

  • MyISAM muss Festplattenlesevorgänge für die Daten durchführen?
  • InnoDB verwendet den Pufferpool für Indizes und Daten und MyISAM nur für den Index?
jcho360
quelle
MyISAM können die OS - Cache - Datenblöcken, so dass es nicht immer „tun Platte für die Daten liest“.
Rick James

Antworten:

68

Nur so kann MyISAM schneller sein als InnoDB unter diesen besonderen Umständen

MyISAM

Beim Lesen können die Indizes einer MyISAM-Tabelle einmal aus der .MYI-Datei gelesen und in den MyISAM-Schlüssel-Cache geladen werden (Größe nach key_buffer_size ). Wie können Sie die .MYD-Datei einer MyISAM-Tabelle schneller lesen lassen? Mit diesem:

ALTER TABLE mytable ROW_FORMAT=Fixed;

Ich habe darüber in meinen letzten Beiträgen geschrieben

InnoDB

OK, was ist mit InnoDB? Führt InnoDB Festplatten-E / A für Abfragen durch? Überraschenderweise tut es das ja !! Sie denken wahrscheinlich, ich bin verrückt danach, das zu sagen, aber es ist absolut wahr, auch für SELECT-Abfragen . An dieser Stelle wundern Sie sich wahrscheinlich: "Wie in aller Welt führt InnoDB Festplatten-E / A-Vorgänge für Abfragen durch?"

Alles geht zurück auf InnoDB als ACID- reklamierte Transactional Storage Engine. Um InnoDB Transactional zu sein, hat es die Unterstützung Iin ACID, die Isolation ist. Die Technik zur Aufrechterhaltung der Isolierung für Transaktionen erfolgt über MVCC, Multiversion Concurrency Control . In einfachen Worten, InnoDB zeichnet auf, wie Daten aussehen, bevor Transaktionen versuchen, sie zu ändern. Wo wird das aufgenommen? In der System-Tablespace-Datei besser bekannt als ibdata1. Das erfordert Disk - I / O .

VERGLEICH

Welche zufälligen Faktoren bestimmen, wer schneller ist, da sowohl InnoDB als auch MyISAM Festplatten-E / A durchführen?

  • Spaltengröße
  • Spaltenformat
  • Zeichensätze
  • Bereich von numerischen Werten (erfordert ausreichend große INTs)
  • Zeilen, die über Blöcke aufgeteilt werden (Zeilenverkettung)
  • Datenfragmentierung verursacht durch DELETEsundUPDATEs
  • Größe des Primärschlüssels (InnoDB verfügt über einen Clustered Index, für den zwei Schlüsselsuchen erforderlich sind)
  • Größe der Indexeinträge
  • Die Liste geht weiter...

In einer Umgebung mit vielen Lesevorgängen ist es daher möglich, dass eine MyISAM-Tabelle mit einem festen Zeilenformat besser als InnoDB-Lesevorgänge aus dem InnoDB-Pufferpool abschneidet, wenn in die in ibdata1 enthaltenen Rückgängig-Protokolle genügend Daten geschrieben werden, um das Transaktionsverhalten zu unterstützen InnoDB-Daten auferlegt.

FAZIT

Planen Sie Ihre Datentypen, Abfragen und Speicher-Engines sorgfältig. Sobald die Daten wachsen, kann es sehr schwierig werden, Daten zu verschieben. Fragen Sie einfach Facebook ...

RolandoMySQLDBA
quelle
1
Hervorragende Antwort, Rolando. Ich muss Ihre Einbeziehung der ungläubigen Behauptungen von Michael Stonebreaker in Frage stellen, der lediglich versucht, sein eigenes Produkt zu verkaufen und nichts über Facebook weiß. Nachdem sie Facebook mehrmals über MySQL gehört haben, ist es klar, dass sie mit ihren Entscheidungen zufrieden sind.
Aaron Brown
@AaronBrown Ich habe Harrison Fisk letztes Jahr auf der Percona Live NYC gehört und Sie haben Recht - Facebook ist sehr zufrieden mit der exklusiven Nutzung von InnoDB und der Zeit, die sie damit verbringen, systemweit Möglichkeiten für die Änderung von Online-Schemata zu finden. Er bietet dem Publikum sogar die Möglichkeit, für Facebook mit Big Data zu arbeiten. Ich habe den Artikel beigefügt, um zu zeigen, dass einige Befürchtungen haben. Ich würde die Möglichkeit begrüßen, mit riesigen Datenmengen zu arbeiten. Es würde Spaß machen und herausfordernd sein. Stellen Sie sich die Techniken zum Lernen vor. Natürlich würde ich niemals MyISAM für den Rest meines Lebens berühren ...
RolandoMySQLDBA
Ich war auch auf dieser Konferenz (und hatte das Glück, einen Vortrag halten zu können) und Harrisons Präsentation war ausgezeichnet.
Aaron Brown
20

In einer einfachen Welt ist MyISAM schneller beim Lesen und InnoDB schneller beim Schreiben.

Sobald Sie gemischte Lese- / Schreibvorgänge einführen, ist InnoDB dank seines Zeilensperrmechanismus auch beim Lesen schneller.

Ich habe vor einigen Jahren einen Vergleich von MySQL-Speicher-Engines geschrieben, der bis heute gültig ist und die einzigartigen Unterschiede zwischen MyISAM und InnoDB aufzeigt.

Nach meiner Erfahrung sollten Sie InnoDB für alles verwenden, außer für Cachetabellen mit hohem Leseaufwand, bei denen der Verlust von Daten aufgrund von Korruption nicht so kritisch ist.

Mike Peters
quelle
4
Diese Antwort ist 5 Jahre alt. InnoDB hat in nahezu jeder Hinsicht aufgeholt. Es gibt nicht mehr viele Argumente für die Verwendung von MyISAM. MySQL 8.0 ist dabei , MyISAM insgesamt zu entfernen .
Rick James
2
Und der Link ist jetzt 9 Jahre veraltet.
Rick James
Korrektur, die Antwort ist 9 Jahre veraltet (jeder, der den ersten Satz liest, wird ernsthafte Probleme haben, wenn er seine Datenbank bearbeitet) und der Link ist 11 Jahre veraltet. Fangen Sie Rick James auf, Sie fallen zurück :).
CYREX
1
Du hast recht @CYREX :-) Es ist erstaunlich, dass dieser Beitrag auch nach 11 Jahren noch immer viel Verkehr hat. So viel hat sich sowohl in meinem Leben als auch in der Art und Weise geändert, wie InnoDB optimiert wird. Heutzutage gibt es selten eine Rechtfertigung für die Verwendung von MyISAM
Mike Peters
Ich musste mir heute einige sterbende Datenbanken ansehen und beide Engines werden noch mit der alten Version von MySQL verwendet. Die Tabellen sind sowohl InnoDB als auch MyISAM und meine Neugier brachte mich zu diesem Beitrag, der sehr hilfreich war.
Farrukh Subhani
14

Um die Antworten hier auf die mechanischen Unterschiede zwischen den beiden Motoren zu ergänzen, präsentiere ich eine empirische Geschwindigkeitsvergleichsstudie.

In Bezug auf die reine Geschwindigkeit ist MyISAM nicht immer schneller als InnoDB, aber meiner Erfahrung nach ist es für PURE READ-Arbeitsumgebungen in der Regel um den Faktor 2,0 bis 2,5 schneller. Offensichtlich ist dies nicht für alle Umgebungen geeignet - wie andere geschrieben haben, fehlen MyISAM Dinge wie Transaktionen und Fremdschlüssel.

Ich habe unten ein bisschen Benchmarking durchgeführt - ich habe Python für Loops und die Timeit-Bibliothek für Timing-Vergleiche verwendet. Aus Gründen des Interesses habe ich auch die Speicher-Engine integriert, die die beste Leistung auf der ganzen Linie bietet, obwohl sie nur für kleinere Tabellen geeignet ist (Sie stoßen immer wieder darauf, The table 'tbl' is fullwenn Sie das MySQL-Speicherlimit überschreiten). Die vier Arten der Auswahl, die ich ansehe, sind:

  1. Vanille SELECTs
  2. zählt
  3. bedingte SELECTs
  4. indizierte und nicht indizierte Unterauswahlen

Zunächst habe ich drei Tabellen mit der folgenden SQL erstellt

CREATE TABLE
    data_interrogation.test_table_myisam
    (
        index_col BIGINT NOT NULL AUTO_INCREMENT,
        value1 DOUBLE,
        value2 DOUBLE,
        value3 DOUBLE,
        value4 DOUBLE,
        PRIMARY KEY (index_col)
    )
    ENGINE=MyISAM DEFAULT CHARSET=utf8

mit 'MyISAM' anstelle von 'InnoDB' und 'memory' in der zweiten und dritten Tabelle.

 

1) Vanille wählt

Abfrage: SELECT * FROM tbl WHERE index_col = xx

Ergebnis: Unentschieden

Vergleich der Vanille-Auswahl durch verschiedene Datenbank-Engines

Die Geschwindigkeit ist im Großen und Ganzen gleich und wie erwartet linear in der Anzahl der auszuwählenden Spalten. InnoDB scheint etwas schneller zu sein als MyISAM, aber das ist wirklich marginal.

Code:

import timeit
import MySQLdb
import MySQLdb.cursors
import random
from random import randint

db = MySQLdb.connect(host="...", user="...", passwd="...", db="...", cursorclass=MySQLdb.cursors.DictCursor)
cur = db.cursor()

lengthOfTable = 100000

# Fill up the tables with random data
for x in xrange(lengthOfTable):
    rand1 = random.random()
    rand2 = random.random()
    rand3 = random.random()
    rand4 = random.random()

    insertString = "INSERT INTO test_table_innodb (value1,value2,value3,value4) VALUES (" + str(rand1) + "," + str(rand2) + "," + str(rand3) + "," + str(rand4) + ")"
    insertString2 = "INSERT INTO test_table_myisam (value1,value2,value3,value4) VALUES (" + str(rand1) + "," + str(rand2) + "," + str(rand3) + "," + str(rand4) + ")"
    insertString3 = "INSERT INTO test_table_memory (value1,value2,value3,value4) VALUES (" + str(rand1) + "," + str(rand2) + "," + str(rand3) + "," + str(rand4) + ")"

    cur.execute(insertString)
    cur.execute(insertString2)
    cur.execute(insertString3)

db.commit()

# Define a function to pull a certain number of records from these tables
def selectRandomRecords(testTable,numberOfRecords):

    for x in xrange(numberOfRecords):
        rand1 = randint(0,lengthOfTable)

        selectString = "SELECT * FROM " + testTable + " WHERE index_col = " + str(rand1)
        cur.execute(selectString)

setupString = "from __main__ import selectRandomRecords"

# Test time taken using timeit
myisam_times = []
innodb_times = []
memory_times = []

for theLength in [3,10,30,100,300,1000,3000,10000]:

    innodb_times.append( timeit.timeit('selectRandomRecords("test_table_innodb",' + str(theLength) + ')', number=100, setup=setupString) )
    myisam_times.append( timeit.timeit('selectRandomRecords("test_table_myisam",' + str(theLength) + ')', number=100, setup=setupString) )
    memory_times.append( timeit.timeit('selectRandomRecords("test_table_memory",' + str(theLength) + ')', number=100, setup=setupString) )

 

2) Zählt

Abfrage: SELECT count(*) FROM tbl

Ergebnis: MyISAM gewinnt

Vergleich der Anzahl von verschiedenen Datenbank-Engines

Dieser zeigt einen großen Unterschied zwischen MyISAM und InnoDB - MyISAM (und Speicher) protokolliert die Anzahl der Datensätze in der Tabelle, sodass diese Transaktion schnell und 0 (1) ist. Die Zeit, die InnoDB benötigt, um zu zählen, steigt superlinear mit der Tabellengröße in dem von mir untersuchten Bereich. Ich vermute, dass viele der in der Praxis beobachteten Beschleunigungen von MyISAM-Abfragen auf ähnliche Effekte zurückzuführen sind.

Code:

myisam_times = []
innodb_times = []
memory_times = []

# Define a function to count the records
def countRecords(testTable):

    selectString = "SELECT count(*) FROM " + testTable
    cur.execute(selectString)

setupString = "from __main__ import countRecords"

# Truncate the tables and re-fill with a set amount of data
for theLength in [3,10,30,100,300,1000,3000,10000,30000,100000]:

    truncateString = "TRUNCATE test_table_innodb"
    truncateString2 = "TRUNCATE test_table_myisam"
    truncateString3 = "TRUNCATE test_table_memory"

    cur.execute(truncateString)
    cur.execute(truncateString2)
    cur.execute(truncateString3)

    for x in xrange(theLength):
        rand1 = random.random()
        rand2 = random.random()
        rand3 = random.random()
        rand4 = random.random()

        insertString = "INSERT INTO test_table_innodb (value1,value2,value3,value4) VALUES (" + str(rand1) + "," + str(rand2) + "," + str(rand3) + "," + str(rand4) + ")"
        insertString2 = "INSERT INTO test_table_myisam (value1,value2,value3,value4) VALUES (" + str(rand1) + "," + str(rand2) + "," + str(rand3) + "," + str(rand4) + ")"
        insertString3 = "INSERT INTO test_table_memory (value1,value2,value3,value4) VALUES (" + str(rand1) + "," + str(rand2) + "," + str(rand3) + "," + str(rand4) + ")"

        cur.execute(insertString)
        cur.execute(insertString2)
        cur.execute(insertString3)

    db.commit()

    # Count and time the query
    innodb_times.append( timeit.timeit('countRecords("test_table_innodb")', number=100, setup=setupString) )
    myisam_times.append( timeit.timeit('countRecords("test_table_myisam")', number=100, setup=setupString) )
    memory_times.append( timeit.timeit('countRecords("test_table_memory")', number=100, setup=setupString) )

 

3) Bedingte Auswahl

Abfrage: SELECT * FROM tbl WHERE value1<0.5 AND value2<0.5 AND value3<0.5 AND value4<0.5

Ergebnis: MyISAM gewinnt

Vergleich bedingter Auswahlen durch verschiedene Datenbankmodule

Hier arbeiten MyISAM und Arbeitsspeicher ungefähr gleich und übertreffen InnoDB bei größeren Tabellen um etwa 50%. Diese Art von Abfrage scheint die Vorteile von MyISAM zu maximieren.

Code:

myisam_times = []
innodb_times = []
memory_times = []

# Define a function to perform conditional selects
def conditionalSelect(testTable):
    selectString = "SELECT * FROM " + testTable + " WHERE value1 < 0.5 AND value2 < 0.5 AND value3 < 0.5 AND value4 < 0.5"
    cur.execute(selectString)

setupString = "from __main__ import conditionalSelect"

# Truncate the tables and re-fill with a set amount of data
for theLength in [3,10,30,100,300,1000,3000,10000,30000,100000]:

    truncateString = "TRUNCATE test_table_innodb"
    truncateString2 = "TRUNCATE test_table_myisam"
    truncateString3 = "TRUNCATE test_table_memory"

    cur.execute(truncateString)
    cur.execute(truncateString2)
    cur.execute(truncateString3)

    for x in xrange(theLength):
        rand1 = random.random()
        rand2 = random.random()
        rand3 = random.random()
        rand4 = random.random()

        insertString = "INSERT INTO test_table_innodb (value1,value2,value3,value4) VALUES (" + str(rand1) + "," + str(rand2) + "," + str(rand3) + "," + str(rand4) + ")"
        insertString2 = "INSERT INTO test_table_myisam (value1,value2,value3,value4) VALUES (" + str(rand1) + "," + str(rand2) + "," + str(rand3) + "," + str(rand4) + ")"
        insertString3 = "INSERT INTO test_table_memory (value1,value2,value3,value4) VALUES (" + str(rand1) + "," + str(rand2) + "," + str(rand3) + "," + str(rand4) + ")"

        cur.execute(insertString)
        cur.execute(insertString2)
        cur.execute(insertString3)

    db.commit()

    # Count and time the query
    innodb_times.append( timeit.timeit('conditionalSelect("test_table_innodb")', number=100, setup=setupString) )
    myisam_times.append( timeit.timeit('conditionalSelect("test_table_myisam")', number=100, setup=setupString) )
    memory_times.append( timeit.timeit('conditionalSelect("test_table_memory")', number=100, setup=setupString) )

 

4) Unterauswahlen

Ergebnis: InnoDB gewinnt

Für diese Abfrage habe ich einen zusätzlichen Tabellensatz für die Unterauswahl erstellt. Jedes ist einfach zwei Spalten von BIGINTs, eine mit einem Primärschlüsselindex und eine ohne Index. Aufgrund der großen Tischgröße habe ich die Memory Engine nicht getestet. Der Befehl zum Erstellen einer SQL-Tabelle lautete

CREATE TABLE
    subselect_myisam
    (
        index_col bigint NOT NULL,
        non_index_col bigint,
        PRIMARY KEY (index_col)
    )
    ENGINE=MyISAM DEFAULT CHARSET=utf8;

Dabei wird in der zweiten Tabelle erneut 'InnoDB' durch 'MyISAM' ersetzt.

In dieser Abfrage belasse ich die Größe der Auswahltabelle bei 1000000 und verändere stattdessen die Größe der unterausgewählten Spalten.

Vergleich von Unterauswahlen durch verschiedene Datenbankmodule

Hier gewinnt die InnoDB leicht. Nachdem wir eine vernünftige Größentabelle erreicht haben, skalieren beide Motoren linear mit der Größe der Unterauswahl. Der Index beschleunigt den MyISAM-Befehl, hat jedoch interessanterweise nur geringe Auswirkungen auf die InnoDB-Geschwindigkeit. subSelect.png

Code:

myisam_times = []
innodb_times = []
myisam_times_2 = []
innodb_times_2 = []

def subSelectRecordsIndexed(testTable,testSubSelect):
    selectString = "SELECT * FROM " + testTable + " WHERE index_col in ( SELECT index_col FROM " + testSubSelect + " )"
    cur.execute(selectString)

setupString = "from __main__ import subSelectRecordsIndexed"

def subSelectRecordsNotIndexed(testTable,testSubSelect):
    selectString = "SELECT * FROM " + testTable + " WHERE index_col in ( SELECT non_index_col FROM " + testSubSelect + " )"
    cur.execute(selectString)

setupString2 = "from __main__ import subSelectRecordsNotIndexed"

# Truncate the old tables, and re-fill with 1000000 records
truncateString = "TRUNCATE test_table_innodb"
truncateString2 = "TRUNCATE test_table_myisam"

cur.execute(truncateString)
cur.execute(truncateString2)

lengthOfTable = 1000000

# Fill up the tables with random data
for x in xrange(lengthOfTable):
    rand1 = random.random()
    rand2 = random.random()
    rand3 = random.random()
    rand4 = random.random()

    insertString = "INSERT INTO test_table_innodb (value1,value2,value3,value4) VALUES (" + str(rand1) + "," + str(rand2) + "," + str(rand3) + "," + str(rand4) + ")"
    insertString2 = "INSERT INTO test_table_myisam (value1,value2,value3,value4) VALUES (" + str(rand1) + "," + str(rand2) + "," + str(rand3) + "," + str(rand4) + ")"

    cur.execute(insertString)
    cur.execute(insertString2)

for theLength in [3,10,30,100,300,1000,3000,10000,30000,100000]:

    truncateString = "TRUNCATE subselect_innodb"
    truncateString2 = "TRUNCATE subselect_myisam"

    cur.execute(truncateString)
    cur.execute(truncateString2)

    # For each length, empty the table and re-fill it with random data
    rand_sample = sorted(random.sample(xrange(lengthOfTable), theLength))
    rand_sample_2 = random.sample(xrange(lengthOfTable), theLength)

    for (the_value_1,the_value_2) in zip(rand_sample,rand_sample_2):
        insertString = "INSERT INTO subselect_innodb (index_col,non_index_col) VALUES (" + str(the_value_1) + "," + str(the_value_2) + ")"
        insertString2 = "INSERT INTO subselect_myisam (index_col,non_index_col) VALUES (" + str(the_value_1) + "," + str(the_value_2) + ")"

        cur.execute(insertString)
        cur.execute(insertString2)

    db.commit()

    # Finally, time the queries
    innodb_times.append( timeit.timeit('subSelectRecordsIndexed("test_table_innodb","subselect_innodb")', number=100, setup=setupString) )
    myisam_times.append( timeit.timeit('subSelectRecordsIndexed("test_table_myisam","subselect_myisam")', number=100, setup=setupString) )

    innodb_times_2.append( timeit.timeit('subSelectRecordsNotIndexed("test_table_innodb","subselect_innodb")', number=100, setup=setupString2) )
    myisam_times_2.append( timeit.timeit('subSelectRecordsNotIndexed("test_table_myisam","subselect_myisam")', number=100, setup=setupString2) )

Ich denke, die Botschaft zum Mitnehmen ist, dass Sie, wenn Sie wirklich auf Geschwindigkeit bedacht sind, die von Ihnen ausgeführten Abfragen einem Benchmarking unterziehen müssen, anstatt davon auszugehen, welcher Motor besser geeignet ist.

StackG
quelle
1
Ich mag Ihre Antwort, weil es für Sie ist, wen Sie bewerten und entscheiden. Keine zwei Systeme profitieren auf dieselbe Weise von unterschiedlichen Speicher-Engines, und bei der Auswahl eines Speicher-Engines ist Sorgfalt geboten. +1 für Sie und Willkommen beim DBA StackExchange !!!
RolandoMySQLDBA
1
Siehe auch meinen Beitrag dba.stackexchange.com/questions/1/… zusammen mit den anderen Antworten. Ihr Beitrag geht weit darüber hinaus.
RolandoMySQLDBA
SELECT * FROM tbl WHERE index_col = xx- Hier sind zwei Faktoren, die wahrscheinlich zu mehr Variationen in der Grafik führen: Primärschlüssel vs. Sekundärschlüssel; Index ist zwischengespeichert vs nicht.
Rick James
2
SELECT COUNT(*)ist ein klarer Gewinner für MyISAM, bis Sie eine WHEREKlausel hinzufügen .
Rick James
Ich gehe davon aus, dass jede Abfrage separat bewertet werden muss. Ich habe den Code in die Antwort aufgenommen - wenn Sie eine andere Abfrage testen möchten, seien Sie bitte mein Gast - oder geben Sie explizit an, welche Abfrage Sie möchten, und ich werde sie hinzufügen.
StackG
4

Welche ist schneller? Beides könnte schneller sein. YMMV.

Welches solltest du verwenden? InnoDB - Absturzsicher usw. usw.

Rick James
quelle
Bitte definieren Sie "etc, etc."
Dellasavia
1
@dellasavia - Das neueste "etc" ist, dass Oracle plant, MyISAM zu entfernen. Sie sind so zuversichtlich in InnoDB.
Rick James