Производительность импорта InnoDB


10

Я борюсь с массовым импортом довольно большой таблицы InnoDB, состоящей примерно из 10 миллионов строк (или 7 ГБ) (что для меня является самой большой таблицей, с которой я когда-либо работал).

Я провел некоторое исследование, как улучшить скорость импорта Inno, и на данный момент мои настройки выглядят так:

/etc/mysql/my.cnf/
[...]
innodb_buffer_pool_size = 7446915072 # ~90% of memory
innodb_read_io_threads = 64
innodb_write_io_threads = 64
innodb_io_capacity = 5000
innodb_thread_concurrency=0
innodb_doublewrite = 0
innodb_log_file_size = 1G
log-bin = ""
innodb_autoinc_lock_mode = 2
innodb_flush_method = O_DIRECT
innodb_flush_log_at_trx_commit=2
innodb_buffer_pool_instances=8


import is done via bash script, here is the mysql code:
SET GLOBAL sync_binlog = 1;
SET sql_log_bin = 0;
SET FOREIGN_KEY_CHECKS = 0;
SET UNIQUE_CHECKS = 0;
SET AUTOCOMMIT = 0;
SET SESSION tx_isolation='READ-UNCOMMITTED';
LOAD DATA LOCAL INFILE '$filepath' INTO TABLE monster
COMMIT;

Данные предоставляются в CSVфайле.
В настоящее время я тестирую свои настройки с меньшими «тестовыми дампами» с 2 миллионами, 3 миллионами,… строками каждый и использую их time import_script.shдля сравнения производительности.

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

Мои результаты пока:

  • 10 000 строк: <1 секунда
  • 100 000 строк: 10 секунд
  • 300 000 строк: 40 секунд
  • 2 миллиона строк: 18 минут
  • 3 миллиона строк: 26 минут
  • 4 миллиона строк: (отменено через 2 часа)

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

На данный момент я хотел бы попробовать предложение из чата, чтобы использовать MyISAMво время импорта и впоследствии изменить таблицу движка.
Я хотел бы попробовать это, но на данный момент мой DROP TABLEзапрос также занимает несколько часов, чтобы закончить. (Что является еще одним показателем, мой параметр меньше оптимального).

Дополнительная информация:
Машина, которую я сейчас использую, имеет 8 ГБ ОЗУ и твердотельный гибридный жесткий диск с 5400 об / мин.
Хотя мы также стремимся удалить устаревшие данные из таблицы, о которой идет речь, мне все же требуется несколько быстрый импорт в
а) тестирование automatic data cleanup featureво время разработки и
б) в случае сбоя нашего сервера, мы хотели бы использовать наш второй сервер в качестве замены (который требует -данные данные, последний импорт занял более 24 часов)

mysql> SHOW CREATE TABLE monster\G
*************************** 1. row ***************************
       Table: monster
Create Table: CREATE TABLE `monster` (
  `monster_id` int(11) NOT NULL AUTO_INCREMENT,
  `ext_monster_id` int(11) NOT NULL DEFAULT '0',
  `some_id` int(11) NOT NULL DEFAULT '0',
  `email` varchar(250) NOT NULL,
  `name` varchar(100) NOT NULL,
  `address` varchar(100) NOT NULL,
  `postcode` varchar(20) NOT NULL,
  `city` varchar(100) NOT NULL,
  `country` int(11) NOT NULL DEFAULT '0',
  `address_hash` varchar(250) NOT NULL,
  `lon` float(10,6) NOT NULL,
  `lat` float(10,6) NOT NULL,
  `ip_address` varchar(40) NOT NULL,
  `cookie` int(11) NOT NULL DEFAULT '0',
  `party_id` int(11) NOT NULL,
  `status` int(11) NOT NULL DEFAULT '2',
  `creation_date` datetime NOT NULL,
  `someflag` tinyint(1) NOT NULL DEFAULT '0',
  `someflag2` tinyint(4) NOT NULL,
  `upload_id` int(11) NOT NULL DEFAULT '0',
  `news1` tinyint(4) NOT NULL DEFAULT '0',
  `news2` tinyint(4) NOT NULL,
  `someother_id` int(11) NOT NULL DEFAULT '0',
  `note` varchar(2500) NOT NULL,
  `referer` text NOT NULL,
  `subscription` int(11) DEFAULT '0',
  `hash` varchar(32) DEFAULT NULL,
  `thumbs1` int(11) NOT NULL DEFAULT '0',
  `thumbs2` int(11) NOT NULL DEFAULT '0',
  `thumbs3` int(11) NOT NULL DEFAULT '0',
  `neighbours` tinyint(4) NOT NULL DEFAULT '0',
  `relevance` int(11) NOT NULL,
  PRIMARY KEY (`monster_id`),
  KEY `party_id` (`party_id`),
  KEY `creation_date` (`creation_date`),
  KEY `email` (`email`(4)),
  KEY `hash` (`hash`(8)),
  KEY `address_hash` (`address_hash`(8)),
  KEY `thumbs3` (`thumbs3`),
  KEY `ext_monster_id` (`ext_monster_id`),
  KEY `status` (`status`),
  KEY `note` (`note`(4)),
  KEY `postcode` (`postcode`),
  KEY `some_id` (`some_id`),
  KEY `cookie` (`cookie`),
  KEY `party_id_2` (`party_id`,`status`)
) ENGINE=InnoDB AUTO_INCREMENT=13763891 DEFAULT CHARSET=utf8

