Który jest szybszy, InnoDB lub MyISAM?

54

Jak MyISAM może być „szybszy” niż InnoDB, jeśli

  • MyISAM musi dokonać odczytu dysku dla danych?
  • InnoDB używa puli buforów do indeksów i danych, a MyISAM tylko do indeksu?
jcho360
źródło
MyISAM pozwala systemowi na buforowanie bloków danych , więc nie zawsze „odczytuje dane z dysku”.
Rick James,

Odpowiedzi:

68

Jedynym sposobem, w jaki MyISAM może być szybszy niż InnoDB, byłby w tych wyjątkowych okolicznościach

MyISAM

Podczas czytania, indeksy stole MyISAM można przeczytać raz z pliku .myi i ładowane w MyISAM Key Cache (jak sortowano key_buffer_size ). Jak można szybciej czytać tabelę MyISAM .MYD? Z tym:

ALTER TABLE mytable ROW_FORMAT=Fixed;

Pisałem o tym w moich poprzednich postach

InnoDB

OK, a co z InnoDB? Czy InnoDB wykonuje jakieś dyskowe operacje wejścia / wyjścia dla zapytań? Zaskakujące, że tak !! Prawdopodobnie myślisz, że jestem szalony, że to mówię, ale to absolutna prawda, nawet w przypadku zapytań SELECT . W tym momencie prawdopodobnie zastanawiasz się: „Jak, na świecie, InnoDB robi dyskowe operacje we / wy dla zapytań?”

Wszystko to wraca do tego, że InnoDB jest silnikiem transakcyjnym do przechowywania danych na podstawie ACID . Aby InnoDB bycia transakcyjna, to musi wspierać Iin ACID, który jest izolacja. Technika utrzymywania izolacji dla transakcji odbywa się za pośrednictwem MVCC, Multiversion Concurrency Control . Mówiąc najprościej, InnoDB rejestruje wygląd danych przed próbą ich zmiany przez transakcje. Gdzie to się rejestruje? W systemowym pliku obszaru tabel, lepiej znanym jako ibdata1. To wymaga dysku I / O .

PORÓWNANIE

Ponieważ zarówno InnoDB, jak i MyISAM robią dyskowe operacje we / wy, jakie losowe czynniki decydują o tym, kto jest szybszy?

  • Rozmiar kolumn
  • Format kolumny
  • Zestawy znaków
  • Zakres wartości liczbowych (wymagające wystarczająco dużych liczb całkowitych)
  • Rzędy dzielone na bloki (łączenie rzędów)
  • Fragmentacja danych spowodowana przez DELETEsiUPDATEs
  • Rozmiar klucza podstawowego (InnoDB ma indeks klastrowany, wymagający dwóch odnośników)
  • Rozmiar wpisów indeksu
  • i tak dalej...

W związku z tym w środowisku o wysokim stopniu odczytu tabela MyISAM ze stałym formatem wiersza może przewyższać odczyt InnoDB z puli buforów InnoDB, jeśli w dziennikach cofania zawartych w ibdata1 zapisywana jest wystarczająca ilość danych do obsługi zachowania transakcyjnego nałożone na dane InnoDB.

WNIOSEK

Zaplanuj dokładnie typy danych, zapytania i silnik pamięci masowej. Gdy dane rosną, przenoszenie danych może być bardzo trudne. Zapytaj Facebooka ...

RolandoMySQLDBA
źródło
1
Doskonała odpowiedź, Rolando. Muszę zakwestionować twoje uwzględnienie niedowierzających twierdzeń Michaela Stonebreakera, który po prostu próbuje sprzedać swój własny produkt i nic nie wie o Facebooku. Po kilkukrotnym wysłuchaniu Facebooka na temat MySQL widać wyraźnie, że są zadowoleni ze swoich wyborów.
Aaron Brown
@AaronBrown W zeszłym roku słuchałem Harrison Fisk na Percona Live w Nowym Jorku i masz rację - Facebook jest bardzo zadowolony z ich wyłącznego korzystania z InnoDB i tego, jak spędzają czas wymyślając sposoby na dokonanie zmian w systemie online. Daje nawet publiczności szansę pracy na Facebooku w zakresie obsługi dużych zbiorów danych. Zamieściłem artykuł, aby pokazać, że niektórzy mają co do tego obawy. Z zadowoleniem przyjąłbym szansę pracy z ogromnymi danymi. To byłoby zabawne i trudne. Wyobraź sobie techniki, których można się nauczyć. Oczywiście nigdy nie
dotknąłbym
Byłem też na tej konferencji (i miałem szczęście, że mogłem wygłosić wykład), a prezentacja Harrisona była doskonała.
Aaron Brown
20

