MyISAM与InnoDB的比较

892

我正在处理一个项目,其中包含大量的数据库写入操作,我会说(70%插入和30%读取)。这个比例也包括更新操作,我认为一次更新操作相当于一次读取和一次写入操作。这些读取操作可以是不准确的(例如,我在读取时不需要100%准确的信息)。

所涉及的任务每小时将执行超过100万个数据库事务。

我在网上阅读了许多关于MyISAM和InnoDB之间区别的文章,对于我将用于此任务的特定数据库/表格而言,MyISAM似乎是显而易见的选择。从我所看到的内容来看,如果需要事务,则InnoDB非常适合,因为它支持行级锁定。

有没有人有这种负载(或更高)的经验? MyISAM是否是最好的选择?


14
MySQL Performance Blog是这类问题的绝佳资源。 - ceejayoz
3
这取决于你的系统是面向 OLTP 还是更偏向数据仓库导向(其中大部分写入都是批量加载)。 - nos
38
MyISAM不支持行级锁定、事务,甚至不支持外键...它甚至无法提供ACID,几乎不能被认为是一个合适的数据库!这就是为什么自MySQL 5.5以来,默认引擎为InnoDB的原因......但由于某种原因,在PhpMyAdmin中创建的表仍然使用MyISAM作为默认引擎,所以很多业余数据库仍在运行MyISAM。 - BlueRaja - Danny Pflughoeft
请查看此链接:http://www.rackspace.com/knowledge_center/article/mysql-engines-myisam-vs-innodb - user2543022
查看所有比较的MySql数据库引擎。 - Somnath Muluk
我刚刚在一个有80,000条记录的数据库上进行了测试,结果表明MyIsam比其他引擎快几秒钟。 - Peter Gruppelaar
25个回答

539

我已经在表格中简要讨论了这个问题,以便您可以得出是否选择InnoDBMyISAM的结论。

以下是应使用哪种db存储引擎的小概述:

                                                 MyISAM   InnoDB
----------------------------------------------------------------
需要全文搜索                                  是       5.6.4
----------------------------------------------------------------
需要事务                                            是
----------------------------------------------------------------
频繁进行select查询                          是      
----------------------------------------------------------------
频繁进行insert、update和delete操作           是
----------------------------------------------------------------
行锁定(单个表上的多处理)                         是
----------------------------------------------------------------
关系型基础设计                                     是

总结

  • 在几乎所有情况下,InnoDB是最好的选择
  • 但是,如果频繁读取而几乎没有写入,则使用MyISAM
  • 在MySQL <= 5.5中进行全文搜索时,请使用MyISAM

11
InnoDB 在 MySQL 5.6 中有全文索引,但迄今为止,它们并不真正准备好用于生产。 - Bill Karwin
3
根据12.9. 全文搜索函数,“全文索引只能与InnoDB或MyISAM表一起使用”。对于MySQL >= 5.6来说,这看起来没问题,然而对于MySQL 5.5,同样的页面仍然说“全文索引只能与MyISAM表一起使用”。上面的表格应该被更新以反映MySQL版本之间的差异。不幸的是,目前为止,MySQL 5.5似乎是标准。 - Hibou57
2
InnoDB - full-text: 5.6.4 是什么意思?它是否存在? - user4795756
2
MyISAM 也在内部存储行计数。因此,在 MyISAM 中,Count() 函数几乎是免费的,而在 InnoDB 中需要花费相当长的时间。 - Hedeshy
3
很好的表格,但是添加一个关于质量和稳定性的行,MyIsam = 否,innoDB = 是,会使其更加出色。 - pilavdzice
显示剩余6条评论

271

我不是一个数据库专家,也没有从经验中说话。然而:

MyISAM表使用表级锁定。根据您的流量估计,您每秒有近200个写入操作。使用MyISAM时,在任何时候只能进行其中之一。您必须确保硬件跟得上这些交易以避免被淹没,即单个查询不能超过5ms。

这提示我您需要一个支持行级锁定的存储引擎,即InnoDB。

另一方面,编写几个简单的脚本来模拟每个存储引擎的负载,然后比较结果应该是相当容易的。


