O que é mais rápido, InnoDB ou MyISAM?

54

Como o MyISAM pode ser "mais rápido" que o InnoDB se

  • O MyISAM precisa fazer leituras de disco para os dados?
  • O InnoDB usa o buffer pool para índices e dados, e o MyISAM apenas para o índice?
jcho360
fonte
O MyISAM permite que o sistema operacional armazene em cache os blocos de dados , portanto nem sempre "faz leituras de disco para os dados".
Rick James

Respostas:

68

A única maneira de o MyISAM ser mais rápido que o InnoDB estaria sob essa circunstância única

MyISAM

Quando lidos, os índices de uma tabela MyISAM podem ser lidos uma vez no arquivo .MYI e carregados no cache de chaves MyISAM (conforme o tamanho de key_buffer_size ). Como você pode ler o arquivo .MYD de uma tabela MyISAM mais rápido? Com isso:

ALTER TABLE mytable ROW_FORMAT=Fixed;

Eu escrevi sobre isso em meus posts anteriores

InnoDB

OK, e o InnoDB? O InnoDB faz E / S de disco para consultas? Surpreendentemente, sim! Você provavelmente está pensando que sou louco por dizer isso, mas é absolutamente verdade, mesmo para consultas SELECT . Neste ponto, você provavelmente está se perguntando "Como o InnoDB está fazendo a E / S de disco para consultas?"

Tudo remonta ao InnoDB como um mecanismo de armazenamento transacional compatível com ACID . Para que o InnoDB seja Transacional, ele precisa suportar o Iin ACID, que é Isolation. A técnica para manter o isolamento para transações é feita via MVCC, Multiversion Concurrency Control . Em termos simples, o InnoDB registra a aparência dos dados antes que as transações tentem alterá-los. Onde isso é gravado? No arquivo de espaço de tabela do sistema, mais conhecido como ibdata1. Isso requer E / S de disco .

COMPARAÇÃO

Como o InnoDB e o MyISAM realizam E / S de disco, que fatores aleatórios determinam quem é mais rápido?

  • Tamanho das colunas
  • Formato da coluna
  • Conjuntos de caracteres
  • Faixa de valores numéricos (exigindo INTs grandes o suficiente)
  • Linhas sendo divididas entre blocos (encadeamento de linhas)
  • Fragmentação de dados causada por DELETEseUPDATEs
  • Tamanho da chave primária (o InnoDB possui um índice clusterizado, exigindo duas pesquisas de chave)
  • Tamanho das entradas do índice
  • A lista continua...

Portanto, em um ambiente de leitura pesada, é possível que uma tabela MyISAM com um formato de linha fixa supere as leituras do InnoDB do InnoDB Buffer Pool se houver dados suficientes sendo gravados nos logs de desfazer contidos no ibdata1 para suportar o comportamento transacional imposta aos dados do InnoDB.

CONCLUSÃO

Planeje seus tipos de dados, consultas e mecanismo de armazenamento com muito cuidado. Depois que os dados crescem, pode ser muito difícil mover os dados. Basta perguntar ao Facebook ...

RolandoMySQLDBA
fonte
11
Excelente resposta, Rolando. Eu tenho que questionar sua inclusão das afirmações incrédulas feitas por Michael Stonebreaker, que está simplesmente tentando vender seu próprio produto e não sabe nada sobre o Facebook. Depois de ouvir o Facebook apresentar sobre o MySQL várias vezes, fica claro que eles se sentem à vontade com suas escolhas.
Aaron Brown
@AaronBrown Eu ouvi Harrison Fisk no ano passado no Percona Live NYC e você está certo - o Facebook está muito feliz com o uso exclusivo do InnoDB e com o tempo gasto com maneiras de fazer todo o sistema de mudança de esquema on-line. Ele até oferece ao público a chance de trabalhar para o Facebook lidando com big data. Incluí o artigo para mostrar que alguns têm medo disso. Gostaria de ter a chance de trabalhar com grandes dados. Seria divertido e desafiador. Imagine as técnicas que existem para aprender. Claro, eu nunca tocaria MyISAM para o resto da minha vida ...
RolandoMySQLDBA
Eu também participei da conferência (e tive a sorte de poder dar uma palestra) e a apresentação de Harrison foi excelente.
Aaron Brown
20

Em um mundo simples, o MyISAM é mais rápido para leituras, o InnoDB é mais rápido para gravações.

Depois que você começar a introduzir leituras / gravações mistas, o InnoDB também será mais rápido para leituras, graças ao seu mecanismo de bloqueio de linhas.

Escrevi uma comparação dos mecanismos de armazenamento MySQL há alguns anos, que ainda se mantém fiel até hoje, descrevendo as diferenças únicas entre o MyISAM e o InnoDB.

Na minha experiência, você deve usar o InnoDB para tudo, exceto para tabelas de cache com muita leitura, onde a perda de dados devido à corrupção não é tão crítica.

Mike Peters
fonte
4
Esta resposta está desatualizada em 5 anos. O InnoDB alcançou praticamente todos os aspectos; não há mais muito argumento para usar o MyISAM. O MySQL 8.0 está no processo de remover o MyISAM completamente.
Rick James
2
E o link agora está 9 anos desatualizado.
Rick James
Correção, a resposta está desatualizada em 9 anos (quem ler a primeira frase terá sérios problemas ao fazer seu banco de dados) e o link está 11 anos desatualizado. Alcance Rick James, você está ficando para trás :).
CYREX
11
Você está certo @CYREX :-) É incrível que este post ainda esteja recebendo tráfego, 11 anos depois. Muita coisa mudou tanto na minha vida quanto na maneira como o InnoDB é otimizado. Atualmente, raramente existe uma justificativa para usar o MyISAM
Mike Peters
Hoje tive que olhar para alguns bancos de dados moribundos e os dois mecanismos ainda em uso com a versão antiga do mysql. As tabelas são InnoDB e MyISAM e minha curiosidade me levou a este post, que foi muito útil.
Farrukh Subhani 15/03
14

