InnoDBとMyISAMのどちらが速いですか?


54

MyISAMがInnoDBよりも「高速」である方法

  • MyISAMはデータのディスク読み取りを行う必要がありますか?
  • InnoDBはインデックスとデータにバッファープールを使用し、MyISAMはインデックスにのみ使用しますか?

MyISAMでは、OSがデータブロックをキャッシュできるため、常に「データのディスク読み取りを行う」とは限りませ
リックジェームズ

回答:


68

MyISAMがInnoDBを高速化できる唯一の方法は、このユニークな状況下である

MyISAM

読み取られると、MyISAMテーブルのインデックスは.MYIファイルから1回読み取られ、MyISAMキーキャッシュ(key_buffer_sizeでサイズが設定されている)にロードされます。MyISAMテーブルの.MYDの読み取りを高速化するにはどうすればよいですか?これとともに:

ALTER TABLE mytable ROW_FORMAT=Fixed;

これについては過去の投稿で書きました

InnoDB

OK、InnoDBはどうですか?InnoDBはクエリに対してディスクI / Oを実行しますか?驚くべきことに、そうです!! あなたはおそらく私がそれを言うことに夢中になっていると思っているでしょうが、それはSELECTクエリに対してさえも絶対に真実です。この時点で、おそらく「InnoDBはクエリのディスクI / Oをどのように行っているのでしょうか?」

すべては、ACIDに準拠したトランザクションストレージエンジンであるInnoDBに戻ります。InnoDBがトランザクション対応であるためには、Iin をサポートする必要がACIDあります。これは分離です。トランザクションの分離を維持するための手法は、MVCC、Multiversion Concurrency Controlを介して行われます。簡単に言えば、InnoDBは、トランザクションがデータを変更しようとする前にデータがどのように見えるかを記録します。それはどこで記録されますか?システムテーブルスペースファイルでは、ibdata1として知られています。それにはディスクI / Oが必要です。

比較

InnoDBとMyISAMの両方がディスクI / Oを行うため、誰がより高速であるかを決定するランダム要因は何ですか?

  • 列のサイズ
  • 列フォーマット
  • キャラクターセット
  • 数値の範囲(十分な大きさのINTが必要)
  • ブロック間で分割されている行(行の連鎖)
  • DELETEsとによって引き起こされるデータの断片化UPDATEs
  • 主キーのサイズ(InnoDBにはクラスター化インデックスがあり、2つのキー検索が必要です)
  • インデックスエントリのサイズ
  • リストが続きます...

したがって、大量の読み取り環境では、トランザクションの動作をサポートするためにibdata1に含まれるundoログに十分なデータが書き込まれている場合、固定行形式のMyISAMテーブルがInnoDBバッファープールからInnoDB読み取りを上回る可能性がありますInnoDBデータに課されます。

結論

データタイプ、クエリ、およびストレージエンジンを実際に慎重に計画してください。データが大きくなると、データの移動が非常に困難になる場合があります。Facebookに聞いてください...


1
素晴らしい答え、ロランド。マイケル・ストーンブレイカーが自分の製品を売ろうとしているだけで、Facebookについては何も知らないというマイケル・ストーンブレイカーの信じられないほどの主張が含まれていることに疑問を持たなければなりません。MySQLについてのFacebookのプレゼンテーションを何回か聞いたことがありますが、彼らが自分の選択に満足していることは明らかです。
アーロンブラウン

@AaronBrown昨年、Percona Live NYCでHarrison Fiskを聞いたことがありますが、FacebookはInnoDBの独占的な使用と、オンラインスキーマ変更システム全体を実行する方法を考え出すことに時間を費やしていることに非常に満足しています。彼は、ビッグデータを処理するFacebookで働く機会を聴衆に提供します。一部の人がそれについて恐れていることを示すために、記事を含めました。膨大なデータを扱う機会を歓迎します。楽しさとやりがいがあります。学ぶためのテクニックを想像してください。もちろん、私の生涯にわたってMyISAMに触れることはありません
...-RolandoMySQLDBA

私もその会議に参加し(そして、講演することができて幸運でした)、ハリソンのプレゼンテーションは素晴らしかったです。
アーロンブラウン

20

単純な世界では、MyISAMは読み取りが高速で、InnoDBは書き込みが高速です。

読み取り/書き込みの混合の導入を開始すると、InnoDBは行ロックメカニズムのおかげで読み取りも高速になります。

数年前にMySQLストレージエンジンの比較を書きましたが、 MyISAMとInnoDBのユニークな違いを概説し、今日でも同じです。