12
接近200吗?如果他的平均交易包含2.5个查询,那就是[(2.5*1M)/3600秒 =]接近700。 - Ozzy
13
我不同意“单个查询最多只需要5毫秒”的说法,因为你做出了两个不太可能的假设:A:所有查询都需要相同的表格;B:只有一个连接可用!我应该告诉你,具有高内存的Linux和MySQL 5.5设置可以支持多达10,000个同时连接(请参见:http://dev.mysql.com/doc/refman//5.5/en/too-many-connections.html)。 - Ozzy
155
当一张表被表锁定时,同一时间只能运行一个查询。即使服务器支持10000个并发连接,每个连接在该表被锁定期间都会被阻塞。 - Ryaner
2
另外,了解到MyISAM支持空间索引而InnoDB不支持可能会有所帮助。而且,尽管MyISAM不会阻止创建外键,但似乎并不使用它们。 - kriver
4
在MyISAM表中无法使用外键。您可以在CREATE TABLE语句中包含外键定义,但它们会被忽略。 - ypercubeᵀᴹ
1
应该指出的是,MyISAM在插入方面的性能比Innodb要高得多,如果您使它们足够快,并且不关心偶尔锁定一两秒钟的情况,那么MyISAM可能是正确的选择。这取决于您的情况以及运行的查询。如果它们非常简单,只是针对一个非常快速的单行选择,特别是如果在固态硬盘上运行,则效果很好。如果进行连接或更新,则MyISAM变得有问题,因为它会锁定更长时间。 - Chris Seline

200

人们经常谈论性能、读写操作、外键等等,但在我看来,存储引擎还必须具备一项非常重要的功能:原子更新。

试试这个:

  1. 对你的MyISAM表执行一个需要5秒钟时间的UPDATE操作。
  2. 当UPDATE正在进行中(例如2.5秒钟时),按Ctrl-C中断它。
  3. 观察表格的效果。有多少行被更新了?有多少行没有被更新?这个表是可读的吗,或者在你按下Ctrl-C时已经损坏了吗?
  4. 使用UPDATE操作打断正在进行的查询,尝试相同的实验,针对InnoDB表执行操作。
  5. 观察InnoDB表格。 行被更新了。InnoDB确保了你具有原子更新,如果无法提交完整的更新,则回滚整个更改。此外,表格不会损坏。即使使用 killall -9 mysqld 来模拟崩溃,也可以正常工作。

当然,性能是令人向往的,但不丢失数据应该比性能更加重要。


5
值得一提的是,ACID数据库的其他特性——一致性、隔离性和持久性——MyISAM也不支持。 - Bill Karwin
Control-C不应破坏表格 - 就像CHECK TABLE将返回成功并且所有查询都将继续而没有错误一样。MyISAM将中止更新而不更新所有记录,但表格将保持内部结构完整性。使用SIGTERM杀死mysqld将产生相同的效果。但是,如果您给它SIGKILL(kill -9)或某些崩溃信号(或者当它遇到错误时自己赚取它),或者如果操作系统崩溃/断电,则情况就不同了 - 您可以看到MyISAM级别的损坏。 - Sasha Pachev
1
InnoDB 也可能会出现严重的自我损坏,通常比 MyISAM 更严重。 ACID 的讽刺是我们有“要么全部,要么全不要”的概念。因此,当 InnoDB 无法提供全部时,它会发生内部断言并拒绝运行,因为某些结构中的一个字节是错误的。90% 的情况下,它可以被忽略,并且最多只会影响一个表。最近的 Percona 服务器有应对此问题的选项- innodb_pass_corrupt_table。 - Sasha Pachev
1
我在过去的三天中一直在搜索这种信息,现在终于找到了。InnoDB是最好的。谢谢Bill Karwin - user3833682
5
现在很少使用MyISAM。在我的上一份工作中,我们只在一个服务器上的一个表格中使用了MyISAM,原因是MyISAM能够以比InnoDB更小的空间存储这个特定的表格。由于磁盘空间受限,我们不得不使用MyISAM,直到我们可以将数据库转移到另一个服务器。在我的新工作中,已经有了每个表格都必须使用InnoDB的政策。 - Bill Karwin
显示剩余2条评论

138

我曾经在一个使用MySQL的高容量系统上工作过,尝试过MyISAM和InnoDB两种引擎。

我发现MyISAM的表级锁定会对我们的工作负载造成严重的性能问题,这听起来与你们的情况相似。不幸的是,我也发现InnoDB的性能也不如我所希望的那样好。

最终,我通过将数据分片来解决争用问题,使得插入操作进入“热”表格,而查询操作从未查询过热表格。

这也允许删除操作(数据是时间敏感的,我们只保留X天)发生在“陈旧”的表格上,这些表格也不会被查询操作触及。InnoDB在批量删除方面的性能似乎很差,因此,如果你计划清除数据,你可能需要设计一种结构,使得旧数据位于一个可以简单删除的“陈旧”表格中,而不是在该表格上运行删除操作。

当然,我不知道你的应用程序是什么,但希望这些信息可以为你提供关于MyISAM和InnoDB的一些问题的见解。


3
“最后,我通过将数据分片来解决争议问题,以便插入操作进入一个“热”表中,而选择操作从未查询过热表。” - 这不是缓冲池的基本作用吗? - BlueRaja - Danny Pflughoeft
15
丹尼 - 不是很重要。调整服务器设置虽然很重要,但绝不是谨慎构建模式的替代品。如果你有一个比可用RAM大得多且访问模式随机地涉及整个数据库的DB,则全世界的缓冲池调整都无法帮助您。只有了解数据和访问模式,才能通过仔细的设计来减轻很多痛苦。 - alanc10n

68

虽然有点晚了,但是这里有一篇我几个月前写的非常全面的文章,详细介绍MYISAM和InnoDB之间的主要区别。拿杯茶(或许还有饼干),享受阅读。


MyISAM和InnoDB的主要区别在于引用完整性和事务处理。此外,还有其他差异,例如锁定、回滚和全文搜索。
引用完整性确保表之间的关系保持一致。更具体地说,这意味着当一个表(例如列表)具有指向另一个表(例如产品)的外键(例如产品ID)时,当指向的表发生更新或删除时,这些更改会级联到链接表中。在我们的示例中,如果产品被重命名,链接表的外键也将更新;如果从“产品”表中删除了一个产品,则指向已删除条目的任何列表也将被删除。此外,任何新列表都必须将该外键指向有效的现有条目。
InnoDB是一个关系型数据库管理系统(RDBMS),因此具有引用完整性,而MyISAM则没有。
数据使用数据操作语言(DML)语句(例如SELECT、INSERT、UPDATE和DELETE)来管理表中的数据。事务将两个或多个DML语句组合成单个工作单元,因此要么应用整个单元,要么不应用。

MyISAM不支持事务,而InnoDB支持。

如果在使用MyISAM表时操作被中断,操作将立即中止,并且受影响的行(甚至是每行内的数据)仍然受影响,即使操作没有完成。

如果在使用InnoDB表时操作被中断,由于它使用了具有原子性的事务,任何未完成的事务都不会生效,因为没有提交。

表锁定与行锁定

当对MyISAM表运行查询时,将锁定正在查询的整个表。这意味着当前查询完成后才能执行后续查询。如果您正在读取大型表格和/或频繁进行读写操作,则可能会导致大量查询积压。

当对InnoDB表运行查询时,只锁定涉及的行,其余表格仍可用于CRUD操作。这意味着可以在同一表上同时运行查询,前提是它们不使用相同的行。

这个InnoDB中的特性被称为并发性。虽然并发性很棒,但是对于一些表格来说,存在一个主要缺点,即在内核线程之间切换会产生开销,因此您应该设置内核线程的限制,以防止服务器停止运行。
事务和回滚:在MyISAM中运行操作时,更改将被设置;在InnoDB中,这些更改可以被回滚。控制事务最常用的命令是COMMIT、ROLLBACK和SAVEPOINT。1. COMMIT - 您可以编写多个DML操作,但只有在进行COMMIT时才会保存更改。2. ROLLBACK - 您可以放弃尚未提交的任何操作。3. SAVEPOINT - 设置列表中的一个操作点,可以将ROLLBACK操作回滚到该点。
可靠性:MyISAM不提供数据完整性 - 硬件故障、非正常关闭和取消操作可能导致数据损坏。这将需要对索引和表进行完全修复或重建。
InnoDB使用事务日志、双写缓冲区、自动校验和验证来防止数据损坏。在进行任何更改之前,InnoDB会将事务之前的数据记录到名为ibdata1的系统表空间文件中。如果出现崩溃,InnoDB将通过重放这些日志来自动恢复。
全文索引在MySQL 5.6.4版本之前的InnoDB不支持。截至本篇文章撰写时,许多共享主机提供商的MySQL版本仍低于5.6.4,这意味着InnoDB表不支持全文索引。
然而,这并不是使用MyISAM的有效理由。最好选择支持最新MySQL版本的托管提供商。不过,使用FULLTEXT索引的MyISAM表可以转换为InnoDB表。
总之,InnoDB应该是您默认的存储引擎选择。当它们满足特定需求时,选择MyISAM或其他数据类型。

我在编写一个PHP会话校验脚本,大部分密钥都是随机字符串[az09]… Innodb执行“INSERT ON DUPLICATE KEY UPDATE”需要超过30ms的时间,因此我尝试了MyISAM,现在只需要不到1ms的时间… 我看到许多答案说InnoDB很难处理“不可排序”的(随机字符串)唯一键…您对此有什么建议吗?实际上,我一直在思考使用MyISAM会有什么影响,但您出色的回答让我意识到这是特定情况下的正确选择。 - Louis Loudog Trottier

64

如果需要进行更多的读写操作,你将受益于InnoDB。因为InnoDB提供的是行级锁而不是表级锁,你的 SELECT 语句可以并发执行,不仅仅是彼此之间,还可以与许多 INSERT 语句一起执行。但是,除非你打算使用SQL事务,否则将InnoDB提交刷新设置为2 (innodb_flush_log_at_trx_commit)。这样可以节约大量的原始性能,否则在从MyISAM转移到InnoDB时会损失很多性能。

另外,请考虑添加复制功能。这可以给你一些读取扩展性,并且由于你说明了读取不必实时,所以可以允许复制稍微滞后一些时间。只要确保它可以在除了最繁忙的流量之外的任何情况下追上来,否则它总是会落后并永远赶不上。然而,如果采用这种方式,我强烈建议你将从从库中读取和复制滞后管理隔离到数据库处理程序中。如果应用程序代码不知道这一点,那么这样做会更简单。

最后,请注意不同的表负载。你不会在所有表上拥有相同的读写比例。一些较小的表几乎全部都是读取,可以继续使用MyISAM。同样,如果你有一些表几乎全部都是写入,那么你可能会从 INSERT DELAYED 中受益,但这仅适用于MyISAM(对于InnoDB表,DELAYED子句将被忽略)。

但是要进行基准测试以确保效果。


4
你所指的"InnoDB commit flush"是指innodb_flush_log_at_trx_commit吗? - ceejayoz
2
我发现你的帖子非常有用 - 谢谢。目前正在评估何时在我的表中使用MyISAM/InnoDB,你的帖子很有帮助。干杯。 - starmonkey
2
http://dev.mysql.com/doc/refman/5.5/en/insert-delayed.html 表示:对于 MyISAM 表,如果数据文件中间没有空闲块,则支持并发 SELECT 和 INSERT 语句。在这些情况下,您很少需要使用 MyISAM 的 INSERT DELAYED。 - tymtam
非常有信息量的帖子。我和楼主有同样的问题,但是你的帖子让我对我的数据库引擎决定感到放心。谢谢!++ - Joe Majewski
快速记录:在5.7版本中,delayed已不再受支持。您可能需要尝试使用LOW_PRIORITY进行测试。 - webmat
请注意,我在这里的建议已经开始过时了。 - staticsan

60

除了这些已经提到的机械差异,我还提供了一项实证速度比较研究。

就纯速度而言,并不总是MyISAM比InnoDB更快,但根据我的经验,在纯读取工作环境下,它往往比InnoDB快2.0-2.5倍。显然,这并不适用于所有环境——正如其他人所写的,MyISAM缺乏事务和外键等功能。

我在下面进行了一些基准测试,使用Python进行循环操作,使用timeit库进行时间比较。出于兴趣,我还包括了内存引擎,它在整体性能方面表现最佳,尽管它只适用于较小的表格(当超过MySQL内存限制时,你会不断遇到“表'tbl'已满”的情况)。我观察的四种选择类型为:

  1. 普通SELECT
  2. 计数
  3. 条件查询
  4. 带有索引和非索引的子查询

首先,我使用以下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

在第二个和第三个表中,将'MyISAM'替换为'InnoDB'和'memory'。

 

1) 原始查询

