Junior: питання на співбесіді з теми «Транзакції й блокування»
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
4 питання
ACID - чотири гарантії, які дає транзакція в реляційній базі:
- Atomicity (атомарність) - усі операції транзакції виконуються або всі разом, або жодна. Переказ грошей не може зняти суму з одного рахунку й не зарахувати на інший.
- Consistency (узгодженість) - транзакція переводить базу з одного коректного стану в інший: обмеження (
NOT NULL, унікальність, зовнішні ключі,CHECK) виконуються після кожної завершеної транзакції. - Isolation (ізольованість) - паралельні транзакції не бачать проміжних станів одна одної. Наскільки суворо - визначає рівень ізоляції.
- Durability (довговічність) - після
COMMITдані не зникнуть навіть при падінні сервера: їх уже записано в журнал (WAL у PostgreSQL, redo log в InnoDB) на диск.
BEGIN;
UPDATE accounts SET balance = balance - 500 WHERE id = 1;
UPDATE accounts SET balance = balance + 500 WHERE id = 2;
COMMIT;
Якщо між двома UPDATE сервер впаде, після перезапуску жодної зміни не буде - атомарність.
Важливо для співбесіди: ізольованість не означає «транзакції виконуються по черзі». На рівнях за замовчуванням (Read Committed у PostgreSQL, Repeatable Read у MySQL) деякі аномалії паралельного виконання можливі, і їх треба враховувати в коді.
ROLLBACK скасовує всі зміни, зроблені в поточній транзакції, і завершує її. Так, ніби нічого не було.
BEGIN;
DELETE FROM orders WHERE created_at < '2020-01-01';
-- ой, не та дата
ROLLBACK;
Коли транзакція відкочується без явного ROLLBACK:
- Розірвалося з'єднання або впав процес застосунку посеред транзакції - база відкотить її сама.
- Помилка в PostgreSQL: після будь-якої помилки транзакція переходить у стан «aborted», і всі наступні команди відхиляються з
current transaction is aborted, доки не будеROLLBACK. - Deadlock: база обирає одну з транзакцій-«жертв» і відкочує її.
Відмінність MySQL: помилка однієї команди (наприклад, порушення унікальності) за замовчуванням відкочує лише цю команду, а не всю транзакцію. Тому покладатися на «база сама все відкотить» небезпечно - застосунок має явно робити ROLLBACK при помилці.
Ще є SAVEPOINT - точка, до якої можна відкотити частину транзакції, не скасовуючи все. Laravel використовує їх для вкладених DB::transaction().
Пастка: DDL у MySQL (CREATE TABLE, ALTER TABLE) неявно комітить поточну транзакцію, тож відкотити міграцію з кількома змінами схеми не вийде. У PostgreSQL DDL транзакційний.
Autocommit - режим, у якому кожна команда - окрема транзакція: вона або виконується й одразу фіксується, або відкочується при помилці. Це поведінка PostgreSQL і більшості драйверів за замовчуванням.
UPDATE accounts SET balance = balance - 100 WHERE id = 1; -- уже закомічено
UPDATE accounts SET balance = balance + 100 WHERE id = 2; -- окрема транзакція
Якщо між двома командами щось впаде, гроші спишуться, але не зарахуються.
Явна транзакція об'єднує команди:
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
Що варто знати:
- Одна команда атомарна й без
BEGIN:UPDATE ... WHERE id IN (1, 2)абоINSERTз кількома рядками виконається повністю або ніяк. - Тригери й каскадні дії виконуються в межах тієї самої транзакції, що й команда, яка їх запустила.
- Незавершена транзакція (забули
COMMIT) тримає блокування й заважає вакууму. Сесія в станіidle in transaction- поширена проблема на проді. - У PHP: PDO працює в autocommit, доки не викликати
beginTransaction(). LaravelDB::transaction(fn () => ...)робитьBEGIN/COMMITіROLLBACKпри винятку.
Неявні транзакції в інших СУБД: в Oracle й у SQL Server з IMPLICIT_TRANSACTIONS транзакція починається сама з першою командою й триває до явного COMMIT. А в MySQL DDL-команди (ALTER TABLE) неявно комітять поточну транзакцію - у PostgreSQL DDL транзакційний і відкочується разом з рештою.
DELETE FROM orders видаляє рядки по одному: позначає кожен як видалений (MVCC), запускає тригери ON DELETE, перевіряє зовнішні ключі, може мати WHERE. Місце звільняється пізніше, вакуумом.
TRUNCATE orders звільняє таблицю цілком - фактично підміняє файли таблиці порожніми. Миттєво навіть на мільярді рядків, і місце повертається одразу.
Відмінності:
DELETE |
TRUNCATE |
|
|---|---|---|
Умова WHERE |
так | ні, лише вся таблиця |
| Швидкість на великій таблиці | повільно, пропорційно кількості рядків | миттєво |
| Тригери на рядки | спрацьовують | ні (є окремі ON TRUNCATE) |
| Блокування | рядків | ексклюзивне на таблицю (ACCESS EXCLUSIVE) |
Лічильник identity/serial |
не скидає | скидає з RESTART IDENTITY |
| Зовнішні ключі | перевіряються по рядках | помилка, якщо на таблицю посилаються, без CASCADE |
Чи можна відкотити: у PostgreSQL так - TRUNCATE транзакційний:
BEGIN;
TRUNCATE orders;
ROLLBACK; -- дані на місці
У MySQL TRUNCATE - DDL-команда з неявним комітом, і відкотити її не можна.
Застереження:
TRUNCATEбере найсильніше блокування: поки транзакція з ним не завершилася, таблицю не може читати ніхто.TRUNCATE ... CASCADEочистить і всі таблиці, що посилаються на цю, - легко знищити більше, ніж планували.- У тестах
TRUNCATEміж тестами - швидкий спосіб очищення (так працюєDatabaseTruncationу Laravel), але транзакція з відкатом (RefreshDatabase) ще швидша.