私の経験では、読み取りによるキャッシュテーブル以外のすべてにInnoDBを使用する必要があります。ただし、破損によるデータの損失はそれほど重要ではありません。


4
この回答は5年前のものです。 InnoDBは事実上すべての方法で追いついています。MyISAMを使用することについての議論はもはやありません。MySQL 8.0は、MyISAMをまとめて削除するプロセスにあります。
リックジェームズ

2
そして、リンクは現在9年遅れています。
リックジェームズ

修正、答えは9年古いです(最初の文を読んでいる人はだれでも、データベースを実行するときにいくつかの深刻な問題があります)。リンクは11年古いです。リック・ジェームスを追いかけてください、あなたは遅れています:)。
サイレックス

1
あなたは正しいです@CYREX :-)その驚くべきことに、この投稿は11年後もトラフィックを獲得しています。私の人生とInnoDBの最適化方法の両方で、大きな変化がありました。今日では、MyISAMテーブルを使用する正当な理由めったにあります
マイク・ピーターズ

私は今日いくつかの死にかけているデータベースを見なければなりませんでした、そして、両方のエンジンはmysqlの古いバージョンでまだ使用中です。テーブルはInnoDBとMyISAMの両方であり、私の好奇心からこの投稿に至りました。これは非常に役に立ちました。
ファルークスバニ

14

ここで、2つのエンジンの機械的な違いをカバーする応答に追加するために、経験的な速度比較研究を提示します。

純粋な速度という点では、常にMyISAMがInnoDBより速いというわけではありませんが、私の経験では、PURE READ作業環境では約2.0〜2.5倍速くなる傾向があります。明らかに、これはすべての環境に適しているわけではありません。他の人が書いているように、MyISAMにはトランザクションや外部キーなどがありません。

以下のベンチマークを少し行いました-ループにpythonを、タイミング比較にtimeitライブラリを使用しました。興味深いことに、メモリエンジンも含めました。これは、より小さなテーブルにのみ適していThe table 'tbl' is fullますが、MySQLのメモリ制限を超えたときに頻繁に発生しますが、全体的に最高のパフォーマンスを提供します。私が検討している4つのタイプの選択は次のとおりです。

  1. バニラセレクト
  2. カウント
  3. 条件付きSELECT
  4. インデックス付きおよび非インデックス付きの副選択

まず、次のSQLを使用して3つのテーブルを作成しました

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

2番目と3番目のテーブルの「InnoDB」と「memory」を「MyISAM」に置き換えます。

 

1)バニラが選択

クエリ: SELECT * FROM tbl WHERE index_col = xx

結果:描く

異なるデータベースエンジンによるバニラセレクトの比較

これらの速度はすべてほぼ同じであり、予想されるように、選択される列の数は線形です。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の2つの列であり、1つは主キーインデックス付き、もう1つはインデックスなしです。テーブルサイズが大きいため、メモリエンジンをテストしませんでした。SQLテーブル作成コマンドは

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

ここでも、2番目のテーブルの「InnoDB」の代わりに「MyISAM」が使用されています。

このクエリでは、選択テーブルのサイズを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
私はあなたの答えが好きです。なぜなら、あなたがベンチマークして決定する人に有利だからです。2つのシステムが異なるストレージエンジンから同じように恩恵を受けることはなく、ストレージエンジンを選択するにはデューデリジェンスが必要です。+1して、DBA StackExchangeへようこそ!!!
RolandoMySQLDBA

1
また、他の回答とともに、私の投稿dba.stackexchange.com/questions/1/…を参照してください。あなたの投稿は、それ以上のものです。
RolandoMySQLDBA

SELECT * FROM tbl WHERE index_col = xx-グラフのバリエーションを増やす可能性がある2つの要因を以下に示します。主キーと副キー。インデックスはキャッシュされますが、キャッシュされません。
リックジェームズ

2
SELECT COUNT(*)WHERE条項を追加するまで、MyISAMの明確な勝者です。
リックジェームズ

私のポイントは、各クエリを個別にベンチマークする必要があると思います。私は答えにコードを含めました-別のクエリを試してみたい場合は、ゲストにしてください-または、どのクエリが必要かを明示して、それを追加します。
StackG

4

どちらが速いですか?どちらかが速いかもしれません。YMMV。

どちらを使うべきですか?InnoDB-クラッシュセーフなど


「など」を定義してください
デルラサビア

1
@dellasavia-最新の「など」は、OracleがMyISAMの削除を計画していることです。InnoDBには自信があります。
リックジェームズ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.