2
Вы пробовали с меньшим импортом, таким как строки 10K или 100K?
ypercubeᵀᴹ

1
Пожалуйста, запустите, SHOW CREATE TABLE yourtable\Gчтобы показать нам структуру таблицы этой таблицы с 10 миллионами строк.
RolandoMySQLDBA

@RolandoMySQLDBA, так что я сделал (с скрытыми именами полей)
Нуала

Отключив буфер двойной записи ( innodb_doublewrite = 0), ваша установка MySQL не является безопасной при сбое: если у вас сбой питания (не сбой MySQL), ваши данные могут быть незаметно повреждены.
jfg956

Ответы:


13

Во-первых, вам нужно знать, что вы делаете с InnoDB, когда вносите миллионы строк в таблицу InnoDB. Давайте посмотрим на архитектуру InnoDB.

InnoDB Архитектура

В левом верхнем углу есть иллюстрация буферного пула InnoDB. Обратите внимание, что есть раздел, посвященный буферу вставки. Что это делает? Он предназначен для переноса изменений во вторичные индексы из пула буферов в буфер вставки внутри табличного пространства системы (он же ibdata1). По умолчанию innodb_change_buffer_max_size имеет значение 25. Это означает, что до 25% пула буферов можно использовать для обработки вторичных индексов.

В вашем случае у вас есть 6,935 ГБ для пула буферов InnoDB. Для обработки ваших вторичных индексов будет использовано максимум 1,734 ГБ.

Теперь посмотри на свой стол. У вас есть 13 вторичных индексов. Каждая обрабатываемая строка должна генерировать запись вторичного индекса, связывать ее с первичным ключом строки и отправлять их в виде пары из буфера вставки в пуле буферов в буфер вставки в ibdata1. Это происходит 13 раз с каждым рядом. Умножьте это на 10 миллионов, и вы почти почувствуете приближение узкого места.

Не забывайте, что импорт 10 миллионов строк в одной транзакции соберет все в один сегмент отката и заполнит пространство UNDO в ibdata1.

SUGGESTIONS

ПРЕДЛОЖЕНИЕ № 1

Мое первое предложение для импорта этой довольно большой таблицы было бы

  • Отбросьте все неуникальные индексы
  • Импортировать данные
  • Создать все неуникальные индексы

ПРЕДЛОЖЕНИЕ № 2

Избавьтесь от дублирующих индексов. В вашем случае у вас есть

KEY `party_id` (`party_id`),
KEY `party_id_2` (`party_id`,`status`)

Оба индекса начинаются с того party_id, что вы можете увеличить обработку вторичного индекса как минимум на 7,6%, избавившись от одного индекса из 13. Вам необходимо в конечном итоге запустить

ALTER TABLE monster DROP INDEX party_id;

ПРЕДЛОЖЕНИЕ № 3

Избавьтесь от индексов, которые вы не используете. Посмотрите код вашего приложения и посмотрите, используют ли ваши запросы все индексы. Возможно, вы захотите взглянуть на использование pt-index, чтобы оно подсказывало, какие индексы не используются.

ПРЕДЛОЖЕНИЕ № 4

Вы должны увеличить innodb_log_buffer_size до 64M, поскольку по умолчанию это 8M. Больший буфер журнала может увеличить производительность операций ввода-вывода при записи InnoDB.

Эпилог

Поместив первые два предложения, сделайте следующее:

  • Удалите 13 неуникальных индексов
  • Импортировать данные
  • Создайте все неуникальные индексы, кроме party_idиндекса

Возможно, следующее может помочь

CREATE TABLE monster_new LIKE monster;
ALTER TABLE monster_new
  DROP INDEX `party_id`,
  DROP INDEX `creation_date`,
  DROP INDEX `email`,
  DROP INDEX `hash`,
  DROP INDEX `address_hash`,
  DROP INDEX `thumbs3`,
  DROP INDEX `ext_monster_id`,
  DROP INDEX `status`,
  DROP INDEX `note`,
  DROP INDEX `postcode`,
  DROP INDEX `some_id`,
  DROP INDEX `cookie`,
  DROP INDEX `party_id_2`;
