Почему MySQL InnoDB вставка так медленно? - PullRequest
55 голосов
/ 22 марта 2012

Я использую большие случайные числа в качестве ключей (поступающих из другой системы). Вставки и обновления в сравнительно небольших (например, в несколько миллионов строк) таблицах занимают намного больше времени, чем я считаю разумным.

Я показал очень простой тест для иллюстрации. В тестовой таблице я постарался сделать это максимально простым; мой реальный код не имеет такой простой структуры и имеет отношения и дополнительные индексы и тому подобное. Однако более простая установка показывает эквивалентную производительность.

Вот результаты:

creating the MyISAM table took 0.000 seconds
creating 1024000 rows of test data took 1.243 seconds
inserting the test data took 6.335 seconds
selecting 1023742 rows of test data took 1.435 seconds
fetching 1023742 batches of test data took 0.037 seconds
dropping the table took 0.089 seconds
creating the InnoDB table took 0.276 seconds
creating 1024000 rows of test data took 1.165 seconds
inserting the test data took 3433.268 seconds
selecting 1023748 rows of test data took 4.220 seconds
fetching 1023748 batches of test data took 0.037 seconds
dropping the table took 0.288 seconds

Вставка 1M строк в MyISAM занимает 6 секунд; InnoDB занимает 3433 секунд !

Что я делаю не так? Что неправильно настроено? (MySQL - это обычная установка Ubuntu со значениями по умолчанию)

Вот код теста:

import sys, time, random
import MySQLdb as db

# usage: python script db_username db_password database_name

db = db.connect(host="127.0.0.1",port=3306,user=sys.argv[1],passwd=sys.argv[2],db=sys.argv[3]).cursor()

def test(engine):

    start = time.time() # fine for this purpose
    db.execute("""
CREATE TEMPORARY TABLE Testing123 (
k INTEGER PRIMARY KEY NOT NULL,
v VARCHAR(255) NOT NULL
) ENGINE=%s;"""%engine)
    duration = time.time()-start
    print "creating the %s table took %0.3f seconds"%(engine,duration)

    start = time.time()
    # 1 million rows in 100 chunks of 10K
    data = [[(str(random.getrandbits(48)) if a&1 else int(random.getrandbits(31))) for a in xrange(10*1024*2)] for b in xrange(100)]
    duration = time.time()-start
    print "creating %d rows of test data took %0.3f seconds"%(sum(len(rows)/2 for rows in data),duration)

    sql = "REPLACE INTO Testing123 (k,v) VALUES %s;"%("(%s,%s),"*(10*1024))[:-1]
    start = time.time()
    for rows in data:
        db.execute(sql,rows)
    duration = time.time()-start
    print "inserting the test data took %0.3f seconds"%duration

    # execute the query
    start = time.time()
    query = db.execute("SELECT k,v FROM Testing123;")
    duration = time.time()-start
    print "selecting %d rows of test data took %0.3f seconds"%(query,duration)

    # get the rows in chunks of 10K
    rows = 0
    start = time.time()
    while query:
        batch = min(query,10*1024)
        query -= batch
        rows += len(db.fetchmany(batch))
    duration = time.time()-start
    print "fetching %d batches of test data took %0.3f seconds"%(rows,duration)

    # drop the table
    start = time.time()
    db.execute("DROP TABLE Testing123;")
    duration = time.time()-start
    print "dropping the table took %0.3f seconds"%duration


test("MyISAM")
test("InnoDB")

Ответы [ 9 ]

61 голосов
/ 22 марта 2012

InnoDB поддерживает транзакции, вы не используете явные транзакции, поэтому innoDB должен делать коммит после каждого оператора ( "выполняет сброс журнала на диск для каждой вставки" ).

Выполните эту команду перед циклом:

START TRANSACTION

и после цикла

COMMIT
41 голосов
/ 22 марта 2012

InnoDB плохо справляется со «случайными» первичными ключами.Попробуйте использовать последовательный ключ или автоинкремент, и я уверен, что вы увидите лучшую производительность.Ваше «реальное» ключевое поле все еще может быть проиндексировано, но для массовой вставки вам может быть лучше удалить и воссоздать этот индекс одним ударом после полной вставки.Было бы интересно увидеть ваши тесты для этого!

