Більшість «налаштувань продуктивності» баз даних починаються не з того місця: з файлу конфігурації. Хтось згадує блог, крутанув ручку, перезапустив продакшн і сподівається, що графіки перестануть кричати. Іноді це допомагає. Частіше — просто переносить біль в іншу підсистему й подовжує розбір польотів.
Найшвидший спосіб виграти дискусію про продуктивність між MariaDB і MySQL — це нудна: визначити вузьке місце до того, як ви щось зміните. Цей чекліст робить саме це. Це той самий підхід, який я застосовую, коли критичний шлях платіжів таймаутиться, а єдина «репродукція» — «іноді повільно».
Один чекліст: знайдіть вузьке місце, потім налаштовуйте
MariaDB і MySQL мають багато спільного, особливо якщо ви переважно працюєте в InnoDB‑світі. Саме тому питання «який швидший» зазвичай невірне. Правильне питання: що зараз обмежує пропускну здатність або затримку на цьому хості? CPU. I/O. Блокування. Пам’ять. Мережа. Реплікація. Або класика: один патологічний запит, що тягне все інше в багнюку.
Ось ментальна модель, яка тримає вас у рамках:
- Вузьке місце пропускної здатності: ви вичерпуєте якийсь ресурс (CPU, пропускну здатність диска, мютекси). Симптоми: QPS стагнує при зростанні навантаження; затримка може, але не обов’язково, підскочити.
- Вузьке місце затримки: щось час від часу блокує (fsync‑пики, блокування, пропуски в буфер‑пулі, затримка в застосуванні реплікації). Симптоми: хвіст затримки зростає; «іноді повільно».
- Чергове вузьке місце: однонитковий або послідовний етап (скидання redo‑логу, SQL‑потік репліки, metadata lock). Симптоми: одна черга росте; усі чекають своєї черги.
Коли ви під тиском, ручки налаштувань здаються продуктивними, бо вони відчутні. Але ручки — це рішення «знизу». Якщо ви не знаєте вузького місця, ви фактично змінюєте випадкові змінні й оголошуєте перемогу, коли наступний інцидент трапиться в інший день.
Операційне правило: не налаштовуйте, поки не зможете сказати однією фразою: «вузьке місце — X, доведено Y». Якщо не можете — ви не налаштовуєте, ви граєте у рулетку.
І так, ви все одно будете налаштовувати. Але ви робитимете це з доказами.
Короткий жарт №1: «Ми налаштували MySQL поки він не став швидким.» Мило. Балансувальник навантаження це пам’ятає.
Факти та історія, що й досі важливі в продакшні
Трохи контексту допомагає, бо пояснює, чому відрізняються значення за замовчуванням, чому поведінка дивує і чому чийсь «порадник для MySQL» ламає MariaDB (або навпаки).
- MariaDB була форкнута з MySQL у 2009 році після занепокоєння через придбання MySQL компанією Oracle. Це не просто дрібниця: саме тому припущення екосистеми з часом розходяться.
- Ранні версії MariaDB активно позиціонувалися як «прямий замінник». Операційно це здебільшого правда на рівні SQL — але поведінка продуктивності може відрізнятися на краях (оптимізатор, thread pool, можливості реплікації).
- MySQL 5.6/5.7 зробили InnoDB центром тяжіння і значно покращили performance schema та інструментарій. Якщо ви все ще ставитеся до нього як до чорної скриньки — ви втрачаєте безкоштовну діагностику.
- MySQL 8.0 видалив query cache (що відоме було проблемами з mutex‑контенцією). Якщо у вашій MariaDB‑флоті все ще працює query cache — це історичний артефакт із сучасними наслідками.
- MariaDB відрізнилася функціями на кшталт Aria, ColumnStore і інтеграції з Galera (в багатьох дистрибутивах). Це не просто фічі; вони змінюють режими відмов і операційні ручки.
- Перебудова словника даних у MySQL 8.0 змінила метадані й деякі реалії оновлень/відкатів. «Працювало на 5.7» — це не план.
- Обидві системи сильно залежать від поведінки Linux I/O (fsync, скидання брудних сторінок, планувальник). Багато «інцидентів бази даних» — це просто ядро, яке робить те, що ви попросили, а не те, що ви мали на увазі.
- Реплікація еволюціонувала по‑різному: MySQL зосередився на GTID, багатонитковій реплікації, груповій реплікації; MariaDB має власну реалізацію GTID і часто іншу ергономіку реплікації. Ті ж слова — інші підводні міни.
Також: розмови про продуктивність швидко старіють. Керівництво з тюнінгу, написане для MySQL 5.5 на шпиндельних дисках, може завдати шкоди на MySQL 8.0 на NVMe з cgroups і шумними сусідами.
Потрібне цитатне правило, перефразована ідея: перефразована ідея: «Надія — не стратегія.»
— часто в операційних кругах; використовуйте як принцип, готовий до виклику, а не як слоган.
Плейбук швидкої діагностики (перший/другий/третій)
Це порядок дій «увійти в палаючу кімнату». Він розроблений для швидкості, а не для вишуканості. Мета — визначити, чи ви CPU‑bound, IO‑bound, lock‑bound або replication‑bound за декілька хвилин.
Перший: доведіть, який тип вузького місця у вас
- Чи насичений ресурс на хості? CPU, тиск пам’яті, IO wait, латентність диска, помилки мережі.
- Чи чекає база даних? очікування блокувань, fsync/redo очікування, пропуски в буфер‑пулі, планування потоків, сплески підключень.
- Чи виконує база щось дороге? повні скани, погані плани, сортування на диску, тимчасові таблиці, відсутні індекси.
Другий: знайдіть головного порушника, а не середнє
- Топ‑запити за сумарним часом (не за кількістю).
- Топ‑очікування (блокування, I/O, flush, metadata locks).
- Топ‑таблиці (гарячі рядки, відсутні індекси, сильний churn).
Третій: вирішіть, пом’якшувати, виправляти чи масштабувати
- Пом’якшити зараз: вбити неконтрольовані запити, знизити навантаження, обережно збільшити розміри пулів, зменшити конкурентність, перенести читання на репліки.
- Виправити далі: зміни індексів, перепис запитів, зміни схеми, партиціонування, корекція рівнів ізоляції.
- Масштабувати за потреби: більше CPU для CPU‑bound, швидше сховище для I/O‑bound, репліки для read fan‑out, шардинг лише якщо ви любите страждання (або дійсно це потрібно).
Порада щодо швидкості: не «семплюйте», дивлячись на середні. Шукайте черги й хвіст затримки. Середні — це те, що ви розповідаєте менеджменту. Черги — те, що ви розповідаєте on‑call.
Практичні завдання (команди, виводи, рішення)
Це виконувані завдання, які можна зробити на Linux DB‑хості та всередині бази даних. Кожне містить (a) команду, (b) що означає вивід, і (c) рішення, яке ви приймаєте. Остання частина — різниця між моніторингом і операціями.
Завдання 1: підтвердити, який движок і версія у вас реально запущені
cr0x@server:~$ mysql -e "SELECT VERSION() AS version, @@version_comment AS comment, @@version_compile_machine AS arch;"
+------------------------+------------------------------+------+
| version | comment | arch |
+------------------------+------------------------------+------+
| 10.6.16-MariaDB-1:... | MariaDB Server | x86_64 |
+------------------------+------------------------------+------+
Значення: Не налагоджуйте «MySQL», коли насправді у вас MariaDB (або навпаки). Різниці у версіях визначають доступну інструментацію та поведінку оптимізатора.
Рішення: Оберіть правильні інструменти: робочі процеси, які інтенсивно покладаються на performance_schema, більш зрілі в MySQL 8.0; у MariaDB можуть знадобитися інші лічильники статусу та еквіваленти sys schema.
Завдання 2: швидка перевірка навантаження хоста (CPU, IO wait, черга виконання)
cr0x@server:~$ vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
3 1 0 81232 64312 987654 0 0 120 980 900 2100 25 10 45 20 0
8 2 0 79012 64320 987120 0 0 110 1600 1100 3200 35 12 33 20 0
Значення: Високе r вказує на конкуренцію за CPU; високе wa — на очікування I/O; стійке b означає заблоковані задачі.
Рішення: Якщо wa стабільно високий і латентність стрибкоподібна, переходьте прямо до завдань по дисковій латентності. Якщо r високий при низькому wa, зосередьтеся на CPU‑профілюванні запитів і контенціях.
Завдання 3: виміряти дискову латентність, а не лише пропускну здатність
cr0x@server:~$ iostat -x 1 3
Device r/s w/s rkB/s wkB/s await svctm %util
nvme0n1 1200 9000 48000 320000 18.5 0.3 92.0
Значення: await — це час, який запити проводять у черзі та на обслуговуванні. Високе await з високою завантаженістю означає, що пристрій насичений або щось вищестояще формує чергу.
Рішення: Якщо await підвищується одночасно з латентністю БД — ви I/O‑bound. Пом’якшення: зменшити пикові записи (чекпоінт/flush), покращити buffer pool або перейти на швидше сховище. Якщо await низький, а БД повільна — вузьке місце десь інде.
Завдання 4: знайти найзавантаженіші потоки MySQL/MariaDB на рівні ОС
cr0x@server:~$ top -H -p $(pidof mysqld)
top - 12:01:22 up 34 days, 2 users, load average: 9.10, 8.80, 7.90
Threads: 210 total, 8 running, 202 sleeping
%Cpu(s): 70.0 us, 10.0 sy, 0.0 ni, 20.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
12345 mysql 20 0 12.3g 8.1g 0.0g R 180.0 52.0 12:11.04 mysqld
Значення: Якщо ви бачите кілька потоків, що зашкалюють, можливо, у вас CPU‑bound запити або мютекс‑контенція. Якщо CPU низький, але затримка висока — зосередьтеся на очікуваннях/блокуваннях/IO всередині InnoDB.
Рішення: Якщо CPU насичений — переходьте до профілювання запитів і планів. Якщо ні — перевіряйте блокування й IO‑очікування в InnoDB.
Завдання 5: миттєво виявити неконтрольовані запити
cr0x@server:~$ mysql -e "SHOW FULL PROCESSLIST\G" | sed -n '1,80p'
*************************** 1. row ***************************
Id: 123
User: app
Host: 10.0.2.14:51234
db: prod
Command: Query
Time: 88
State: Sending data
Info: SELECT ... FROM orders JOIN order_items ...
Значення: Time — скільки часу виконується. State підказує, що відбувається (не завжди точно). Велика кількість довготривалих станів «Sending data» часто означає великі скани/з’єднання.
Рішення: Якщо один запит домінує і впливає на користувачів — розгляньте можливість його вбити і обмежити викликачів. Потім зафіксуйте запит для EXPLAIN і роботи з індексами.
Завдання 6: швидко знайти контенцію блокувань (InnoDB)
cr0x@server:~$ mysql -e "SHOW ENGINE INNODB STATUS\G" | sed -n '1,140p'
TRANSACTIONS
------------
Trx id counter 987654321
Purge done for trx's n:o < 987654000 undo n:o < 0 state: running
History list length 12045
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 987654310, ACTIVE 92 sec
2 lock struct(s), heap size 1136, 1 row lock(s)
MySQL thread id 123, OS thread handle 1400, query id 7777 10.0.2.14 app updating
UPDATE accounts SET balance=balance-? WHERE id=?
Значення: Довгі «ACTIVE» транзакції та великий History list length часто корелюють з відставанням purge, зростанням undo та вторинними симптомами типу «рандомні повільні запити».
Рішення: Якщо ви бачите довготривалу транзакцію, що утримує блокування — можливо, її треба вбити, виправити межі транзакції в додатку або знизити рівень ізоляції там, де безпечно. Також розберіться, чому purge не справляється — часто через довгих читачів.
Завдання 7: перевірити навантаження на buffer pool та індикатори hit ratio
cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_reads'; SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read_requests';"
+-------------------------+-----------+
| Variable_name | Value |
+-------------------------+-----------+
| Innodb_buffer_pool_reads| 124009876 |
+-------------------------+-----------+
+----------------------------------+-------------+
| Variable_name | Value |
+----------------------------------+-------------+
| Innodb_buffer_pool_read_requests | 98012345678 |
+----------------------------------+-------------+
Значення: Innodb_buffer_pool_reads — це фізичні читання. Якщо вони швидко ростуть відносно read requests — ви промахуєте кеш і платите дисковою латентністю.
Рішення: Якщо фізичних читань багато і дискова латентність значна — збільште buffer pool (якщо RAM дозволяє) або зменшіть робочий набір (індекси, патерни запитів). Якщо buffer pool у нормі — не чіпайте його даремно.
Завдання 8: виявити витік у тимчасові таблиці (сортування/з’єднання на диску)
cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Created_tmp%tables';"
+-------------------------+----------+
| Variable_name | Value |
+-------------------------+----------+
| Created_tmp_disk_tables | 9123456 |
| Created_tmp_tables | 12345678 |
+-------------------------+----------+
Значення: Високе співвідношення тимчасових таблиць на диску свідчить про занадто маленькі пам’ятні ліміти для сортувань/з’єднань або про запити, що по суті спадають на диск (відсутні індекси, великі GROUP BY).
Рішення: Якщо використання дискових тимчасових таблиць корелює з повільними ендпойнтами — спочатку виправте плани запитів (індекси), потім обережно піднімайте пам’ять для тимчасових таблиць. Більші ліміти під конкуренцією можуть призвести до викиду пам’яті.
Завдання 9: перевірити тиск на redo log та проблеми з фсинком
cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Innodb_os_log_fsyncs'; SHOW GLOBAL STATUS LIKE 'Innodb_log_waits';"
+----------------------+----------+
| Variable_name | Value |
+----------------------+----------+
| Innodb_os_log_fsyncs | 77881234 |
+----------------------+----------+
+------------------+-------+
| Variable_name | Value |
+------------------+-------+
| Innodb_log_waits | 12345 |
+------------------+-------+
Значення: Innodb_log_waits вказує на очікування місця в буфері логу/флашу. У поєднанні з високою частотою fsync і дисковою латентністю це вказує на вузьке місце записів.
Рішення: Якщо log waits зростають — ви досягаєте межі commit/flush. Пом’якшення: швидше сховище, зменшення частоти транзакцій, батчинг записів або зміна налаштувань надійності лише з явним погодженням бізнесу.
Завдання 10: перевірити відставання репліки і чи це I/O, SQL apply або блокування
cr0x@server:~$ mysql -e "SHOW SLAVE STATUS\G" | egrep -i "Seconds_Behind_Master|Slave_IO_Running|Slave_SQL_Running|Relay_Log_Space"
Seconds_Behind_Master: 420
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Relay_Log_Space: 987654321
Значення: IO‑потік в порядку, SQL‑потік працює, але відставання значне й relay log накопичується. Репліка може завантажувати, але не встигає застосувати транзакції.
Рішення: Досліджуйте вузьке місце застосування: однониткове застосування, гарячі рядки, DDL, довгі транзакції або повільне сховище. Розгляньте багатониткову реплікацію там, де це підтримується й безпечно, і виправляйте патерни транзакцій, що сериалізують застосування.
Завдання 11: перевірити сплески підключень і планування потоків
cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Threads%'; SHOW GLOBAL STATUS LIKE 'Connections';"
+-------------------+-------+
| Variable_name | Value |
+-------------------+-------+
| Threads_connected | 980 |
| Threads_running | 64 |
| Threads_cached | 0 |
+-------------------+-------+
+---------------+--------+
| Variable_name | Value |
+---------------+--------+
| Connections | 987654 |
+---------------+--------+
Значення: Багато підключених потоків при низькому кеші може означати сильну зміну підключень (додаток не пулює) або занадто малий thread cache.
Рішення: Спочатку виправте додаток: увімкніть пулінг, повторне використання з’єднань, обмежте конкурентність. Лише потім налаштовуйте кеш потоків; інакше ви просто дозволите додатку завдати шкоди швидше.
Завдання 12: порівняти розмір таблиць/індексів з пам’яттю, щоб передбачити промахи кеша
cr0x@server:~$ mysql -e "SELECT table_schema, SUM(data_length+index_length)/1024/1024/1024 AS gb FROM information_schema.tables GROUP BY table_schema ORDER BY gb DESC;"
+--------------+--------+
| table_schema | gb |
+--------------+--------+
| prod | 820.12 |
| mysql | 0.05 |
+--------------+--------+
Значення: Якщо ваш активний датасет — сотні ГБ, а buffer pool — 32GB, ви не кешуєте робочий набір. Читання будуть йти з диска. Плани мають бути селективними.
Рішення: Або забезпечте RAM відповідно до робочого набору, або спроектуйте запити/індекси, щоб уникати сканів. Якщо ні — переносіть читання на репліки або приймайте, що хвіст затримки визначається сховищем.
Завдання 13: зловити накопичення metadata locks (DDL та «безпечні» ALTER)
cr0x@server:~$ mysql -e "SHOW FULL PROCESSLIST;" | awk '$6 ~ /Waiting/ || $7 ~ /metadata/ {print}'
234 app 10.0.2.21:49910 prod Query 35 Waiting for table metadata lock ALTER TABLE users ADD COLUMN ...
Значення: Metadata locks блокують запити, що звертаються до таблиці. Один DDL може заморозити гарячий шлях таблиці, якщо він чекає за довгою транзакцією.
Рішення: Призупиніть DDL, знайдіть і завершіть довгу транзакцію, або використовуйте онлайн‑зміни схеми. Також: плануйте зміни схеми так само ретельно, як плануєте ризик, а не як обід.
Завдання 14: швидко переглянути slow query log (якщо увімкнено)
cr0x@server:~$ sudo tail -n 20 /var/log/mysql/slow.log
# Time: 2025-12-31T11:55:22.123456Z
# Query_time: 12.400 Lock_time: 0.000 Rows_sent: 50 Rows_examined: 50000000
SELECT ... FROM events WHERE customer_id=... ORDER BY created_at DESC LIMIT 50;
Значення: Кількість переглянутих рядків (Rows examined) — показник. 50M переглянутих, щоб повернути 50 рядків — це проблема індексу або плану, а не «більше buffer pool».
Рішення: Додайте/виправте індекс (ймовірно (customer_id, created_at)), перевірте через EXPLAIN і подумайте над переписуванням запиту. Не «налаштовуйте» навколо відсутнього індексу.
Завдання 15: перевірити план через EXPLAIN і зробити його читабельним
cr0x@server:~$ mysql -e "EXPLAIN SELECT id FROM events WHERE customer_id=42 ORDER BY created_at DESC LIMIT 50\G"
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: events
type: ALL
possible_keys: idx_customer_created
key: NULL
rows: 50000000
Extra: Using where; Using filesort
Значення: type: ALL і filesort на великій таблиці вказують на повний скан + сортування. Індекс існує, але не використовується (неправильний порядок, низька селективність, невідповідна колація, функція над колонкою або старі статистики).
Рішення: Виправте запит або індекс. Якщо статистики застаріли — виконайте analyze. Якщо оптимізатор помиляється, розгляньте hint‑и лише як останній засіб і тільки після тестів на регресії.
Завдання 16: перевірити дедлоки (вони не рідкість; просто недостатньо звітні)
cr0x@server:~$ mysql -e "SHOW ENGINE INNODB STATUS\G" | sed -n '/LATEST DETECTED DEADLOCK/,+60p'
LATEST DETECTED DEADLOCK
------------------------
*** (1) TRANSACTION:
TRANSACTION 987654400, ACTIVE 2 sec updating or deleting
...
WE ROLL BACK TRANSACTION (1)
Значення: Дедлоки — це баги конкуренції, а не «баг бази даних». Зазвичай спричинені непослідовним порядком оновлень або надто великими транзакціями.
Рішення: Виправте порядок оновлень і обсяг транзакцій у коді. Потім переконайтеся, що логіка повторів правильна і обмежена. Повторювати нескінченно — це просто імітація обробки дедлоків.
Завдання 17: виміряти файлову систему і опції монтування, що впливають на fsync
cr0x@server:~$ mount | egrep ' /var/lib/mysql | /data '
/dev/nvme0n1p2 on /var/lib/mysql type ext4 (rw,noatime,data=ordered)
Значення: Файлова система та опції монтування впливають на оновлення метаданих і write barriers. Вам не потрібні екзотичні налаштування; потрібні послідовні, протестовані налаштування.
Рішення: Якщо ви бачите fsync‑зависання — перевірте прошивку диска, стан файлової системи і журнали ядра перед тим, як чіпати налаштування надійності бази.
Завдання 18: переконатися, що база не свопиться (це тихо все зруйнує)
cr0x@server:~$ free -h
total used free shared buff/cache available
Mem: 31Gi 28Gi 0.8Gi 0.2Gi 2.2Gi 1.1Gi
Swap: 4Gi 1.9Gi 2.1Gi
Значення: Якщо на DB‑хості використовується swap, латентність стає непередбачуваною. Навіть «трохи swap» може означати, що сторінки, що вам важливі, викидаються.
Рішення: Зменшіть пам’ятний слід (buffer pool занадто великий, пер‑з’єднання буфери занадто великі), додайте RAM або обмежте конкуренцію. Не звикайте до swap на базах даних, якщо вам подобаються детективні історії.
Чеклісти / покроковий план
Це операційний чекліст, який я хочу бачити на стіні (або в runbook). Це мінімальний набір кроків, що надійно приводить до відповіді без тюнінгу за фольклором.
Чекліст A: 10‑хвилинна тріаж (продакшн у вогні)
- Підтвердити область впливу: один ендпойнт, один орендар чи все? Зхопіть часове вікно і показники помилок з моніторингу додатка.
- Насичення хоста: запустіть
vmstatіiostat -x. Вирішіть: CPU‑bound чи IO‑bound. - Черга в базі:
SHOW FULL PROCESSLIST, щоб побачити, чи багато станів «Waiting for …» або кілька довгих виконань. - Блокування:
SHOW ENGINE INNODB STATUSі пошук довгих транзакцій, очікувань блокувань, дедлоків. - Реплікація (якщо доречно): перевірте відставання. Якщо читання віддаються з реплік, відставання — це помилка, яку бачить користувач.
- Термінове пом’якшення: вбийте найгірший запит, знизьте навантаження, призупиніть DDL або тимчасово направте трафік на читання інакше.
- Захопіть докази: уривок зі slow log, EXPLAIN, відповідні лічильники статусу перед перезапуском будь-чого.
Чекліст B: 1‑годинна діагностика (знайдіть справжнє вузьке місце)
- Топ‑запити за сумарним часом: використовуйте агрегацію slow log або перегляди performance schema (якщо доступні). Зосередьтеся на сумарному часі, а не лише на найдовшому окремому запиті.
- Валідація плану: EXPLAIN для найгірших; шукайте повні скани, filesort, тимчасові таблиці, погане порядкування джоінів.
- Класифікація очікувань: визначте, чи очікування здебільшого I/O, блокувань чи CPU. У MySQL 8.0 performance_schema може це чітко показати; у MariaDB більше покладайтеся на InnoDB status + лічильники статусу.
- Перевірка форми даних: розміри таблиць, розміри індексів, нерівномірний розподіл, гарячі рядки.
- Перевірка конкуренції: активні потоки, підключення, обсяг транзакцій, розміри батчів.
- Перевірка сховища: журнали ядра, латентність диска, поведінка файлової системи, сплески writeback.
- Визначте клас виправлення: запит/індекс, поведінка додатка, сховище або ємність.
Чекліст C: безпечна послідовність налаштувань (тільки після того, як ви знаєте вузьке місце)
- Спочатку виправлення запитів/індексів (найвищий ROI, найнижчий системний ризик). Робіть зміни тестованими й відкатними.
- Друге — контролі конкуренції (пулінг, черги, обмеження). Запобігайте thundering‑herds.
- Третє — розмір пам’яті (buffer pool, буфери на з’єднання). Слідкуйте за swap і OOM.
- Останні — I/O/налаштування надійності і лише з явним прийняттям ризику. Ви можете купити продуктивність за рахунок надійності; відновити надійність після втрати даних не вдасться.
Короткий жарт №2: Вимкнення надійності — це як зняти детектор диму, бо він шумить. Ви спатимете спокійно аж до моменту, коли вже ні.
Три міні‑історії з корпоративного світу (реалістично, анонімізовано)
Міні‑історія 1: інцидент через хибне припущення
Середня SaaS‑компанія перенесла критичний сервіс з MySQL на MariaDB, бо пакет постачальника це спрощував і додаток «використовує лише стандартний SQL». Команда ретельно провела міграцію схеми, запуск інтеграційних тестів і оголосила успіх. У стейджингу латентність виглядала нормально. Продакшн — інша тварина.
Хибне припущення було тонким: вони думали, що їхнє навантаження залежить від запитів. Насправді — ні. У продакшні була висока конкурентність операцій запису зі періодичними сплесками — щогодинні підсумки рахунків, кожна транзакція торкалася кількох гарячих рядків. Додаток використовував довгі транзакції з додатковими читаннями «щоб бути впевненим». Ці читання не були безпечні. Вони утримували блокування й посилювали тиск purge.
За день почали з’являтися «випадкові» повільні запити. Потім підкралося відставання реплікації. Потім одна репліка була підвищена під час іншого вікна обслуговування і система розвалилася: новий primary мав великий history list length, сховище мало періодичні fsync‑зависання, і додаток підсилював це відкриттям нових підключень під час повторів.
Спочатку вони звинуватили вибір движка. Це було зручно і невірно. Справжній винуватець — обсяг транзакцій і контенція гарячих рядків. Коли вони скоротили транзакції, додали відсутній композитний індекс і ввели обмежені повтори, обидва движки — MariaDB і MySQL — працювали добре. Движок важив менше, ніж дисципліна робочого навантаження.
Висновок постмортему був різким: «прямий замінник» стосується коректності, а не передбачуваності продуктивності. Вони додали воротар для міграцій: доведіть клас вузького місця під навантаженням, схожим на продакшн, перед переключенням движка.
Міні‑історія 2: оптимізація, що відбилася бумерангом
Рітейл‑платформа мала класичну проблему: повільний звіт під час пікових годин. Хтось запропонував просте рішення: збільшити буфери на підключення і розміри тимчасових таблиць, щоб сорти не виходили на диск. На папері це виглядало розумним. На практиці — спричинило каскад.
Зміни ввели в п’ятницю (не зі зла, а через оптимізм). Під час піку кількість підключень зросла через проблему upstream pooler. Тепер кожне підключення могло алокувати більше пам’яті для сортувань і з’єднань. Тиск пам’яті підскочив, ядро почало свопити, і раптом кожний запит — швидкий чи повільний — став повільнішим. Латентність стала стрибкоподібною, потім вирівнялася на високому рівні.
Вони швидко відкотили, але урок запам’ятався: пам’ятні налаштування множаться на конкурентність. «Більші буфери» — не безкоштовний обід; це шведський стіл, де кожен потік бере піднос. Якщо додаток запрошує 1000 гостей — потрібен контролер, а не більша кухня.
Вони вирішили банально. Спочатку відремонтували пулінг з’єднань і встановили здорові обмеження конкурентності. Потім додали цільовий індекс і переписали запит звіту на попереднє агрегування. Після цього помірні налаштування тимчасових таблиць справді допомогли, бо вже не було боротьби зі swap.
Міні‑історія 3: нудна, але правильна практика, що врятувала день
Фінансова команда тримала MySQL і MariaDB в різних бізнес‑підрозділах. Інфраструктура була заплутана, але їхня операційна гігієна була надзвичайно доброю. На кожному DB‑хості був стандартний скрипт «захоплення доказів»: він збирав processlist, InnoDB status, ключові лічильники статусу, IO‑статистику і спайс slow logs, і зберігав це з таймштампами.
Одного дня вони отримали сплеск таймаутів. On‑call запустив скрипт двічі з інтервалом у дві хвилини. Другий запуск показав зростання Innodb_log_waits і раптовий стрибок дискового await. Processlist показав багато коротких записів, що чекали commit. Це не була проблема плану запиту; це була проблема латентності запису.
Оскільки вони мали дані, не витрачали час на суперечки про різницю движків. Перевірили журнали ядра й знайшли ресети на одному NVMe. RAID/контролер маскував проблему настільки, що вона виглядала як «повільність бази». Вони швидко перейшли на стендбай і замінили пристрій того ж дня.
Виправлення не було креативним. Воно було повторюваним і підкріпленим доказами. Їхній «нудний скрипт» запобіг тижню забобонного тюнінгу і суперечці про те, яка форк‑версія MySQL «стабільніша».
Поширені помилки: симптом → корінна причина → виправлення
Тут продакшн‑систем визнають свої гріхи. Кожен пункт конкретний, бо невизначені поради — це як інструмент для відтворення інцидентів.
1) Симптом: «CPU високий, але запити прості»
Корінна причина: конкуренція і контенція. Багато «простих» запитів все одно можуть конкурувати за гарячі рядки, індекси або внутрішні мютекси. Ще одна поширена причина: неефективні порівняння колацій або виклики функцій над індексованими колонками, що заважає використанню індексу.
Виправлення: Знайдіть топ‑запити за сумарним часом, підтвердьте використання індексів через EXPLAIN і зменшіть конкуренцію в додатку. Розгляньте додавання або переформування індексів, щоб уникнути гарячих точок.
2) Симптом: «Затримка стрибає кожні кілька хвилин, потім відновлюється»
Корінна причина: I/O‑flush шторми, поведінка чекпоінтів або глюки сховища. Також: фонова робота або DDL, що запускається по розкладу.
Виправлення: Корелюйте сплески з iostat await, Innodb_os_log_fsyncs і поведінкою брудних сторінок. Перенесіть пікові операції поза часом навантаження, батчуйте записи і перевірте здоров’я сховища.
3) Симптом: «Відставання репліки росте, але CPU низький»
Корінна причина: застосування SQL на репліці серіалізується через гарячі рядки, великі транзакції, DDL або однониткове застосування. Низький CPU не означає, що все добре; це може означати очікування на I/O або блокування.
Виправлення: Перевірте ріст relay log, довгі транзакції, увімкніть/налаштуйте багатониткову реплікацію там, де можливо, і зменшіть розмір транзакцій на primary.
4) Симптом: «Багато підключень, але Threads_running не дуже високий»
Корінна причина: зміна підключень і ресурсні накладки; додаток не використовує пулінг або health checks надто агресивні. Також: max_connections встановлено високо, щоб «вирішити» помилки, і це створює більшу потенційну площу ураження.
Виправлення: Виправте пулінг, обмежте конкурентність, використайте проксі/пулер якщо треба, і залишайте max_connections реалістичним, щоб при перевантаженні система помирала швидко, а не повільно.
5) Симптом: «Дедлоки з’явилися після додавання індексу»
Корінна причина: змінені шляхи доступу змінили порядок захоплення блокувань. Індекс не створив дедлоки; він виявив помилки порядку під конкуренцією.
Виправлення: Стандартизувати порядок оновлень у коді, зменшити обсяг транзакцій і забезпечити обмежені повтори при дедлоках. Використовуйте послідовні WHERE‑клаузи та уникайте range‑апдейтів на гарячих таблицях.
6) Симптом: «ALTER TABLE зависає вічно»
Корінна причина: metadata lock чекає за довгою транзакцією, часто «безпечне» читання залишене відкритим пулом з’єднань.
Виправлення: Знайдіть блокуючу сесію через processlist/InnoDB status, вбийте або закрийте довгу транзакцію і застосуйте онлайн‑методи зміни схеми з операційними запобіжниками.
7) Симптом: «I/O‑util низький, але await високий»
Корінна причина: черги в стеку зберігання, тротлінг, шумний сусід або латентність, вкинута прошивкою/контролером. %util може брехати на сучасних пристроях і в віртуалізованих середовищах.
Виправлення: Довіряйте метрикам латентності, перевірте журнали ядра, підтвердіть здоров’я пристрою і розгляньте ізоляцію I/O (виділені томи, кращий QoS) перед настройкою InnoDB.
8) Симптом: «Запити стали повільнішими після оновлення»
Корінна причина: регресії плану оптимізатора, змінені значення за замовчуванням, інша поведінка статистик або різні SQL‑режими. Тут MariaDB і MySQL відрізняються більше, ніж визнають.
Виправлення: Захопіть EXPLAIN‑плани до/після, освіжіть статистики, зафіксуйте критичні запити за допомогою протестованих hint‑ів якщо потрібно, і запускайте відтворення навантаження або тестування під навантаженням, а не лише unit‑тести.
MariaDB vs MySQL: де відмінності насправді болять
Якщо ви діагностуєте вузькі місця, вибір движка важливий переважно там, де він змінює інструментацію, поведінку оптимізатора, керування конкуренцією і семантику реплікації. Ось як думати про це, не перетворюючи обговорення на релігійну війну.
Інструментація: чи бачите ви, що відбувається?
Performance schema у MySQL 8.0 — сильна перевага для діагностики в продакшні: очікування, стадії, зведення підписів запитів і контенція робляться видимими. У MariaDB інструментація теж є, але зрілість і налаштування за замовчуванням відрізняються в залежності від версії і дистрибутива.
Операційна порада: оберіть одну «золотий шлях» для захоплення доказів на кожен движок. Для MySQL 8.0 спирайтесь на performance_schema і зведення digest. Для MariaDB перевірте, що увімкнено, і покладайтесь на комбінацію статус‑лічильників, InnoDB status, slow logs і таргетованого семплінгу.
Дрейф оптимізатора: той самий запит — різний план
Ось де «прямий замінник» може запалити. Запит може коректно працювати в обох системах, але обрати різний порядок джоінів або шлях індексу. Під навантаженням це вже не академічно; це різниця між «працює» і «тане в руках».
Операційна порада: трактуйте зміну движка та великі оновлення як події зміни плану. Зафіксуйте базові плани критичних запитів і протестуйте регресії під репрезентативним розподілом даних.
Поведінка конкурентності: обробка потоків і внутрішня контенція
MariaDB часто обговорюють у зв’язку з поведінкою thread pool в деяких дистрибутивах. MySQL має власні механізми обробки потоків і покращення в різних версіях. У обох випадках тема одна: неконтрольована конкуренція перетворює затримку на лотерею.
Операційна порада: обмежуйте конкуренцію на рівні додатка й на межі бази. Не «вирішуйте» перевантаження підняттям max_connections. Це як розширювати воронку, поки труба засмічена.
Реплікація і кластери: різні інструменти, різні режими відмов
Екосистема MySQL схиляється до GTID, semi‑sync, group replication і managed‑service патернів. MariaDB часто зустрічається з Galera. Обидва рішення можуть бути надійними. Обидва також можуть падати так, що це виглядає як «випадкова повільність», коли реальна проблема — flow control, конфлікти сертифікації або серіалізація застосування.
Операційна порада: визначте, що вам потрібно: масштабування читань, відновлення, multi‑writer чи георозподіл. Потім спроектуйте для цього. Не вмикайте «кластер» як фічу продуктивності; це фіча доступності з витратами на затримку.
Реальність движка зберігання: більшість людей просто запускає InnoDB
Якщо ваше навантаження InnoDB, багато вузьких місць будуть однаковими в обох системах: промахи buffer pool, flush redo log, doublewrite, page flushing і контенція блокувань. Саме тому цей чекліст працює незалежно від бренду.
Операційна порада: якщо хтось каже «MariaDB повільніша» або «MySQL повільніший», запитайте: повільніша в якому класі вузького місця? І де докази?
Питання та відповіді
1) Який найшвидший спосіб знайти вузьке місце?
Класифікуйте: CPU‑bound, IO‑bound, lock‑bound чи replication‑bound. Використайте vmstat, iostat -x і SHOW FULL PROCESSLIST перед тим, як чіпати конфіг.
2) З чого починати: slow query log чи performance schema?
Починайте з того, що вже увімкнено й надійно працює. Slow query log має низький поріг входження і працює в обох движках. Performance schema багатший у MySQL 8.0, але треба знати, що увімкнено й який наклад ви готові прийняти.
3) Якщо дискова латентність висока, чи можна просто збільшити buffer pool?
Лише якщо у вас є запас RAM і ваш робочий набір зможе виграти від цього. Якщо активний датасет значно більший за пам’ять, збільшення buffer pool має спадну віддачу. Спочатку виправте селективність і індексацію.
4) Чому важливіше «Threads_running» ніж «Threads_connected»?
Connected показує, скільки клієнтів підключено. Running показує, скільки активно використовує CPU або очікує всередині движка. Високі показники running часто корелюють з контенцією й чергуванням.
5) Відставання репліки — це проблема бази чи додатка?
Обидва. Відставання часто викликане формою транзакцій додатка: великі транзакції, гарячі рядки або DDL. Налаштування бази (паралелізм застосування, I/O‑потужність) може допомогти, але не зробить фундаментально послідовне навантаження паралельним.
6) Коли можна змінювати налаштування надійності заради продуктивності?
Коли бізнес явно приймає ризик і ви можете кількісно оцінити радіус ураження. Інакше ставте надійність як незмінну і вирішуйте реальне вузьке місце (сховище, частота транзакцій, батчинг).
7) Мій запит використовує індекс, але все одно повільний — чому?
«Використовує індекс» все ще може означати скан великого діапазону, випадкові I/O або подальше сортування. Дивіться rows examined, «Using filesort», «Using temporary» і чи індекс відповідає фільтру + порядку.
8) Як уникнути регресій планів під час оновлень MariaDB/MySQL?
Зафіксуйте критичні запити (EXPLAIN‑плани та виконання під репрезентативними даними), програвайте робоче навантаження або навантажувальні тести і порівнюйте топ‑підписи запитів до і після. Розглядайте зміни оптимізатора як і функцію, і ризик.
9) Яка найпоширеніша корінна причина «іноді повільно»?
Черги: fsync‑зависання, очікування блокувань або фонова скидання сторінок. Перервна латентність зазвичай означає, що щось блокує, а не те, що CPU раптом забув як рахувати.
10) Чи варто переходити з MySQL на MariaDB (або навпаки) щоб виправити продуктивність?
Не як перший крок. Перехід може допомогти в конкретних випадках (функції, інструментація, модель реплікації), але більшість виграшів у продуктивності отримують через виправлення запитів/індексів, дисципліну транзакцій і коректність сховища.
Наступні кроки, що не зіпсують вам вихідні
Ось практичний план, який я би передав ротації on‑call, що хоче менше загадкових 3 ранку і більш передбачувані системи:
- Стандартизувати захоплення доказів: скрипт/runbook, що збирає processlist, InnoDB status, ключові лічильники і статистику I/O хоста з таймштампами.
- Увімкнути хоча б одне джерело трасування запитів: slow query log з розумними порогами, плюс захоплення плану для топ‑порушників.
- Встановити таксономію вузьких місць: CPU, I/O, блокування, реплікація, пам’ять. Тегуйте інциденти відповідно. Шаблони з’являються швидко.
- Виправити форму навантаження: скоротіть транзакції, додайте відсутні композитні індекси, зменшіть join‑fan‑out і обмежте конкурентність на стороні додатка.
- Лише потім налаштовувати: підганяйте розміри пам’яті і I/O‑параметри на основі доказів, і робіть це з кроками відкату.
Справжня перевага MariaDB vs MySQL у продуктивності — не секретний параметр. Це операційна ясність. Ви перемагаєте, швидко знаходячи вузьке місце, виправляючи реальну причину і відмовляючись плутати активність з прогресом.