¿Cuál es más rápido, InnoDB o MyISAM?

54

¿Cómo puede MyISAM ser "más rápido" que InnoDB si

  • MyISAM necesita hacer lecturas de disco para los datos?
  • InnoDB usa el grupo de búferes para índices y datos, y MyISAM solo para el índice.
jcho360
fuente
MyISAM permite que los bloques de datos del caché del sistema operativo , por lo que no siempre "hace lecturas de disco para los datos".
Rick James

Respuestas:

68

La única forma en que MyISAM puede ser más rápido que InnoDB sería bajo esta circunstancia única

MyISAM

Cuando se lee, los índices de una tabla MyISAM se pueden leer una vez desde el archivo .MYI y cargarse en el caché de claves MyISAM (según el tamaño de key_buffer_size ). ¿Cómo puede hacer que una tabla MyISAM .MYD sea más rápida de leer? Con este:

ALTER TABLE mytable ROW_FORMAT=Fixed;

Escribí sobre esto en mis publicaciones anteriores

InnoDB

OK, ¿qué hay de InnoDB? ¿InnoDB realiza alguna E / S de disco para consultas? Sorprendentemente, sí lo hace! Probablemente esté pensando que estoy loco por decir eso, pero es absolutamente cierto, incluso para consultas SELECT . En este punto, probablemente se esté preguntando "¿Cómo está InnoDB haciendo E / S de disco para consultas?"

Todo se remonta a que InnoDB es un motor de almacenamiento transaccional de queja ACID . Para que InnoDB sea Transaccional, debe admitir Iin ACID, que es Aislamiento. La técnica para mantener el aislamiento de las transacciones se realiza a través de MVCC, Control de concurrencia multiversional . En términos simples, InnoDB registra el aspecto de los datos antes de que las transacciones intenten cambiarlos. ¿Dónde se graba eso? En el archivo de espacio de tabla del sistema, mejor conocido como ibdata1. Eso requiere E / S de disco .

COMPARACIÓN

Dado que tanto InnoDB como MyISAM hacen E / S de disco, ¿qué factores aleatorios determinan quién es más rápido?

  • Tamaño de columnas
  • Formato de columna
  • Conjuntos de caracteres
  • Rango de valores numéricos (que requieren INT suficientemente grandes)
  • Filas que se dividen en bloques (encadenamiento de filas)
  • Fragmentación de datos causada por DELETEsyUPDATEs
  • Tamaño de la clave principal (InnoDB tiene un índice agrupado, que requiere dos búsquedas de clave)
  • Tamaño de las entradas de índice
  • la lista continua...

Por lo tanto, en un entorno de lectura pesada, es posible que una tabla MyISAM con un formato de fila fija supere las lecturas de InnoDB del InnoDB Buffer Pool si se escriben suficientes datos en los registros de deshacer contenidos en ibdata1 para admitir el comportamiento transaccional impuesta a los datos de InnoDB.

CONCLUSIÓN

Planifique sus tipos de datos, consultas y motor de almacenamiento con mucho cuidado. Una vez que los datos crecen, puede ser muy difícil mover los datos. Solo pregúntale a Facebook ...

RolandoMySQLDBA
fuente
1
Excelente respuesta, Rolando. Tengo que cuestionar su inclusión de las afirmaciones incrédulas hechas por Michael Stonebreaker, que simplemente está tratando de vender su propio producto y no sabe nada sobre Facebook. Después de haber escuchado a Facebook presentar sobre MySQL varias veces, está claro que están cómodos con sus elecciones.
Aaron Brown
@AaronBrown He escuchado a Harrison Fisk el año pasado en Percona Live NYC y tienes razón: Facebook está muy contento con su uso exclusivo de InnoDB y con la forma en que dedican tiempo a encontrar formas de hacer cambios en el sistema en línea en todo el sistema. Incluso ofreció a la audiencia la oportunidad de trabajar para Facebook manejando big data. Incluí el artículo para mostrar que algunos tienen miedo de eso. Me gustaría tener la oportunidad de trabajar con grandes datos. Sería divertido y desafiante. Imagina las técnicas que hay que aprender. Por supuesto, nunca tocaría MyISAM por el resto de mi vida ...
RolandoMySQLDBA
También estuve en esa conferencia (y tuve la suerte de poder dar una charla) y la presentación de Harrison fue excelente.
Aaron Brown
20

En un mundo simple, MyISAM es más rápido para lecturas, InnoDB es más rápido para escrituras.

Una vez que comience a introducir lecturas / escrituras mixtas, InnoDB también será más rápido para las lecturas, gracias a su mecanismo de bloqueo de filas.

Escribí una comparación de los motores de almacenamiento MySQL hace unos años, que todavía es verdad hasta el día de hoy, describiendo las diferencias únicas entre MyISAM e InnoDB.

En mi experiencia, debería usar InnoDB para todo excepto para las tablas de caché de lectura pesada, donde la pérdida de datos debido a la corrupción no es tan crítica.

Mike Peters
fuente
44
Esta respuesta tiene 5 años de retraso. InnoDB se ha puesto al día prácticamente en todos los sentidos; ya no hay mucho argumento para usar MyISAM. MySQL 8.0 está en proceso de eliminar MyISAM todos juntos.
Rick James
2
Y el enlace está ahora 9 años desactualizado.
Rick James
Corrección, la respuesta está desactualizada 9 años (cualquiera que lea la primera oración tendrá algunos problemas serios al hacer su base de datos) y el enlace está desactualizado 11 años. Póngase al día con Rick James, te estás quedando atrás :).
CYREX
1
Tienes razón @CYREX :-) Es sorprendente que esta publicación siga recibiendo tráfico, 11 años después. Mucho ha cambiado tanto en mi vida como en la forma en que InnoDB está optimizado. Hoy en día, rara vez existe una justificación para usar MyISAM
Mike Peters
Tuve que mirar algunas bases de datos moribundas hoy y ambos motores todavía están en uso con la versión anterior de mysql. Las tablas son InnoDB y MyISAM y mi curiosidad me trajo a esta publicación que fue muy útil.
Farrukh Subhani
14

