Lequel est le plus rapide, InnoDB ou MyISAM?

54

Comment MyISAM peut-il être "plus rapide" qu'InnoDB si

  • MyISAM doit faire des lectures de disque pour les données?
  • InnoDB utilise le pool de mémoire tampon pour les index et les données, et MyISAM uniquement pour l'index?
jcho360
la source
MyISAM laisse les blocs de données en cache du système d'exploitation , de sorte qu'il ne " fait pas toujours des lectures de disque pour les données".
Rick James

Réponses:

68

La seule façon dont MyISAM peut être plus rapide qu'InnoDB serait dans cette situation unique

MyISAM

Une fois lus, les index d’une table MyISAM peuvent être lus une fois à partir du fichier .MYI et chargés dans le cache de clé MyISAM (au format key_buffer_size ). Comment pouvez-vous accélérer la lecture du .MYD d'une table MyISAM? Avec ça:

ALTER TABLE mytable ROW_FORMAT=Fixed;

J'ai écrit à ce sujet dans mes posts précédents

InnoDB

OK, qu'en est-il de InnoDB? InnoDB effectue-t-il des entrées / sorties sur disque pour les requêtes? Étonnamment, oui c'est le cas !! Vous pensez probablement que je suis fou de dire cela, mais c'est absolument vrai, même pour les requêtes SELECT . À ce stade, vous vous demandez probablement "Comment InnoDB gère-t-il les entrées / sorties sur disque pour les requêtes?"

Tout revient à InnoDB en tant que moteur de stockage transactionnel à réclamation ACID . Pour qu'InnoDB soit transactionnel, il doit supporter l' Iin ACID, qui est Isolation. La technique de maintenance de l'isolation des transactions est effectuée via MVCC, Multiversion Concurrency Control . En termes simples, InnoDB enregistre à quoi ressemblent les données avant que les transactions ne tentent de les modifier. Où cela est-il enregistré? Dans le fichier de tablespace système, mieux connu sous le nom de ibdata1. Cela nécessite des E / S de disque .

COMPARAISON

Etant donné qu'InnoDB et MyISAM effectuent des E / S de disque, quels facteurs aléatoires dictent qui est le plus rapide?

  • Taille des colonnes
  • Format de colonne
  • Jeux de caractères
  • Gamme de valeurs numériques (nécessitant des INT assez importants)
  • Rangées divisées en blocs (chaînage)
  • Fragmentation des données causée par DELETEsetUPDATEs
  • Taille de la clé primaire (InnoDB a un index clusterisé, nécessitant deux recherches de clé)
  • Taille des entrées d'index
  • la liste continue...

Ainsi, dans un environnement de lecture intensive, il est possible qu'une table MyISAM avec un format de ligne fixe surpasse les lectures InnoDB du pool de mémoire tampon InnoDB si suffisamment de données sont écrites dans les journaux d'annulation contenus dans ibdata1 pour prendre en charge le comportement transactionnel. imposées sur les données InnoDB.

CONCLUSION

Planifiez soigneusement vos types de données, vos requêtes et votre moteur de stockage. Une fois que les données ont augmenté, il peut devenir très difficile de les déplacer. Il suffit de demander à Facebook ...

RolandoMySQLDBA
la source
1
Excellente réponse, Rolando. Je dois remettre en question votre inclusion des affirmations incrédules de Michael Stonebreaker, qui essaie simplement de vendre son propre produit et ne connaît rien de Facebook. Après avoir écouté les présentations de Facebook à propos de MySQL à plusieurs reprises, il est clair qu'ils sont à l'aise avec leurs choix.
Aaron Brown
@AaronBrown J'ai écouté Harrison Fisk l'année dernière à Percona Live NYC et vous avez raison. Facebook est très satisfait de son utilisation exclusive d'InnoDB et de la façon dont il passe du temps à trouver des moyens de modifier le système en ligne. Il offre même au public la possibilité de travailler pour Facebook dans le traitement de données volumineuses. J'ai inclus l'article pour montrer que certains ont des craintes à ce sujet. Je serais heureux de pouvoir travailler avec d’énormes données. Ce serait amusant et stimulant. Imaginez les techniques à apprendre. Bien sûr, je ne toucherais jamais à MyISAM pour le restant de mes jours ...
RolandoMySQLDBA
J'étais également à cette conférence (et j'ai eu la chance de pouvoir donner une conférence) et la présentation de Harrison était excellente.
Aaron Brown
20

