Comment MyISAM peut-il être "plus rapide" qu'InnoDB si
La seule façon dont MyISAM peut être plus rapide qu'InnoDB serait dans cette circonstance unique
Lors de la lecture, les index d'une table MyISAM peuvent être lus une fois à partir du fichier .MYI et chargés dans le cache de clés MyISAM (tels que dimensionnés par key_buffer_size ). Comment 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 précédents articles
OK, et InnoDB? InnoDB effectue-t-il des E/S disque pour les requêtes? Étonnamment, oui! 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 diable InnoDB fait-il des E/S disque pour les requêtes?"
Tout cela remonte à InnoDB étant un [~ # ~] acide [~ # ~] - plainte Transactional Storage Engine. Pour qu'InnoDB soit transactionnel, il doit prendre en charge le I
dans ACID
, qui est l'isolement. La technique pour maintenir l'isolement des transactions se fait via MVCC, Multiversion Concurrency Control . En termes simples, InnoDB enregistre à quoi ressemblent les données avant que les transactions tentent de les modifier. Où cela est-il enregistré? Dans le fichier d'espace disque logique du système, mieux connu sous le nom d'ibdata1. Cela nécessite des E/S disque .
Étant donné qu'InnoDB et MyISAM effectuent des E/S disque, quels facteurs aléatoires déterminent qui est le plus rapide?
DELETEs
et UPDATEs
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 tampons InnoDB s'il y a suffisamment de données écrites dans les journaux d'annulation contenus dans ibdata1 pour prendre en charge le comportement transactionnel imposées aux données InnoDB.
Planifiez soigneusement vos types de données, vos requêtes et votre moteur de stockage. Une fois que les données augmentent, il peut devenir très difficile de déplacer les données. Il suffit de demander à Facebook ...
Dans un monde simple, MyISAM est plus rapide pour les lectures, InnoDB est plus rapide pour les écritures.
Une fois que vous commencez à introduire des lectures/écritures mixtes, InnoDB sera également plus rapide pour les lectures, grâce à son mécanisme de verrouillage de ligne.
J'ai écrit une comparaison de moteurs de stockage MySQL il y a quelques années, qui est toujours vrai à ce jour, décrivant 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 intensive, où la perte de données due à la corruption n'est pas aussi critique.
Pour compléter les réponses couvrant les différences mécaniques entre les deux moteurs, je présente une étude empirique de comparaison de vitesse.
En termes de vitesse pure, il n'est pas toujours vrai que MyISAM est plus rapide qu'InnoDB, mais d'après mon expérience, il a tendance à être plus rapide pour les environnements de travail PURE READ par un facteur d'environ 2,0 à 2,5 fois. De toute évidence, cela ne convient pas à tous les environnements - comme d'autres l'ont écrit, MyISAM manque de choses telles que les transactions et les clés étrangères.
J'ai fait un peu de benchmarking ci-dessous - j'ai utilisé python pour le bouclage et la bibliothèque timeit pour les comparaisons de temps. Pour l'intérêt, j'ai également inclus le moteur de mémoire, cela donne les meilleures performances à travers le conseil d'administration bien qu'il ne soit approprié que pour les petites tables (vous rencontrez continuellement The table 'tbl' is full
lorsque vous dépassez la limite de mémoire MySQL). Les quatre types de sélection que je regarde sont:
Tout d'abord, j'ai créé trois tables en utilisant le 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 "mémoire" dans les deuxième et troisième tableaux.
Requête: SELECT * FROM tbl WHERE index_col = xx
Résultat: nul
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 c'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) )
Requête: SELECT count(*) FROM tbl
Résultat: MyISAM gagne
Celui-ci montre une grande différence entre MyISAM et InnoDB - MyISAM (et la mémoire) garde une 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 manière super-linéaire avec la taille de la table dans la plage que j'ai étudiée. Je soupçonne que bon nombre des accélérations 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) )
Requête: SELECT * FROM tbl WHERE value1<0.5 AND value2<0.5 AND value3<0.5 AND value4<0.5
Résultat: MyISAM gagne
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 grandes. C'est le genre de requête pour laquelle 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) )
Résultat: InnoDB gagne
Pour cette requête, j'ai créé un ensemble supplémentaire de tables pour la sous-sélection. Chacune est simplement 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 du tableau de sélection à 1000000 et je fais plutôt varier la taille des colonnes sous-sélectionnées.
Ici, InnoDB gagne facilement. Après avoir atteint une table de taille raisonnable, les deux moteurs évoluent linéairement avec la taille de la sous-sélection. L'index accélère la commande MyISAM mais a, de façon intéressante, peu d'effet 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 comparer les requêtes que vous faites plutôt que de faire des hypothèses sur le moteur qui conviendra le mieux.
Lequel est plus vite? Soit pourrait être plus rapide. YMMV.
Que devez-vous utiliser? InnoDB - crash-safe, etc., etc.