Para adicionar as respostas aqui, cobrindo as diferenças mecânicas entre os dois motores, apresento um estudo empírico de comparação de velocidade.

Em termos de velocidade pura, nem sempre o MyISAM é mais rápido que o InnoDB, mas, na minha experiência, ele tende a ser mais rápido nos ambientes de trabalho do PURE READ por um fator de cerca de 2,0 a 2,5 vezes. Claramente, isso não é apropriado para todos os ambientes - como outros escreveram, o MyISAM não possui coisas como transações e chaves estrangeiras.

Fiz um pouco de benchmarking abaixo - usei python para loop e a biblioteca timeit para comparações de tempo. Por interesse, também incluí o mecanismo de memória, que oferece o melhor desempenho geral, embora seja adequado apenas para tabelas menores (você encontra continuamente The table 'tbl' is fullquando excede o limite de memória do MySQL). Os quatro tipos de seleção que eu olho são:

  1. SELECTs de baunilha
  2. conta
  3. SELECTs condicionais
  4. sub-seleciona indexada e não indexada

Primeiramente, criei três tabelas usando o seguinte 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

com 'MyISAM' substituído por 'InnoDB' e 'memory' na segunda e terceira tabelas.

 

1) Baunilha seleciona

Inquerir: SELECT * FROM tbl WHERE index_col = xx

Resultado: empate

Comparação de baunilha selecionada por diferentes mecanismos de banco de dados

A velocidade delas é praticamente a mesma e, como esperado, é linear no número de colunas a serem selecionadas. O InnoDB parece um pouco mais rápido que o MyISAM, mas isso é 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) Contagens

Inquerir: SELECT count(*) FROM tbl

Resultado: MyISAM vence

Comparação de contagens por diferentes mecanismos de banco de dados

Este demonstra uma grande diferença entre MyISAM e InnoDB - MyISAM (e memória) controla o número de registros na tabela, portanto, essa transação é rápida e O (1). A quantidade de tempo necessária para a contagem do InnoDB aumenta super linearmente com o tamanho da tabela no intervalo investigado. Suspeito que muitas das acelerações das consultas MyISAM observadas na prática sejam devidas a efeitos semelhantes.

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) Seleções condicionais

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

Resultado: MyISAM vence

Comparação de seleções condicionais por diferentes mecanismos de banco de dados

Aqui, MyISAM e memória executam aproximadamente o mesmo e superam o InnoDB em cerca de 50% para tabelas maiores. Esse é o tipo de consulta para a qual os benefícios do MyISAM parecem 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-seleciona

Resultado: InnoDB vence

Para esta consulta, criei um conjunto adicional de tabelas para a sub-seleção. Cada uma é simplesmente duas colunas de BIGINTs, uma com um índice de chave primária e outra sem nenhum índice. Devido ao grande tamanho da tabela, não testei o mecanismo de memória. O comando de criação da tabela SQL foi

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

onde mais uma vez, 'MyISAM' é substituído por 'InnoDB' na segunda tabela.

Nesta consulta, deixo o tamanho da tabela de seleção em 1000000 e, em vez disso, vario o tamanho das colunas sub-selecionadas.

Comparação de sub-seleções por diferentes mecanismos de banco de dados

Aqui o InnoDB vence facilmente. Depois de chegarmos a uma tabela de tamanho razoável, os dois mecanismos escalam linearmente com o tamanho do sub-select. O índice acelera o comando MyISAM, mas curiosamente tem pouco efeito na velocidade do 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) )

Acho que a mensagem de tudo isso é que, se você realmente está preocupado com a velocidade, precisa comparar as consultas que está fazendo, em vez de fazer suposições sobre qual mecanismo será mais adequado.

StackG
fonte
11
Gosto da sua resposta porque é a favor de quem você avalia e decide. Dois sistemas não se beneficiam da mesma maneira com diferentes mecanismos de armazenamento e é necessária a devida diligência para escolher um mecanismo de armazenamento. +1 para você e Bem-vindo ao DBA StackExchange !!!
RolandoMySQLDBA
11
Além disso, consulte minha postagem dba.stackexchange.com/questions/1/… junto com as outras respostas. Sua postagem meio que vai além.
RolandoMySQLDBA
SELECT * FROM tbl WHERE index_col = xx- Aqui estão dois fatores que podem levar a mais variações no gráfico: Chave primária versus chave secundária; índice é armazenado em cache vs não.
Rick James
2
SELECT COUNT(*)é um vencedor claro para o MyISAM até você adicionar uma WHEREcláusula.
21715 Rick Rick James
Suponho que meu argumento é que cada consulta precise ser comparada separadamente. Incluí o código na resposta - se você quiser testar uma consulta diferente, seja meu convidado - ou seja explícita a consulta que deseja, e eu a adicionarei.
StackG
4

O que é mais rápido? Qualquer um pode ser mais rápido. YMMV.

Qual você deve usar? InnoDB - seguro contra falhas, etc, etc.

Rick James
fonte
por favor, defina "etc, etc."
Dellasavia
11
@dellasavia - O "etc" mais recente é que a Oracle planeja remover o MyISAM. Eles são tão confiantes no InnoDB.
Rick James