查询:SELECT * FROM tbl WHERE index_col = xx

结果:平局

Comparison of vanilla selects by different database engines

它们的速度基本相同,并且预期是随要选择的列数线性增长的。InnoDB似乎比MyISAM快一些,但这差异微不足道。

代码:

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

查询:SELECT count(*) FROM tbl

结果:MyISAM 胜出

不同数据库引擎的计数比较

这个示例展示了 MyISAM 和 InnoDB 之间的巨大差异 - MyISAM(和内存)跟踪表中记录的数量,因此此事务很快且 O(1)。在我调查的范围内,InnoDB 进行计数所需的时间随着表大小呈超线性增长。我怀疑实际观察到的 MyISAM 查询加速的许多效果都是由于类似的影响。

代码:

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) 条件查询

查询语句:SELECT * FROM tbl WHERE value1<0.5 AND value2<0.5 AND value3<0.5 AND value4<0.5

查询结果:MyISAM 胜出

不同数据库引擎条件查询的比较

在这个例子中,MyISAM 和内存表性能差不多,并且在处理更大的数据表时比 InnoDB 快50%左右。这是 MyISAM 的优势最大的查询类型之一。

代码:

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) 子查询

结果:InnoDB胜出

针对这个查询,我创建了一个额外的子查询表。每个表只有两列BIGINT,一列是主键索引,另一列没有任何索引。由于表格较大,我没有测试内存引擎。SQL表格创建命令如下:

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