W prostym świecie MyISAM jest szybszy do odczytu, a InnoDB do szybszego zapisu.

Gdy zaczniesz wprowadzać mieszane operacje odczytu / zapisu, InnoDB będzie również szybsze w przypadku odczytu, dzięki mechanizmowi blokowania wierszy.

Kilka lat temu napisałem porównanie silników pamięci masowej MySQL , które do dziś są prawdziwe, przedstawiając wyjątkowe różnice między MyISAM a InnoDB.

Z mojego doświadczenia wynika, że ​​powinieneś używać InnoDB do wszystkiego oprócz tabel obciążających pamięć podręczną, w których utrata danych z powodu uszkodzenia nie jest tak istotna.

Mike Peters
źródło
4
Ta odpowiedź jest 5 lat nieaktualna. InnoDB nadrabia zaległości praktycznie na wszystkie sposoby; nie ma już wielu argumentów za używaniem MyISAM. MySQL 8.0 jest w trakcie usuwania MyISAM.
Rick James
2
Link jest teraz 9 lat nieaktualny.
Rick James
Korekta, odpowiedź jest nieaktualna o 9 lat (każdy, kto czyta pierwsze zdanie, będzie miał poważne problemy z tworzeniem bazy danych), a link ma 11 lat. Złap Ricka Jamesa, jesteś w tyle :).
CYREX
1
Masz rację @CYREX :-) To niesamowite, że ten post wciąż jest generowany ruch, 11 lat później. Tak wiele się zmieniło zarówno w moim życiu, jak i w sposobie optymalizacji InnoDB. Obecnie korzystanie z MyISAM rzadko jest uzasadnione
Mike Peters
Musiałem dzisiaj przyjrzeć się niektórym umierającym bazom danych i oba silniki są nadal używane ze starą wersją mysql. Tabele to zarówno InnoDB, jak i MyISAM, a moja ciekawość przyniosła mi ten post, który był bardzo pomocny.
Farrukh Subhani
14

Aby dodać tutaj odpowiedzi na różnice mechaniczne między dwoma silnikami, przedstawiam empiryczne badanie porównania prędkości.

Jeśli chodzi o czystą prędkość, nie zawsze jest tak, że MyISAM jest szybszy niż InnoDB, ale z mojego doświadczenia wynika, że ​​jest on szybszy w środowiskach roboczych PURE READ około 2,0-2,5 razy. Oczywiście nie jest to odpowiednie dla wszystkich środowisk - jak napisali inni, MyISAM nie ma takich transakcji, jak klucze obce.

Poniżej przeprowadziłem trochę testów porównawczych - użyłem Pythona do zapętlenia i biblioteki timeit do porównań czasowych. Dla zainteresowania załączyłem również silnik pamięci, który zapewnia najlepszą wydajność na całej płycie, chociaż jest odpowiedni tylko dla mniejszych tabel (ciągle napotykasz, The table 'tbl' is fullgdy przekroczysz limit pamięci MySQL). Cztery typy wybranych, na które patrzę, to:

  1. wanilia WYBIERA
  2. liczy się
  3. WYBORY warunkowe
  4. indeksowane i nieindeksowane podselekcje

Po pierwsze, utworzyłem trzy tabele, używając następującego 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

z „MyISAM” zamienionym na „InnoDB” i „memory” w drugiej i trzeciej tabeli.

 

1) Wanilia wybiera

Pytanie: SELECT * FROM tbl WHERE index_col = xx

Wynik: remis

Porównanie wyborów waniliowych według różnych silników baz danych

