Перший раз — бо вивчив список питань напам'ять і не розумів, що стоїть за відповідями. Другий — бо підготувався з AI, а він красиво збрехав. Особиста і трохи ганебна історія про те, які питання по MySQL реально ловлять на співбесіді у 2026-му — від PRIMARY KEY до EXPLAIN — і чому список правильних відповідей не рятує жодного разу.
Зізнаюся одразу: я двічі провалював одну й ту саму співбесіду.
Не тому що не готувався — готувався обидва рази сумлінно. А тому що обидва рази готувався не так.
Розповім про обидва провали детально, по питаннях, — бо це, по суті, весь список того, що реально запитують про MySQL у джуна і мідла. Просто дорога до кожного пункту в мене була на рідкість болісною.
Раунд перший: я визубрив список і згорів на першому ж «а чому»
План був простий і, як мені здавалося, надійний: нагуглити список питань, вивчити формулювання, прийти і відтарабанити. Перші п'ять хвилин співбесіди пройшли відмінно. Далі почалися уточнення.
PRIMARY KEY проти UNIQUE. Тут я не підвів: PRIMARY KEY гарантує унікальність і не пускає NULL, UNIQUE теж гарантує унікальність, але NULL дозволяє. У таблиці один PRIMARY KEY і скільки завгодно UNIQUE. Далі інтерв'юер запитав: «а що InnoDB робить, якщо первинного ключа немає взагалі?» — і от тут я поплив. Виявилося, InnoDB не розводить руками, а сама вибирає перший підходящий UNIQUE-індекс з NOT NULL, а якщо і такого немає — створює прихований службовий ідентифікатор. Формулювання я знав, механіку під ним — ні.
FOREIGN KEY. Запитали, що відбувається при видаленні батьківського запису. Тут я непогано перелічив варіанти: CASCADE видаляє дочірні записи слідом, RESTRICT блокує операцію, SET NULL обнуляє зовнішній ключ. Затримка вийшла на питанні «а як у вас у проєкті?» — і тут з'ясувалося, що я знаю теорію, але жодного разу не замислювався, що частина команд навмисно переносить цю логіку на рівень застосунку, а не довіряє її базі.
CHAR проти VARCHAR. Це теж пройшло гладко: CHAR — рядок фіксованої довжини, зберігає рівно оголошену кількість байтів, VARCHAR — змінної, зберігає лише потрібне плюс один-два службові байти. CHAR швидше там, де довжина і справді фіксована — код країни, стать, — VARCHAR годиться для всього іншого.
DATETIME проти TIMESTAMP. А от тут я сів красиво. TIMESTAMP зберігає секунди з 1970 року і сам конвертується під часовий пояс клієнта, але впирається у 2038 рік. DATETIME зберігає дату як є, без прив'язки до поясу, і живе аж до 9999-го. Я бадьоро відповів «просто різні формати» — і отримав закономірне питання, який з них обрати для системи, яка має пережити 2038 рік. Мовчання було красномовним.
Далі пішов JOIN — і тут я видихнув занадто рано
JOIN я знав залізно, тому розслабився — і дарма, бо саме на розслабленості на цьому питанні і ловлять.
INNER JOIN віддає лише ті рядки, де знайшовся збіг в обох таблицях. LEFT JOIN — усі рядки лівої таблиці цілком, а там, де пари не знайшлося, — NULL замість значень з правої. Я це відтарабанив. А потім мене запитали: «що поверне LEFT JOIN, якщо збігів немає взагалі жодного?» Я чесно відповів «порожній результат» — і був неправий. Результат буде, просто дірявий: усі рядки лівої таблиці з NULL замість правої. Класична пастка для тих, хто знає формулювання, але жодного разу не перевіряв руками.
Дали живу задачку — знайти студентів, які не вирішили жодної задачі:
SELECT students.*
FROM students
LEFT JOIN solved_tasks ON students.id = solved_tasks.student_id
WHERE solved_tasks.id IS NULL;Написав за хвилину — з цим впорався. А от на питанні про CROSS JOIN знову загальмував і з ходу не зміг пояснити, чому він небезпечний. CROSS JOIN — це декартів добуток: кожен рядок першої таблиці з кожним рядком другої. Мільйон записів тут і мільйон там без умови з'єднання — і я щойно попросив базу побудувати трильйон рядків результату. Залом інтерв'юера, здається, пробігла легка тривога за долю продакшена.
GROUP BY і HAVING — тут я героїчно наступив на власні граблі
Запитали, чому не можна просто взяти і вибрати негрупований стовпець при GROUP BY. Я бадьоро видав завчене «MySQL вибере будь-яке значення з групи» — і це була застаріла відповідь. З режимом ONLY_FULL_GROUP_BY, який увімкнений за замовчуванням з MySQL 5.7, такий запит просто впаде з помилкою. Дозволено вибирати лише самі стовпці групування, агрегати і функціонально залежні від них поля.
Далі — задачка: вивести категорії курсів, де середній рейтинг вищий за 4.5.
SELECT category, AVG(rating) as avg_rating
FROM courses
GROUP BY category
HAVING AVG(rating) > 4.5;Я за інерцією ледь не написав умову через WHERE — і вчасно себе зупинив. WHERE фільтрує рядки до групування і поняття не має про AVG(rating), бо на цьому етапі групування ще не існує. HAVING фільтрує вже готові групи. Переплутати їх — стабільна класика першої співбесіди.
Індекси — от тут все і закінчилося
Запитали, як влаштований індекс. Відповів правильно: це B+дерево з відсортованих значень з указівником на рядок, прискорює читання. Запитали: «а чому тоді не проіндексувати взагалі всі стовпці?» — і от на цьому простому питанні я завис найдовше. Довелося визнати вголос, що не думав про це раніше: кожен INSERT і UPDATE зобов'язаний оновити всі індекси таблиці, а не лише самі дані, — за швидкість читання завжди платиш швидкістю запису.
Про складений індекс і правило лівого префікса я знав лише формулювання. Індекс на (course_id, level) відпрацює для умови по course_id, відпрацює для course_id + level, але виявиться марним для одного level — бо дерево відсортоване за першим стовпцем, і шукати за другим без нього так само безглуздо, як шукати слово в паперовому словнику, знаючи лише другу літеру. Я це промовив — але коли попросили намалювати дерево на папері, стало зрозуміло, що я цитую, а не розумію.