在第二个表中,'MyISAM' 再次被替换为 'InnoDB'。

在这个查询中,我将选择表的大小保持在1000000,并改变子选择列的大小。

不同数据库引擎的子选择比较

这里InnoDB轻松胜出。当我们达到一个合理大小的表时,两个引擎都随子选择大小线性扩展。索引加速了MyISAM命令,但有趣的是对InnoDB速度影响很小。subSelect.png

代码:

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

我认为所有这些的重点是,如果你真的关心速度,你需要基准测试你正在执行的查询,而不是对哪个引擎更合适做任何假设。


1
性能并不总是唯一的考虑因素,那么稳定性方面的图表呢?如果引擎崩溃并且不支持基本的数据库功能,那么它对任何事情都没有用处。 - pilavdzice
1
如果my.cnf文件没有针对InnoDB进行优化,那么MyISAM在大多数情况下可能会胜过InnoDB。您并没有提及您的my.cnf文件的具体内容,这实际上是影响InnoDB性能最重要的因素。 - itoctopus
谢谢itoctopus - 我很想听听您推荐的任何优化建议。这些测试中使用的完整代码在上面,随意重复实验并告诉我们是否发现结果有显著变化。 - StackG

33

虽然有些离题,但为了文档目的和完整性,我想补充以下内容。