Para agregar a las respuestas aquí que cubren las diferencias mecánicas entre los dos motores, presento un estudio empírico de comparación de velocidad.

En términos de velocidad pura, no siempre es el caso de que MyISAM sea más rápido que InnoDB, pero en mi experiencia tiende a ser más rápido para entornos de trabajo PURE READ en un factor de aproximadamente 2.0-2.5 veces. Claramente, esto no es apropiado para todos los entornos: como otros han escrito, MyISAM carece de cosas como transacciones y claves foráneas.

He hecho un poco de evaluación comparativa a continuación: he usado python para bucles y la biblioteca timeit para comparaciones de temporización. Por interés, también he incluido el motor de memoria, que proporciona el mejor rendimiento en todos los ámbitos, aunque solo es adecuado para tablas más pequeñas (se encuentra continuamente The table 'tbl' is fullcuando excede el límite de memoria de MySQL). Los cuatro tipos de selección que miro son:

  1. SELECTs de vainilla
  2. cuenta
  3. SELECT condicionales
  4. subselecciones indexadas y no indexadas

Primero, creé tres tablas usando el siguiente 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' sustituido por 'InnoDB' y 'memoria' en las tablas segunda y tercera.

 

1) Vanilla selecciona

Consulta: SELECT * FROM tbl WHERE index_col = xx

Resultado: empate

Comparación de selecciones de vainilla por diferentes motores de bases de datos

La velocidad de estos es, en general, la misma, y ​​como se espera es lineal en el número de columnas que se seleccionarán. InnoDB parece un poco más rápido que MyISAM pero esto es realmente marginal.

Código:

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

Consulta: SELECT count(*) FROM tbl

Resultado: MyISAM gana

Comparación de conteos por diferentes motores de bases de datos.

Éste demuestra una gran diferencia entre MyISAM e InnoDB: MyISAM (y la memoria) realiza un seguimiento del número de registros en la tabla, por lo que esta transacción es rápida y O (1). La cantidad de tiempo requerida para que InnoDB cuente aumenta de forma superlineal con el tamaño de la tabla en el rango que investigué. Sospecho que muchas de las aceleraciones de las consultas de MyISAM que se observan en la práctica se deben a efectos similares.

Código:

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

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

Resultado: MyISAM gana

Comparación de selecciones condicionales por diferentes motores de bases de datos

Aquí, MyISAM y la memoria funcionan aproximadamente igual, y superan a InnoDB en aproximadamente un 50% para tablas más grandes. Este es el tipo de consulta para la cual los beneficios de MyISAM parecen ser maximizados.

Código:

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

Resultado: InnoDB gana

Para esta consulta, creé un conjunto adicional de tablas para la sub-selección. Cada uno es simplemente dos columnas de BIGINT, una con un índice de clave principal y otra sin ningún índice. Debido al gran tamaño de la tabla, no probé el motor de memoria. El comando de creación de la tabla SQL fue

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

donde una vez más, 'MyISAM' se sustituye por 'InnoDB' en la segunda tabla.

En esta consulta, dejo el tamaño de la tabla de selección en 1000000 y, en cambio, varío el tamaño de las columnas subseleccionadas.

Comparación de sub-selecciones por diferentes motores de bases de datos.

Aquí el InnoDB gana fácilmente. Después de llegar a una tabla de tamaño razonable, ambos motores se escalan linealmente con el tamaño de la sub-selección. El índice acelera el comando MyISAM pero, curiosamente, tiene poco efecto en la velocidad de InnoDB. subSelect.png

Código:

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

Creo que el mensaje final de todo esto es que si está realmente preocupado por la velocidad, debe comparar las consultas que está haciendo en lugar de hacer suposiciones sobre qué motor será más adecuado.

StackG
fuente
1
Me gusta tu respuesta porque está a favor de quien sea que compares y decidas. No hay dos sistemas que se beneficien de la misma manera con diferentes motores de almacenamiento y se requiere la debida diligencia para elegir un motor de almacenamiento. ¡+1 para ti y bienvenido al DBA StackExchange!
RolandoMySQLDBA
1
Además, vea mi publicación dba.stackexchange.com/questions/1/… junto con las otras respuestas. Tu publicación va más allá.
RolandoMySQLDBA
SELECT * FROM tbl WHERE index_col = xx- Aquí hay dos factores que pueden conducir a una mayor variación en el gráfico: clave primaria frente a clave secundaria; El índice está en caché frente a no.
Rick James
2
SELECT COUNT(*)es un claro ganador para MyISAM hasta que agregue una WHEREcláusula.
Rick James
Supongo que mi punto es que cada consulta debe ser comparada por separado. He incluido el código en la respuesta: si desea probar una consulta diferente, sea mi invitado, o sea explícito qué consulta desea y la agregaré.
StackG
4

¿Cual es mas rápido? Cualquiera de los dos podría ser más rápido. YMMV.

¿Cuál deberías usar? InnoDB - a prueba de choques, etc., etc.

Rick James
fuente
por favor, defina "etc, etc."
dellasavia
1
@dellasavia: el "etc" más reciente es que Oracle planea eliminar MyISAM. Confían tanto en InnoDB.
Rick James