ALTER TABLE monster RENAME monster_old;
ALTER TABLE monster_new RENAME monster;

Импортируйте данные в monster. Затем запустите это

ALTER TABLE monster
  ADD INDEX `creation_date`,
  ADD INDEX `email` (`email`(4)),
  ADD INDEX `hash` (`hash`(8)),
  ADD INDEX `address_hash` (`address_hash`(8)),
  ADD INDEX `thumbs3` (`thumbs3`),
  ADD INDEX `ext_monster_id` (`ext_monster_id`),
  ADD INDEX `status` (`status`),
  ADD INDEX `note` (`note`(4)),
  ADD INDEX `postcode` (`postcode`),
  ADD INDEX `some_id` (`some_id`),
  ADD INDEX `cookie` (`cookie`),
  ADD INDEX `party_id_2` (`party_id`,`status`);

ДАЙТЕ ЭТО ПОПРОБУЙТЕ !!!

АЛЬТЕРНАТИВА

Вы можете создать таблицу с monster_csvименем MyISAM без индексов и сделать это:

CREATE TABLE monster_csv ENGINE=MyISAM AS SELECT * FROM monster WHERE 1=2;
ALTER TABLE monster RENAME monster_old;
CREATE TABLE monster LIKE monster_old;
ALTER TABLE monster DROP INDEX `party_id`;

Импортируйте ваши данные в monster_csv. Затем используйте mysqldump для создания другого импорта

mysqldump -t -uroot -p mydb monster_csv | sed 's/monster_csv/monster/g' > data.sql

Файл mysqldump data.sqlбудет расширять команды INSERT, импортируя 10 000–20 000 строк одновременно.

Теперь просто загрузите mysqldump

mysql -uroot -p mydb < data.sql

Наконец, избавьтесь от таблицы MyISAM

DROP TABLE monster_csv;

Я даже не знал обо всех этих ключах (это не мой дизайн), но ваше объяснение кажется очень убедительным. На сегодня уже поздно начинать еще одну попытку, но я вижу несколько полезных советов, что попробовать завтра. Буду держать вас в курсе! <3
Нуала

1
Мне удалось импортировать всю базу данных (не только monsterтаблицу) менее чем за 20 минут, когда у меня не было ключей для таблиц InnoDB. Добавление ключей заняло ок. еще 20 мин. Я бы сказал, что это в значительной степени решает мою проблему в этом случае. Большое спасибо!
Нуала

8

Я хотел написать комментарий (так как это не окончательный ответ), но он стал слишком длинным:

Я дам вам несколько общих советов, и мы можем подробно рассказать о каждом из них, если вы хотите:

  • Уменьшите долговечность (вы уже сделали это). Последние версии позволяют даже делать это больше. Вы можете зайти так далеко, что отключите буфер двойной записи, так как повреждение не является проблемой для импорта.
  • Увеличение буферизации на: Увеличьте размер журнала транзакций и увеличьте доступный размер пула буферов. Мониторинг использования файла журнала транзакций и контрольных точек. Не бойтесь огромных бревен для импорта.
  • Избегайте больших транзакций - ваш откат будет полон ненужных данных. Это, наверное, ваша самая большая проблема.
  • SQL будет узким местом, избегая накладных расходов SQL (handlersocket, memcached) и / или загружая его в параллель с несколькими потоками одновременно. Параллельность должна достигать сладкого места, не слишком много, не слишком мало.
  • Загрузка данных в порядке фрагментации первичного ключа может быть проблемой
  • Проверьте сжатие InnoDB, если IO - это узкое место, а процессор и память не замедляют его
  • Попробуйте позже создать вторичные ключи (в некоторых случаях быстрее), не загружайте индексированные данные - DISABLE KEYS не влияет на InnoDB . Если нет, следите за своим буфером вставки (возможно, обогнав половину пула буферов).
  • Изменить или отключить алгоритм контрольной суммы - вероятно, не ваша проблема, но это становится узким местом на высокопроизводительных флэш-картах.
  • В крайнем случае: следите за своим сервером, чтобы найти текущее узкое место и попытайтесь устранить его (InnoDB очень гибок в этом).

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


Большое спасибо! Мне нравится сначала опробовать идею Роландо относительно индексов, но я думаю, что этот «откат транзакции» все еще будет проблемой. Не могли бы вы уточнить это? Я думаю, что хочу отключить как можно больше этой функциональности во время импорта и просто повторно включить ее при
запуске.