通常使用InnoDB将会导致应用程序更简单,可能也更少出现Bug。因为你可以将所有引用完整性(Foreign Key-constraints)放入数据模型中,所以需要的应用程序代码远不如使用MyISAM那么多。

每次插入、删除或替换记录时,都需要检查和维护关系。例如,如果你删除一个父项,所有子项也应该被删除。例如,在一个简单的博客系统中,如果你删除一个Blogposting记录,你还必须删除评论记录和点赞等。在InnoDB中,这是由数据库引擎自动完成的(如果在模型中指定了约束条件),不需要应用程序代码。在MyISAM中,这必须被编写到应用程序中,而在Web服务器中非常困难。Web服务器天生就是非常并发/并行的,因为这些操作应该是原子的,而MyISAM不支持真正的事务,所以在Web服务器上使用MyISAM是有风险的 / 容易出错。

此外,在大多数一般情况下,InnoDB将表现得更好,其中一个原因是能够使用记录级别锁定,而不是表级别锁定。不仅在写操作比读操作频繁的情况下,还在处理大数据集上的复杂连接时。我们注意到,仅通过使用InnoDB表而不是MyISAM表,就可以将非常大的连接(花费几分钟)的性能提高三倍。

我认为,在使用MySQL时,通常应该选择InnoDB(使用一个完整的3NF数据模型和引用完整性)。MyISAM只应该在特定情况下使用。它很可能会表现得更差,导致应用程序变得更大且更容易出现Bug。