Dans un monde simple, MyISAM est plus rapide pour les lectures, InnoDB est plus rapide pour les écritures.

Une fois que vous avez commencé à introduire des lectures / écritures mixtes, InnoDB sera également plus rapide pour les lectures, grâce à son mécanisme de verrouillage de rangée.

J'ai comparé les moteurs de stockage MySQL il y a quelques années, ce qui est toujours valable, soulignant les différences uniques entre MyISAM et InnoDB.

D'après mon expérience, vous devriez utiliser InnoDB pour tout, sauf pour les tables de cache à lecture lourde, où la perte de données en raison d'une corruption n'est pas aussi critique.

Mike Peters
la source
4
Cette réponse est périmée depuis 5 ans. InnoDB a rattrapé pratiquement tous les problèmes; il n'y a plus beaucoup d'argument pour utiliser MyISAM. MySQL 8.0 est en train de supprimer MyISAM tous ensemble.
Rick James
2
Et le lien est maintenant obsolète depuis 9 ans.
Rick James
Correction, la réponse est obsolète depuis 9 ans (toute personne lisant la première phrase rencontrera de sérieux problèmes lorsqu'elle utilisera sa base de données) et le lien sera périmé depuis 11 ans. Rattraper Rick James, vous êtes en retard :).
CYREX le
1
Tu as raison @CYREX :-) C'est incroyable ce post continue à attirer du trafic, 11 ans plus tard. Tant de choses ont changé dans ma vie et dans la manière dont InnoDB est optimisé. De nos jours, il est rarement justifié d'utiliser MyISAM
Mike Peters
Je devais regarder certaines bases de données en train de mourir aujourd'hui et les deux moteurs sont toujours utilisés avec l'ancienne version de mysql. Les tables sont à la fois InnoDB et MyISAM et ma curiosité m'a amené à ce post qui était très utile.
Farrukh Subhani
14

Pour ajouter aux réponses ici couvrant les différences mécaniques entre les deux moteurs, je présente une étude de comparaison de vitesse empirique.

En termes de vitesse pure, MyISAM n’est pas toujours plus rapide que InnoDB, mais selon mon expérience, il a tendance à être plus rapide de 2,0 à 2,5 fois plus dans les environnements de travail PURE READ. Clairement, cela ne convient pas à tous les environnements - comme d'autres l'ont écrit, MyISAM manque d'éléments tels que les transactions et les clés étrangères.

J'ai fait un peu de comparaison ci-dessous - j'ai utilisé python pour les boucles et la bibliothèque timeit pour les comparaisons de temps. Il est intéressant de noter que j'ai également inclus le moteur de mémoire, qui offre les meilleures performances globales, même s'il ne convient que pour les petites tables (que vous rencontrez continuellement The table 'tbl' is fulllorsque vous dépassez la limite de mémoire MySQL). Les quatre types de sélection que je regarde sont les suivants:

  1. SELECT à la vanille
  2. compte
  3. SELECT conditionnel
  4. sous-sélections indexées et non indexées

Tout d'abord, j'ai créé trois tables en utilisant le code SQL suivant

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

avec 'MyISAM' substitué à 'InnoDB' et 'memory' dans les deuxième et troisième tables.

 

1) La vanille choisit

Requete: SELECT * FROM tbl WHERE index_col = xx

Résultat: dessiner

Comparaison de vanilla selects par différents moteurs de base de données

