Продуктивність імпорту 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під час розробки та
б) у випадку, коли наш сервер вийде з ладу, ми хотіли б використовувати наш 2-й сервер як заміну (для чого потрібно -даточні дані, останній імпорт зайняв більше 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
Ви пробували з менш великим імпортом, наприклад, 10 К або 100 К рядків?
ypercubeᵀᴹ

1
Будь ласка, запустіть, SHOW CREATE TABLE yourtable\Gщоб показати нам структуру таблиці цієї 10-мільйонної таблиці рядків.
RolandoMySQLDBA

@RolandoMySQLDBA, так що я зробив (із затемненими назвами полів)
nuala

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

Відповіді:


13

По-перше, вам потрібно знати, що ви робите з InnoDB, коли ви вводите мільйони рядків у таблицю InnoDB. Давайте подивимось на архітектуру InnoDB.

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

У верхньому лівому куті розміщено ілюстрацію буфера InnoDB. Зауважте, є його розділ, присвячений буфері вставки. Що це робить? Потрібно мігрувати зміни до вторинних індексів від буферного пулу до вставки буфера всередині системного простору таблиць (aka ibdata1). За замовчуванням innodb_change_buffer_max_size встановлено на 25. Це означає, що до 25% буферного пулу можна використовувати для обробки вторинних індексів.

У вашому випадку у вас є 6.935 Гб для буфера InnoDB. Для обробки ваших вторинних індексів буде використано максимум 1,734 ГБ.

А тепер подивіться на свій стіл. У вас 13 вторинних індексів. Кожен рядок, який ви обробляєте, повинен генерувати вторинний запис індексу, з'єднати його з первинним ключем рядка та надіслати їх у вигляді пари з Вставити буфер у пуфі буфера в Вставити буфер в ibdata1. Це відбувається 13 разів з кожним рядом. Помножте це на 10 мільйонів, і ви майже можете відчути, що настає вузьке місце.

Не забувайте, що імпорт 10 мільйонів рядків за одну транзакцію зведе всі в один відкатний сегмент і заповнить простір UNDO в ibdata1.

ПРОПОЗИЦІЇ

ПРЕДЛОЖЕННЯ №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-індексів, щоб підказати, які індекси не використовуються.

ПРОПОЗИЦІЯ №4

Ви повинні збільшити розмір innodb_log_buffer_size до 64М, оскільки за замовчуванням дорівнює 8М. Більший буфер журналу може збільшити продуктивність вводу / виводу запису 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 (обробник сокета, запам'ятовується) та / або завантажуйте його одночасно з декількома потоками одночасно. Паралельність повинна дійти до солодкого місця, не надто багато, не надто мало.
  • Завантаження даних у фрагментації порядку первинного ключа може бути суттєвим
  • Перевірте стиснення 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 годину, але ваш список, безумовно, далеко не марний, і, мабуть, використаю його для ознайомлення досить скоро, оскільки швидкість нашого БД зростає лякає мене :)
nuala

Я згоден з @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 починати агресивно промивати пуфер буфера, коли відсоток брудних сторінок досягає цієї межі. Надаючи цей параметр (і якщо вам пощастить за розміром даних), ви можете уникнути агресивного вводу-виводу під час імпорту та затримати цей IO на пізніше.

Можливо (і це здогадка) імпорт ваших даних у багатьох дрібних транзакціях допоможе вам. Я точно не знаю, як побудований журнал REDO, але якщо він завантажений в оперативну пам’ять (і диск, коли буде потрібно занадто багато оперативної пам’яті), поки транзакція досягає прогресу, у вас може виникнути непотрібні IO. Ви можете спробувати це: після сортування вашого файлу розділіть його на багато фрагментів (спробуйте 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.