Що швидше, InnoDB або MyISAM?


54

Як MyISAM може бути "швидшим", ніж InnoDB, якщо

  • MyISAM потребує читання дисків для даних?
  • InnoDB використовує буферний пул для індексів і даних, а MyISAM лише для індексу?

MyISAM дозволяє блоку даних кешу ОС , тому не завжди "читання диска для даних".
Рік Джеймс,

Відповіді:


68

Єдиний спосіб MyISAM може бути швидшим, щоб InnoDB опинився під цією унікальною обставиною

MyISAM

Під час читання індекси таблиці MyISAM можна прочитати один раз з файлу .MYI та завантажити в кеш-пам'ять MyISAM (розмір за розміром key_buffer_size ). Як можна швидше зробити .MYD таблиці MyISAM швидше для читання? З цим:

ALTER TABLE mytable ROW_FORMAT=Fixed;

Про це я писав у своїх минулих дописах

InnoDB

Добре, а як щодо InnoDB? Чи InnoDB робить якісь дискові введення / виведення для запитів? Дивно, але так! Ви, напевно, думаєте, що я божевільний, що це сказав, але це абсолютно вірно навіть для SELECT запитів . На даний момент ви, мабуть, замислюєтесь: "Як у світі InnoDB робить дискові введення / виведення для запитів?"

Все повертається до InnoDB, будучи двигуном транзакційного зберігання ACID- скаргами. Для того , щоб бути InnoDB транзакційних, він повинен підтримувати Iв системі ACID, яка є ізоляцією. Техніка підтримки ізоляції для транзакцій здійснюється за допомогою MVCC, Multiversion Concurrency Control . Простіше кажучи, InnoDB записує, як виглядають дані, перш ніж транзакції намагаються їх змінити. Де це записується? У файлі системного простору таблиць, більш відомий як ibdata1. Для цього потрібен диск вводу / виводу .

ПОРІВНЯЙТЕ

Оскільки і InnoDB, і MyISAM роблять диск вводу / виводу, які випадкові фактори диктують, хто швидший?

  • Розмір стовпців
  • Формат стовпця
  • Набори символів
  • Діапазон числових значень (вимагає достатньо великих INT)
  • Ряди розділяються по блоках (рядкові ланцюги)
  • Фрагментація даних, викликана DELETEsтаUPDATEs
  • Розмір первинного ключа (InnoDB має кластерний індекс, що вимагає двох ключових пошукових запитів)
  • Розмір індексних записів
  • список продовжується ...

Таким чином, у важкому для читання середовищі можна, щоб таблиця MyISAM з фіксованим форматом рядків перевершила InnoDB зчитування з пулу InnoDB Buffer, якщо в журнали відміни, що містяться в ibdata1, є достатньо даних для підтримки поведінки транзакцій накладені на дані InnoDB.

ВИСНОВОК

Ретельно плануйте типи даних, запити та механізм зберігання даних. Як тільки дані зростають, переміщення даних може стати дуже важким. Просто запитайте у Facebook ...


1
Відмінна відповідь, Роландо. Я маю сумніватися у вашому включенні недовірливих претензій Майкла Стоунбрейкера, який просто намагається продати власний товар і нічого не знає про Facebook. Прослухавши кілька разів присутніх у Facebook про MySQL, зрозуміло, що їм комфортно їх вибір.
Аарон Браун

@AaronBrown Я слухав Harrison Fisk в минулому році в Percona Live NYC, і ви маєте рацію - 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 років. Догнати Ріка Джеймса, ти відстаєш :).
CYREX

1
Ви праві @CYREX :-) Його дивовижна ця публікація все ще отримує трафік, 11 років потому. Так багато змінилося як у моєму житті, так і в оптимізації InnoDB. На сьогоднішній день рідко буває виправдання використовувати MyISAM
Майк Петерс

Мені довелося подивитися деякі вимираючі бази даних сьогодні, і обидва двигуни все ще використовуються зі старою версією mysql. Таблиці є і InnoDB, і MyISAM, і моя цікавість привела мене до цієї посади, що було дуже корисно.
Фаррух Субхані

14

Щоб додати тут відповіді, що охоплюють механічні відмінності між двома двигунами, я представляю емпіричне дослідження порівняння швидкості.

Що стосується чистої швидкості, то не завжди буває так, що MyISAM швидше, ніж InnoDB, але, на мій досвід, це, як правило, швидше для робочих середовищ PURE READ в 2,0-2,5 рази. Зрозуміло, що це не підходить для всіх середовищ - як писали інші, MyISAM не вистачає таких речей, як транзакції та зовнішні ключі.

Я трохи провів порівняльний аналіз нижче - я використав python для циклічного циклу та бібліотеку timeit для порівняння часу. Для інтересу я також включив двигун пам'яті, це дає найкращі показники в усьому світі, хоча він підходить лише для менших таблиць (ви постійно стикаєтесь, The table 'tbl' is fullколи перевищуєте ліміт пам’яті MySQL). Я переглядаю чотири типи вибору:

  1. ванільний ВИБІР
  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" та "пам'ять" у другій та третій таблицях.

 

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, один з індексом первинного ключа та один без індексу. Через великий розмір столу я не випробовував двигун пам'яті. Команда створення таблиці 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
Мені подобається ваша відповідь, тому що це на користь того, кого ви орієнтуєте та вирішите. Жодна дві системи не отримують переваги однаково від різних двигунів зберігання даних, і для вибору двигуна зберігання не потрібно ретельної перевірки. +1 для вас і Ласкаво просимо до DBA StackExchange !!!
RolandoMySQLDBA

1
Також дивіться мою публікацію dba.stackexchange.com/questions/1/… разом з іншими відповідями. Вигляд вашої посади іде вище та далі.
RolandoMySQLDBA

SELECT * FROM tbl WHERE index_col = xx- Ось два фактори, які можуть призвести до більшої зміни в графіку: Первинний ключ та вторинний ключ; індекс кешований проти ні.
Рік Джеймс,

2
SELECT COUNT(*)є явним переможцем для MyISAM, поки ви не додасте WHEREпункт.
Рік Джеймс,

Я припускаю, що моя думка полягає в тому, що кожен запит потрібно орієнтувати окремо. Я включив код у відповідь - якщо ви хочете пробувати інший запит, будь ласка, будьте моїм гостем - або уточніть, який запит ви хочете, і я додам його.
StackG

4

Що швидше? Або може бути швидше. YMMV.

Який слід використовувати? InnoDB - безпечний для аварій, тощо.


будь ласка, визначте "тощо, тощо".
dellasavia

1
@dellasavia - Найновішим "тощо" є те, що Oracle планує видалити MyISAM. Вони впевнені в InnoDB.
Рік Джеймс
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.