Quale è più veloce, InnoDB o MyISAM?


54

Come può MyISAM essere "più veloce" di InnoDB se

  • MyISAM deve fare letture del disco per i dati?
  • InnoDB utilizza il pool di buffer per indici e dati e MyISAM solo per l'indice?

MyISAM consente al sistema operativo di memorizzare nella cache blocchi di dati , quindi non sempre "esegue letture su disco per i dati".
Rick James,

Risposte:


68

L'unico modo in cui MyISAM può essere più veloce di InnoDB in questa circostanza unica

MyISAM

Quando viene letto, gli indici di una tabella MyISAM possono essere letti una volta dal file .MYI e caricati nella cache delle chiavi MyISAM (in base alle dimensioni di key_buffer_size ). Come si può rendere più veloce la lettura .MYD di una tabella MyISAM? Con questo:

ALTER TABLE mytable ROW_FORMAT=Fixed;

Ho scritto di questo nei miei post precedenti

InnoDB

OK, che dire di InnoDB? InnoDB esegue I / O su disco per le query? Sorprendentemente, sì, lo fa !! Probabilmente stai pensando che io sia pazzo per averlo detto, ma è assolutamente vero, anche per le query SELECT . A questo punto, probabilmente ti starai chiedendo "In che modo InnoDB sta eseguendo l'I / O su disco per le query?"

Tutto risale al fatto che InnoDB è un motore di memorizzazione transazionale ACID- complaint. Affinché InnoDB sia Transazionale, deve supportare Iin ACID, che è Isolation. La tecnica per mantenere l'isolamento per le transazioni viene eseguita tramite MVCC, Multiversion Concurrency Control . In termini semplici, InnoDB registra l'aspetto dei dati prima che le transazioni tentino di modificarli. Dove viene registrato? Nel file del tablespace di sistema, meglio noto come ibdata1. Ciò richiede l'I / O del disco .

CONFRONTO

Poiché sia ​​InnoDB che MyISAM eseguono l'I / O su disco, quali fattori casuali determinano chi è più veloce?

  • Dimensione delle colonne
  • Formato colonna
  • Set di caratteri
  • Intervallo di valori numerici (che richiedono INT abbastanza grandi)
  • Righe suddivise in blocchi (concatenamento di file)
  • Frammentazione dei dati causata da DELETEseUPDATEs
  • Dimensione della chiave primaria (InnoDB ha un indice cluster, che richiede due ricerche di chiavi)
  • Dimensione delle voci dell'indice
  • l'elenco continua ...

Pertanto, in un ambiente di lettura pesante, è possibile che una tabella MyISAM con un formato di riga fissa superi le letture di InnoDB dal pool di buffer InnoDB se vi sono dati sufficienti scritti nei registri di annullamento contenuti in ibdata1 per supportare il comportamento transazionale imposto sui dati InnoDB.

CONCLUSIONE

Pianifica i tipi di dati, le query e il motore di archiviazione in modo molto accurato. Una volta che i dati crescono, potrebbe essere molto difficile spostarli. Basta chiedere a Facebook ...


1
Ottima risposta, Rolando. Devo mettere in dubbio la tua inclusione delle incredibili affermazioni fatte da Michael Stonebreaker, che sta semplicemente cercando di vendere il proprio prodotto e non sa nulla di Facebook. Dopo aver ascoltato più volte Facebook presenti su MySQL, è chiaro che si sentono a proprio agio con le loro scelte.
Aaron Brown,

@AaronBrown Ho ascoltato Harrison Fisk l'anno scorso a Percona Live NYC e hai ragione: Facebook è molto contento del loro uso esclusivo di InnoDB e del modo in cui passano il tempo a trovare modi per effettuare il cambio di schema online a livello di sistema. Offre anche al pubblico la possibilità di lavorare per Facebook gestendo i big data. Ho incluso l'articolo per dimostrare che alcuni hanno delle paure al riguardo. Gradirei l'opportunità di lavorare con dati enormi. Sarebbe divertente e stimolante. Immagina le tecniche che ci sono da imparare. Certo, non toccherei mai MyISAM per il resto della mia vita ...
RolandoMySQLDBA

Sono stato anche a quella conferenza (e ho avuto la fortuna di poter tenere un discorso) e la presentazione di Harrison è stata eccellente.
Aaron Brown,

20

In un mondo semplice, MyISAM è più veloce per le letture, InnoDB è più veloce per le scritture.

Una volta che inizi a introdurre letture / scritture miste, InnoDB sarà più veloce anche per le letture, grazie al suo meccanismo di blocco delle file.

Ho scritto un confronto tra i motori di archiviazione MySQL alcuni anni fa, che è ancora valido fino ad oggi, delineando le differenze uniche tra MyISAM e InnoDB.

Nella mia esperienza, dovresti usare InnoDB per tutto tranne che per le tabelle di cache pesanti in lettura, in cui la perdita di dati a causa della corruzione non è così critica.


4
Questa risposta è scaduta 5 anni. InnoDB ha raggiunto praticamente in tutti i modi; non ci sono più argomenti per l'utilizzo di MyISAM. MySQL 8.0 è in procinto di rimuovere MyISAM tutti insieme.
Rick James,

2
E il collegamento è ormai obsoleto di 9 anni.
Rick James,

Correzione, la risposta è obsoleta di 9 anni (chiunque legga la prima frase avrà alcuni seri problemi durante l'esecuzione del proprio database) e il collegamento è obsoleto di 11 anni. Recupera Rick James, stai cadendo indietro :).
CYREX

1
Hai ragione @CYREX :-) È incredibile che questo post stia ancora ricevendo traffico, 11 anni dopo. Sono cambiate molte cose sia nella mia vita che nel modo in cui InnoDB è ottimizzato. Al giorno d'oggi, raramente c'è mai una giustificazione per usare MyISAM
Mike Peters l'