Добили функцією поверх колонки:
-- індекс не використовується
WHERE DATE(created_at) = '2026-01-01'
-- індекс використовується
WHERE created_at >= '2026-01-01' AND created_at < '2026-01-02'MySQL порівнює результат функції, а не значення, що зберігається — а в індексі лежать сирі значення, не перераховані. Чому перший варіант не використовує індекс, я пояснити не зміг — просто запам'ятав як факт, без причини за ним.
Останнім цвяхом став EXPLAIN — план для запиту по прогресу студентів:
| id | table | type | possible_keys | key | rows |
| 1 | student_progress | ALL | NULL | NULL | 100000 |
type = ALL — повне сканування таблиці. possible_keys = NULL — підходящого індексу немає взагалі. На ста тисячах рядків це не «може бути проблема», це вона і є. Я це побачив, але не зміг з ходу сказати, що робити далі. Якщо зовсім коротко: покривний індекс — це індекс, у який уже включені всі стовпці, потрібні запиту, тому MySQL читає лише його і взагалі не заглядає в таблицю. Мені знадобилося три підказки інтерв'юера, щоб згадати це своїми словами.
Відмова прийшла ввічлива. Висновок я зробив грубий: список питань не дорівнює розумінню. Формулювання я знав усі. Механіку за ними — приблизно наполовину.
Раунд другий: я підготувався з AI — і AI мене красиво підвів
На другий захід я не повторював старої помилки. Розгорнув локальний MySQL, поганяв EXPLAIN руками, відчув індекси не як визначення, а як різницю в секундах на реальній таблиці. З транзакціями і версіями вирішив не ризикувати і уточнити все в нейромережі — благо, три секунди на відповідь, навіщо гуглити.
Про ACID нейромережа видала гладкий текст, я його переказав: атомарність — все або нічого, цілісність транзакції ніхто не переживе наполовину; узгодженість — дані після транзакції залишаються в узгодженому стані; ізольованість — паралельні транзакції не заважають одна одній; довговічність — підтверджені дані не втрачаються навіть при збої.
Приклад на ізольованість я придумав сам, і він несподівано зайшов. Уяви склад з однією останньою одиницею товару. Один процес читає залишок і без підтвердженої ізоляції бачить чужі незакомічені зміни — наприклад, що товар уже списаний, хоча друга транзакція ще може відкотитися. Це брудне читання: прочитав те, що ще не факт. Другий сценарій — читання, що не повторюється: в рамках однієї транзакції двічі прочитав залишок і отримав два різних числа, бо між читаннями інша транзакція встигла закомітити свої зміни.
Тут пощастило: запитали рівень ізоляції InnoDB за замовчуванням, і я не поліз у чат — згадав сам. REPEATABLE READ, в основі — MVCC (Multi-Version Concurrency Control), яка тримає для транзакції узгоджений знімок даних на весь її час. Додав, що у PostgreSQL дефолт інший, READ COMMITTED, — і побачив, що це зарахували як плюс.
Про InnoDB і MyISAM теж пройшло гладко: MyISAM не вміє в транзакції і блокує таблицю цілком, а не рядок. У 2026-му обирати її для нового проєкту — як обрати Java 8 для нового бекенду: технічно працює, а пояснити навіщо — не вийде.
Далі — оптимізація. Про проблему N+1 розповів без запинки: один запит дістає список користувачів, а потім для кожного — ще один запит окремо, і замість одного JOIN отримуєш N+1 похід у базу. Діагностика — не по коду, а по логу: там видно однотипну кулеметну чергу дрібних SELECT-ів замість одного складеного запиту. Про SELECT * теж не підвів: тягне всі стовпці без розбору, а якщо є покривний індекс — змушує базу все одно йти в таблицю за зайвим, обнуляючи весь сенс індексу. Про JOIN і підзапит чесно визнав: у MySQL 8.4 оптимізатор часто сам приводить їх до порівнянних планів, і обирати варто за читабельністю і за фактом з EXPLAIN, а не за міфом «джойни завжди швидші».
А потім добила версія MySQL.
Запитав у чату перед співбесідою: «яка версія MySQL зараз актуальна для продакшена?» Відповідь прийшла впевнена: MySQL 8.0, стабільна і перевірена. Я це записав, вивчив і на співбесіді видав з повною переконаністю.
Інтерв'юер помовчав секунду і запитав: «в курсі, що 8.0 у квітні 2026-го дійшла до кінця підтримки?» Не в курсі. Актуальна лінія — MySQL 8.4 LTS, підтримка від Oracle до 2032 року, поряд уже вийшла MySQL 9.7 як новий LTS-реліз. Сказати «ми на 8.0» з упевненим виглядом у 2026-му — як заявити, що пишеш на Python 2: формально буває, але звучить як явний сигнал, що стек роками не чіпали.
Заодно запитали про MySQL і PostgreSQL — тут я впорався, у обох у 2026-му різниця на простих операціях невелика, MySQL як і раніше сильний у CRUD і навантаженнях на читання, PostgreSQL — у складній аналітиці. А от питання про MariaDB мене добило остаточно: я на автоматі назвав її «тим самим MySQL, просто форк» — а по факту це самостійна СУБД, історично сумісна на рівні багатьох запитів і протоколів, але давно розійшлася з MySQL достатньо, щоб перед міграцією все перевіряти окремо.
Друга відмова. Прикріше за першу — бо я чесно готувався, просто довірився відповіді, яку ніхто не перевірив.
Що об'єднує обидва провали
Переглядав обидві співбесіди і побачив одну й ту саму помилку в різній обгортці. Першого разу я скопіював у голову список відповідей. Другого — скопіював у голову список відповідей, які згенерувала нейромережа. Різниця в джерелі, а не в підході. І там, і там я не розумів, що стоїть за фразами, які вимовляю.
От тут і проходить та сама межа, про яку ми говоримо постійно. Вайб-підхід — запитати в нейромережі готову відповідь, вставити в голову, не перевіривши. AI-native підхід — поставити те саме питання, а потім розгорнути локальну базу і переконатися руками, чи актуальна відповідь, перш ніж нести її на живу розмову. Згенерувати правильний на вигляд SQL або гладке визначення сьогодні може будь-хто — модель та сама у всіх. А от перевірити і пояснити, чому це так, — уже ні. За даними PwC, розробники, які саме так і працюють з AI — розуміють, а не просто переказують, — заробляють щонайменше на 56% більше за тих, хто обмежується копіпастою.
Співбесіда, якщо розібратися, завжди запитує одне й те саме: не «що ти пам'ятаєш», а «що ти розумієш настільки, щоб пояснити комусь, хто ставить незручні уточнення». Список питань цю перевірку не проходить. Проходять лише руки, які самі це чіпали.
Що тепер
Третій захід я готую інакше — не список, а практика: розгортаю базу, ламаю її сам, читаю EXPLAIN до і після кожного рішення, і все, що радить AI, перевіряю на своїй же таблиці, перш ніж повторити вголос хоч слово.

Якщо в тебе фундаменту під SQL поки немає — не повторюй мій перший провал, почни одразу з рук, а не зі списку. На інтерактивному курсі SQL від JavaRush ти пишеш і розбираєш запити з першого уроку — і сама на своїх помилках намацуєш, де стане індекс, а де ні. А коли фундамент є і хочеться не повторити мій другий провал — навчитися користуватися нейромережею по-інженерному, а не перетворювати її на джерело красиво поданої застарілої інформації, — з цього починається JavaRush AI Native Developer.
Третьої співбесіди я не боюся. Цього разу я, здається, нарешті розумію, а не пам'ятаю.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