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?
Como o MyISAM pode ser "mais rápido" que o InnoDB se
Respostas:
A única maneira de o MyISAM ser mais rápido que o InnoDB estaria sob essa circunstância única
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
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 I
in 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 .
Como o InnoDB e o MyISAM realizam E / S de disco, que fatores aleatórios determinam quem é mais rápido?
DELETEs
eUPDATEs
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.
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 ...
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.
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 full
quando excede o limite de memória do MySQL). Os quatro tipos de seleção que eu olho são:
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.
Inquerir: SELECT * FROM tbl WHERE index_col = xx
Resultado: empate
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) )
Inquerir: SELECT count(*) FROM tbl
Resultado: MyISAM vence
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) )
Inquerir: SELECT * FROM tbl WHERE value1<0.5 AND value2<0.5 AND value3<0.5 AND value4<0.5
Resultado: MyISAM vence
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) )
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.
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.
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.
SELECT COUNT(*)
é um vencedor claro para o MyISAM até você adicionar uma WHERE
cláusula.
O que é mais rápido? Qualquer um pode ser mais rápido. YMMV.
Qual você deve usar? InnoDB - seguro contra falhas, etc, etc.