1
Предложение Роландо - моя точка № 7. Избежать накладных расходов отката так же просто, как сочетание SET SESSION tx_isolation='READ-UNCOMMITTED';(полезно только при импорте с несколькими параллельными потоками) и комментария @ypercube о вставке в пакеты. У вас есть полный пример здесь: mysqlperformanceblog.com/2008/07/03/… Убедитесь, что вы получаете все преимущества в последних версиях InnoDB: mysqlperformanceblog.com/2011/01/07/…
jynus

1
У меня сложилось общее впечатление, что нужно было бы избегать импорта в меньшие патроны, а вместо этого пойти на операцию «все включено», но я вижу, что многопоточность может открыть некоторые возможности. Думаю, это очень конкретный случай. Однако я принял ответ Роландо, так как только этот твик (ваш # 7) помог мне получить полный импорт менее чем за 1 час, но ваш список определенно далек от бесполезности, и я думаю, что он будет использоваться для справки довольно скоро, так как скорость нашей БД возрастает. пугает меня :)
Нуала

Я согласен с @yoshi. Ваш ответ является более полным с точки зрения устранения неполадок и повышения производительности. +1
RolandoMySQLDBA

3

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

Во-первых, откладывание создания индекса - это хорошо, достаточно подробностей в других ответах. Я не вернусь на это.

Большой файл журнала InnoDB вам очень поможет (если вы используете MySQL 5.6, так как в MySQL 5.5 его невозможно увеличить). Вы вставляете 7 ГБ данных, я бы рекомендовал общий размер журнала не менее 8 ГБ (оставьте значение innodb_log_files_in_groupпо умолчанию (2) и увеличьте его innodb_log_file_sizeдо 4 ГБ). Эти 8 ГБ не являются точными: они должны быть не меньше размера импорта в журнале REDO и, возможно, в два или четыре раза больше этого размера. Причины, по которым размер журнала InnoDB увеличивают, заключаются в том, что, когда журнал становится почти заполненным, InnoDB начнет активно сбрасывать свой буферный пул на диск, чтобы избежать заполнения журнала (когда журнал заполнен, InnoDB не может выполнять запись в базу данных до некоторой страницы пула буферов записываются на диск).

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

У вас уже есть буферный пул, настолько большой, насколько позволяет ваша оперативная память, и если ваша таблица не помещается в нем, вы ничего не можете сделать, кроме как покупать больше оперативной памяти. Но если ваша таблица помещается в буферный пул, но больше 75% вашего буферного пула, вы можете попробовать увеличить ее innodb_max_dirty_pages_pctдо 85 или 95 во время импорта (значение по умолчанию - 75). Этот параметр конфигурации сообщает InnoDB о необходимости активной очистки пула буферов, когда процент грязных страниц достигает этого предела. Увеличив этот параметр (и, если вам повезет, с размером данных), вы можете избежать агрессивного ввода-вывода во время импорта и отложить этот ввод позже.

Возможно (и это предположение) импорт ваших данных во многих небольших транзакциях поможет вам. Я не знаю точно, как создается журнал REDO, но если он буферизируется в ОЗУ (и на диске, когда потребуется слишком много ОЗУ), в то время как транзакция выполняется, у вас могут возникнуть ненужные операции ввода-вывода. Вы можете попробовать это: как только ваш файл отсортирован, разбейте его на несколько частей (попробуйте с 16 МБ и другими размерами) и импортируйте их один за другим. Это также позволит вам контролировать ход вашего импорта. Если вы не хотите, чтобы ваши данные были частично видны другому читателю во время импорта, вы можете импортировать, используя другое имя таблицы, создать индексы позже, а затем переименовать таблицу.

Про ваш гибридный диск SSD / 5400RPM я не знаю, как и как это оптимизировать. 5400RPM выглядит медленно для базы данных, но, возможно, SSD избегает этого. Возможно, вы заполняете часть SSD вашего диска последовательными записями в журнал REDO, и SSD ухудшает производительность. Я не знаю.

Плохие советы, которые вы не должны использовать (или будьте осторожны), заключаются в следующем: не используйте многопоточность: будет очень трудно оптимизировать, чтобы избежать разбиения страниц в InnoDB. Если вы хотите использовать многопоточность, вставьте в разные таблицы (или в разные разделы одной и той же таблицы).

Если вы рассматриваете многопоточность, возможно, у вас есть компьютер с несколькими сокетами (NUMA). В этом случае убедитесь, что вы избежали проблемы с безумной заменой MySQL .

Если вы используете MySQL 5.5, обновитесь до MySQL 5.6: он имеет возможность увеличения размера журнала REDO и имеет лучшие алгоритмы очистки буферного пула.

Удачи в импорте.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.