La vitesse de ceux-ci est globalement la même et, comme prévu, est linéaire dans le nombre de colonnes à sélectionner. InnoDB semble légèrement plus rapide que MyISAM mais ceci est vraiment 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) compte

Requete: SELECT count(*) FROM tbl

Résultat: MyISAM gagne

Comparaison des nombres par différents moteurs de base de données

Celui-ci montre une grande différence entre MyISAM et InnoDB - MyISAM (et la mémoire) garde la trace du nombre d'enregistrements dans la table, donc cette transaction est rapide et O (1). Le temps nécessaire à InnoDB pour compter augmente de façon super linéaire avec la taille de la table dans la plage que j'ai étudiée. Je pense que bon nombre des accélérations résultant des requêtes MyISAM observées dans la pratique sont dues à des effets similaires.

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) Sélection conditionnelle

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

Résultat: MyISAM gagne

Comparaison des sélections conditionnelles effectuées par différents moteurs de base de données

Ici, MyISAM et la mémoire fonctionnent à peu près de la même manière et battent InnoDB d’environ 50% pour les tables plus volumineuses. C'est le type de requête pour lequel les avantages de MyISAM semblent être maximisés.

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) sous-sélection

Résultat: InnoDB gagne

Pour cette requête, j'ai créé un ensemble supplémentaire de tables pour la sous-sélection. Chacune consiste simplement en deux colonnes de BIGINT, une avec un index de clé primaire et une sans index. En raison de la grande taille de la table, je n'ai pas testé le moteur de mémoire. La commande de création de table SQL était

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

où, encore une fois, «MyISAM» est remplacé par «InnoDB» dans le deuxième tableau.

Dans cette requête, je laisse la taille de la table de sélection à 1000000 et à la place je fais varier la taille des colonnes sous-sélectionnées.

Comparaison des sous-sélections effectuées par différents moteurs de base de données

Ici, l'InnoDB gagne facilement. Une fois que nous arrivons à une table de taille raisonnable, les deux moteurs s’échelonnent linéairement avec la taille de la sous-sélection. L'index accélère la commande MyISAM mais a peu d'effet intéressant sur la vitesse InnoDB. 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) )

Je pense que le message à retenir de tout cela est que si vous êtes vraiment préoccupé par la vitesse, vous devez analyser les requêtes que vous faites plutôt que de faire des suppositions quant au moteur le plus approprié.

StackG
la source
1
J'apprécie votre réponse car elle est favorable à quiconque se repère et décide. Aucun système ne bénéficie de la même manière de moteurs de stockage différents et il faut faire preuve de diligence raisonnable pour choisir un moteur de stockage. +1 pour vous et bienvenue sur DBA StackExchange !!!
RolandoMySQLDBA
1
Voir aussi mon post dba.stackexchange.com/questions/1/… avec les autres réponses. Votre message va au-delà des attentes.
RolandoMySQLDBA
SELECT * FROM tbl WHERE index_col = xx- Deux facteurs sont susceptibles d’entraîner davantage de variations dans le graphique: clé primaire vs clé secondaire; l'index est mis en cache vs pas.
Rick James
2
SELECT COUNT(*)est un gagnant clair pour MyISAM jusqu'à ce que vous ajoutiez une WHEREclause.
Rick James
Je suppose que ce que je veux dire, c'est que chaque requête doit être analysée séparément. J'ai inclus le code dans la réponse - si vous voulez tester une requête différente, soyez mon invité - ou expliquez la requête que vous souhaitez et je l'ajouterai.
StackG
4

Lequel est plus vite? Soit pourrait être plus rapide. YMMV.

Lequel devriez-vous utiliser? InnoDB - sans risque d'accident, etc.

Rick James
la source
veuillez définir "etc, etc."
dellasavia
1
@dellasavia - Le plus récent "etc" indique qu'Oracle envisage de supprimer MyISAM. Ils sont aussi confiants en InnoDB.
Rick James