话虽如此,数据建模是一种很少在Web设计师/程序员中发现的艺术。不是针对你们,但这解释了为什么MyISAM被如此广泛地使用。


31

InnoDB提供:

ACID transactions
row-level locking
foreign key constraints
automatic crash recovery
table compression (read/write)
spatial data types (no spatial indexes)

在InnoDB中,除了TEXT和BLOB之外的行数据最多可以占用8,000个字节。InnoDB不支持全文本索引。在InnoDB中,当未使用WHERE、GROUP BY或JOIN时,COUNT(*)的执行速度比MyISAM慢,因为行计数没有在内部存储。InnoDB将数据和索引存储在一个文件中。InnoDB使用缓冲池来缓存数据和索引。

MyISAM提供:

fast COUNT(*)s (when WHERE, GROUP BY, or JOIN is not used)
full text indexing
smaller disk footprint
very high table compression (read only)
spatial data types and indexes (R-tree)

MyISAM具有表级锁定,但没有行级锁定。没有事务。没有自动崩溃恢复功能,但它提供了修复表功能。没有外键约束。与InnoDB表相比,MyISAM表在磁盘上通常更紧凑。如果需要,可以通过myisampack压缩MyISAM表以进一步减小尺寸,但表会变成只读。MyISAM在一个文件中存储索引,在另一个文件中存储数据。MyISAM使用关键字缓存索引,并将数据缓存管理留给操作系统。

总的来说,我推荐大多数情况下使用InnoDB,仅在特殊用途时使用MyISAM。新的MySQL版本中InnoDB现在是默认引擎。


2
就此而言,在InnoDB中,VARCHAR也可以像BLOB和TEXT一样进入溢出页。所有这些数据类型在内部存储方式上都是相似的。 - Bill Karwin
很好知道,@BillKarwin!我们的应用程序中大量使用VARCHAR,并且让VARCHAR对这个约8kB的限制做出贡献有点令人担忧。 - rinogo
请参阅http://www.mysqlperformanceblog.com/2010/02/09/blob-storage-in-innodb/以获取更多详细信息。 - Bill Karwin
答案已经过时了,因为MySQL 5.6+版本的InnoDB引擎现在也支持全文索引,而且MySQL 5.5+/5.7+还支持空间数据类型(5.5+)空间索引(r-tee)(5.7+)。为了获得最佳支持,您至少需要使用MySQL版本5.7+。 - Raymond Nijland

25

这个问题和大多数答案都已经过时了.

是的,MyISAM比InnoDB更快是一个老掉牙的说法,注意问题的日期:2008年。现在已经过去了将近十年,InnoDB已经取得了重大的性能进展。

那张引人注目的图表只是针对一种情况,即COUNT(*)没有WHERE子句的情况下,MyISAM才获胜。但你真的会把时间花在这样的操作上吗?

如果您运行并发测试,InnoDB很可能会获胜,甚至击败MEMORY

如果您在对SELECTs进行基准测试时执行任何写操作,则由于表级锁定,MyISAM和MEMORY可能会输。

实际上,Oracle非常确定InnoDB更好,以至于他们已经从8.0中除去了MyISAM。

问题是在5.1早期编写的。从那时起,这些主要版本被标记为“一般可用”:

  • 2010年:5.5(12月发布的.8)
  • 2013年:5.6(2月发布的.10)
  • 2015年:5.7(10月发布的.9)
  • 2018年:8.0(4月发布的.11)

最重要的是:不要使用MyISAM


3
MySQL数据库技术正在不断进步,但是StackOverflow上的问题和答案仍然停留在过去。MyISAM和InnoDB之间的主要区别不再仅仅是关于服务器的“负载”,而更多地涉及到对参照完整性事务的支持,以及并发性可恢复性方面的差异。(+10) - spencer7593

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接