Некоторые связанные вопросы

21 голосов
/ 05 августа 2012

Мне нужно было одновременно выполнять тестирование приложения с высокой вставкой в ​​MyISAM и InnoDB.Был один параметр, который решал проблемы со скоростью, которые у меня были.Попробуйте установить следующее:

innodb_flush_log_at_trx_commit = 2

Убедитесь, что вы понимаете риски, прочитав о настройке здесь .

Также см. https://dba.stackexchange.com/questions/12611/is-it-safe-to-use-innodb-flush-log-at-trx-commit-2/12612 и https://dba.stackexchange.com/a/29974/9405

6 голосов
/ 27 августа 2012

Я получаю очень разные результаты в моей системе, но по умолчанию они не используются.Скорее всего, у вас узкое место по размеру innodb-log-file, который по умолчанию равен 5M.При innodb-log-file-size = 100M я получаю такие результаты (все числа в секундах):

                             MyISAM     InnoDB
create table                  0.001      0.276
create 1024000 rows           2.441      2.228
insert test data             13.717     21.577
select 1023751 rows           2.958      2.394
fetch 1023751 batches         0.043      0.038
drop table                    0.132      0.305

Увеличение innodb-log-file-size ускорит это на несколько секунд.Отказ от гарантий долговечности путем установки innodb-flush-log-at-trx-commit=2 или 0 также несколько улучшит номера вставок.

5 голосов
/ 22 марта 2012

Значение по умолчанию для InnoDB на самом деле довольно плохое. InnoDB сильно зависит от оперативной памяти, вы можете найти лучший результат, если вы настроите параметры. Вот руководство, которое я использовал Базовая оптимизация InnoDB

2 голосов
/ 28 мая 2017

Это старая тема, но часто ищется.До тех пор, пока вам известно о рисках (как указано выше в @philip Koshy) потери потерянных транзакций в последние одну секунду или около того, перед массовыми обновлениями вы можете установить эти глобальные параметры

innodb_flush_log_at_trx_commit=0
sync_binlog=0

и затем включитьзатем снова включите (если необходимо) после завершения обновления.

innodb_flush_log_at_trx_commit=1
sync_binlog=1

для полного соответствия ACID.

Существует огромная разница в производительности записи / обновления, когда оба они выключеныи вкл.По моему опыту, другие вещи, рассмотренные выше, имеют некоторое значение, но только незначительное.

Еще одна вещь, которая сильно влияет на update/insert, - это полнотекстовый индекс.В одном случае для таблицы с двумя текстовыми полями, имеющей полнотекстовый индекс, вставка 2-миллиметровых строк заняла 6 часов, а то же самое заняло всего 10 минут после удаления полнотекстового индекса.Больше индексов, больше времени.Таким образом, поисковые индексы, кроме уникального и первичного ключа, могут быть удалены до массовых вставок / обновлений.

2 голосов
/ 13 марта 2013

Какой у вас размер буферного пула innodb?Убедитесь, что вы установили 75% вашей оперативной памяти.Обычно вставки лучше, когда в порядке первичного ключа для InnoDB.Но при большом размере пула вы должны видеть хорошие скорости.

1 голос
/ 08 марта 2016

вещей, которые ускоряют вставки:

  • я удалил все ключи из таблицы перед большой вставкой в ​​пустую таблицу
  • затем обнаружил, что у меня проблема в том, что индекс не помещается в памяти.
  • также обнаружил, что у меня есть sync_binlog = 0 (должно быть 1), даже если binlog не используется.
  • также обнаружил, что я не установил innodb_buffer_pool_instances
0 голосов
/ 22 марта 2019

Решение

  1. Создайте новый УНИКАЛЬНЫЙ ключ, идентичный вашему текущему ПЕРВИЧНОМУ ключу
  2. Добавить новый столбец id - целое число без знака, auto_increment
  3. Создать первичный ключ для нового id столбца

Бам, мгновенное улучшение 10x + вставка.

...