Ho dovuto esaminare alcuni database morenti oggi ed entrambi i motori sono ancora in uso con la vecchia versione di mysql. Le tabelle sono sia InnoDB che MyISAM e la mia curiosità mi ha portato a questo post che è stato molto utile.
Farrukh Subhani,

14

Per aggiungere alle risposte qui che riguardano le differenze meccaniche tra i due motori, presento uno studio empirico di confronto della velocità.

In termini di pura velocità, non è sempre vero che MyISAM sia più veloce di InnoDB, ma nella mia esperienza tende ad essere più veloce per gli ambienti di lavoro PURE READ di un fattore di circa 2,0-2,5 volte. Chiaramente questo non è appropriato per tutti gli ambienti - come altri hanno scritto, MyISAM manca di cose come transazioni e chiavi esterne.

Ho fatto un po 'di benchmarking di seguito - ho usato Python per il looping e la libreria timeit per i confronti di temporizzazione. Per interesse ho anche incluso il motore di memoria, questo offre le migliori prestazioni su tutta la linea anche se è adatto solo per tavoli più piccoli (si incontrano continuamente The table 'tbl' is fullquando si supera il limite di memoria MySQL). I quattro tipi di selezione che guardo sono:

  1. SELEZIONI vaniglia
  2. conta
  3. SELECT condizionali
  4. sotto-selezioni indicizzate e non indicizzate

Innanzitutto, ho creato tre tabelle utilizzando il seguente SQL

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

con "MyISAM" ha sostituito "InnoDB" e "memoria" nella seconda e terza tabella.

 

1) Vanilla seleziona

Query: SELECT * FROM tbl WHERE index_col = xx

Risultato: pareggio

Confronto di vanilla seleziona da diversi motori di database

La velocità di questi è sostanzialmente la stessa e, come previsto, è lineare nel numero di colonne da selezionare. InnoDB sembra leggermente più veloce di MyISAM ma questo è davvero marginale.

Codice:

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

Query: SELECT count(*) FROM tbl

Risultato: MyISAM vince

Confronto dei conteggi da parte di diversi motori di database

Questo dimostra una grande differenza tra MyISAM e InnoDB - MyISAM (e memoria) tiene traccia del numero di record nella tabella, quindi questa transazione è veloce e O (1). La quantità di tempo necessaria per il conteggio di InnoDB aumenta in modo super-lineare con la dimensione della tabella nell'intervallo che ho studiato. Sospetto che molte delle accelerazioni delle query MyISAM osservate nella pratica siano dovute a effetti simili.

Codice:

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

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

Risultato: MyISAM vince

Confronto tra selezioni condizionali da diversi motori di database

In questo caso, MyISAM e la memoria si comportano all'incirca allo stesso modo e battono InnoDB di circa il 50% per tavoli più grandi. Questo è il tipo di query per cui i vantaggi di MyISAM sembrano essere massimizzati.

Codice:

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

Risultato: InnoDB vince

Per questa query, ho creato un set aggiuntivo di tabelle per la sottoselezione. Ciascuno è semplicemente due colonne di BIGINT, una con un indice di chiave primaria e una senza alcun indice. A causa delle grandi dimensioni della tabella, non ho testato il motore di memoria. Il comando di creazione della tabella SQL era

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

dove ancora una volta, "MyISAM" è sostituito da "InnoDB" nella seconda tabella.

In questa query, lascio la dimensione della tabella di selezione a 1000000 e invece modifico la dimensione delle colonne selezionate.

Confronto di sotto-selezioni da diversi motori di database

Qui InnoDB vince facilmente. Dopo aver raggiunto una tabella di dimensioni ragionevoli, entrambi i motori si ridimensionano in modo lineare con le dimensioni della sottoselezione. L'indice accelera il comando MyISAM ma ha un effetto interessante sulla velocità di InnoDB. subSelect.png

Codice:

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

Penso che il messaggio da portare a casa di tutto ciò sia che se sei veramente preoccupato per la velocità, devi confrontare le domande che stai facendo piuttosto che fare ipotesi su quale motore sarà più adatto.


1
Mi piace la tua risposta perché è a favore di chi fai il benchmark e decidi. Non esistono due sistemi che beneficiano allo stesso modo di diversi motori di archiviazione e per scegliere un motore di archiviazione è necessaria la dovuta diligenza. +1 per te e benvenuto nello StackExchange di DBA !!!
RolandoMySQLDBA l'

1
Inoltre, vedi il mio post dba.stackexchange.com/questions/1/… insieme alle altre risposte. Il tuo post va oltre.
RolandoMySQLDBA l'

SELECT * FROM tbl WHERE index_col = xx- Qui ci sono due fattori che possono portare a maggiori variazioni nel grafico: chiave primaria vs chiave secondaria; L'indice è memorizzato nella cache e non.
Rick James,

2
SELECT COUNT(*)è un chiaro vincitore per MyISAM fino a quando non aggiungi una WHEREclausola.
Rick James,

Suppongo che il mio punto sia che ogni query deve essere confrontata separatamente. Ho incluso il codice nella risposta - se vuoi provare una query diversa, per favore, sii mio ospite - o sii esplicito quale query desideri e lo aggiungerò.
StackG

4

Qual è più veloce? O potrebbe essere più veloce. YMMV.

Quale dovresti usare? InnoDB - crash-safe, ecc. Ecc.


per favore, definisci "ecc. ecc."
dellasavia,

1
@dellasavia - Il "etc" più recente è che Oracle sta pianificando di rimuovere MyISAM. Sono così fiduciosi in InnoDB.
Rick James,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.