Ich szybkość jest zasadniczo taka sama i zgodnie z oczekiwaniami jest liniowa w liczbie kolumn do wyboru. InnoDB wydaje się nieco szybszy niż MyISAM, ale jest to naprawdę marginalne.

Kod:

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) Liczy się

Pytanie: SELECT count(*) FROM tbl

Wynik: MyISAM wygrywa

Porównanie liczby różnych silników baz danych

Ten pokazuje dużą różnicę między MyISAM a InnoDB - MyISAM (i pamięć) śledzi liczbę rekordów w tabeli, więc transakcja jest szybka i O (1). Ilość czasu potrzebna do zliczenia InnoDB wzrasta super-liniowo wraz z rozmiarem stołu w badanym zakresie. Podejrzewam, że wiele przyspieszeń z zapytań MyISAM, które są obserwowane w praktyce, wynika z podobnych efektów.

Kod:

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) Wybór warunkowy

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

Wynik: MyISAM wygrywa

Porównanie selekcji warunkowych przez różne silniki baz danych

Tutaj MyISAM i pamięć działają mniej więcej tak samo i pokonały InnoDB o około 50% w przypadku większych tabel. Jest to rodzaj zapytania, dla którego korzyści MyISAM wydają się zmaksymalizowane.

Kod:

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

Wynik: InnoDB wygrywa

Dla tego zapytania utworzyłem dodatkowy zestaw tabel dla podselekcji. Każda z nich to po prostu dwie kolumny BIGINT, jedna z indeksem klucza podstawowego i jedna bez indeksu. Ze względu na duży rozmiar stołu nie testowałem silnika pamięci. Poleceniem tworzenia tabeli SQL było

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

gdzie ponownie „MyISAM” zastępuje się „InnoDB” w drugiej tabeli.

W tym zapytaniu zostawiam rozmiar tabeli wyboru na 1000000 i zamiast tego zmieniam rozmiar subselekcji kolumn.

Porównanie podselekcji według różnych silników baz danych

Tutaj InnoDB wygrywa łatwo. Po przejściu do rozsądnej tabeli rozmiarów oba silniki skalują się liniowo z rozmiarem podselekcji. Indeks przyspiesza polecenie MyISAM, ale co ciekawe, ma niewielki wpływ na szybkość InnoDB. subSelect.png

Kod:

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

Myślę, że przesłaniem tego wszystkiego jest to, że jeśli naprawdę martwisz się szybkością, musisz przeprowadzić analizę porównawczą zapytań, a nie założyć, który silnik będzie bardziej odpowiedni.

StackG
źródło
1
Podoba mi się twoja odpowiedź, ponieważ jest na korzyść tego, kogo porównujesz i decydujesz. Żadne dwa systemy nie korzystają w ten sam sposób z różnych silników pamięci masowej i przy wyborze silnika pamięci wymagana jest należyta staranność. +1 dla Ciebie i Witamy w DBA StackExchange !!!
RolandoMySQLDBA
1
Zobacz także mój post dba.stackexchange.com/questions/1/… wraz z innymi odpowiedziami. Twój post wykracza poza to.
RolandoMySQLDBA
SELECT * FROM tbl WHERE index_col = xx- Oto dwa czynniki, które mogą prowadzić do większej zmienności na wykresie: Klucz główny vs. klucz pomocniczy; indeks jest buforowany vs nie.
Rick James
2
SELECT COUNT(*)jest wyraźnym zwycięzcą dla MyISAM, dopóki nie dodasz WHEREklauzuli.
Rick James,
Przypuszczam, że moim celem jest to, że każde zapytanie musi być testowane osobno. W odpowiedzi umieściłem kod - jeśli chcesz wypróbować inne zapytanie, bądź moim gościem - lub wyraź, jakie zapytanie chcesz, a ja go dodam.
StackG
4

Który jest szybszy? Albo może być szybszy. YMMV.

Którego powinieneś użyć? InnoDB - odporny na awarie itp.

Rick James
źródło
proszę zdefiniować „itp.”
dellasavia
1
@dellasavia - Najnowsze „etc” jest takie, że Oracle planuje usunąć MyISAM. Są tak pewni InnoDB.
Rick James