Transaction isolation levels (рівні ізоляції транзакцій) визначають, як транзакції взаємодіють одна з одною та які побічні ефекти можуть виникати при їх паралельному виконанні. Рівні ізоляції допомагають балансувати між продуктивністю системи та вимогами до точності і консистентності даних. У стандарті SQL визначені чотири рівні ізоляції:
Read Uncommitted (Читання незавершених даних):
Read Committed (Читання завершених даних):
COMMIT
).Repeatable Read (Повторюване читання):
Serializable (Серіалізоване):
Ось приклад, як встановити різні рівні ізоляції для транзакцій в MySQL:
-- Встановити рівень ізоляції Read Uncommitted
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
-- Встановити рівень ізоляції Read Committed
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- Встановити рівень ізоляції Repeatable Read (за замовчуванням в MySQL)
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
-- Встановити рівень ізоляції Serializable
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
Щоб дізнатись поточний рівень ізоляції:
SELECT @@transaction_isolation;
Наведемо приклад використання рівня ізоляції Read Uncommitted
, щоб показати, як транзакція може читати незавершені дані (dirty reads).
Припустимо, у нас є дві транзакції, T1 і T2, що виконуються одночасно.
Таблиця users
:
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(100),
amount INT
);
INSERT INTO users (id, name, amount) VALUES (1, 'Jon', 100);
INSERT INTO users (id, name, amount) VALUES (2, 'Jane', 50);
START TRANSACTION;
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
UPDATE users SET amount = amount - 100 WHERE id = 1;
-- Не робимо COMMIT або ROLLBACK, залишаємо транзакцію незавершеною
START TRANSACTION;
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
-- Читаємо дані до завершення T1
SELECT * FROM users WHERE id = 1;
Давайте розглянемо, що відбувається:
amount
у користувача з id = 1
на 100, але не завершує транзакцію (COMMIT
або ROLLBACK
не виконується).Read Uncommitted
, читає значення amount
для користувача з id = 1
.На рівні ізоляції Read Uncommitted
, T2 може побачити незавершені зміни, зроблені T1. Отже, запит у T2 може повернути amount = 0
для користувача з id = 1
, хоча ці зміни ще не були підтверджені.
Наведемо приклад використання рівня ізоляції Read Committed
, щоб показати, як транзакція може бачити лише завершені зміни інших транзакцій.
Припустимо, у нас є дві транзакції, T1 і T2, що виконуються одночасно.
Таблиця users
:
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(100),
amount INT
);
INSERT INTO users (id, name, amount) VALUES (1, 'Jon', 100);
INSERT INTO users (id, name, amount) VALUES (2, 'Jane', 50);
Транзакція T1:
START TRANSACTION;
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
UPDATE users SET amount = amount - 100 WHERE id = 1;
-- Не робимо COMMIT або ROLLBACK, залишаємо транзакцію незавершеною
Транзакція T2:
START TRANSACTION;
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- Читаємо дані до завершення T1
SELECT * FROM users WHERE id = 1;
Давайте розглянемо, що відбувається:
amount
у користувача з id = 1
на 100, але не завершує транзакцію (COMMIT
або ROLLBACK
не виконується).Read Committed
, читає значення amount
для користувача з id = 1
.На рівні ізоляції Read Committed
, T2 не побачить незавершених змін, зроблених T1. Отже, запит у T2 поверне amount = 100
для користувача з id = 1
, оскільки зміни, зроблені T1, ще не підтверджені (COMMIT
не було виконано).
Рівень ізоляції Repeatable Read
забезпечує, що дані, прочитані однією транзакцією, залишатимуться незмінними протягом всієї її тривалості, навіть якщо інші транзакції змінюють ці дані. Це захищає від проблеми "неповторюваних читань" (non-repeatable read), але не гарантує захисту від "привидів" (phantom reads), коли нові рядки можуть бути додані іншими транзакціями.
Repeatable Read
і читає значення amount
для користувача з id = 1
. Значення становить 100.amount
для користувача з id = 1
, зменшуючи його на 50. Після цього транзакція T2 завершується (COMMIT
).amount
для користувача з id = 1
в T1 повертає 100, оскільки рівень ізоляції Repeatable Read
гарантує, що дані, прочитані на початку транзакції, залишатимуться незмінними протягом всієї її тривалості.Це гарантує, що T1 бачить консистентний набір даних протягом всієї транзакції, незважаючи на зміни, зроблені іншими транзакціями.
Repeatable Read
і читає всіх користувачів, у яких amount > 25
. На цей момент у таблиці є два рядки, які задовольняють цю умову: Jon і Jane.amount = 30
і завершується (COMMIT
).SELECT * FROM users WHERE amount > 25
і бачить нового користувача (Jack, 30). Це є прикладом "привида", оскільки новий рядок з'явився в результаті запиту під час виконання транзакції T1.Таким чином, хоча рівень ізоляції Repeatable Read
гарантує, що дані, прочитані на початку транзакції, залишатимуться незмінними протягом всієї її тривалості, він не захищає від додавання нових рядків іншими транзакціями, що може призвести до появи "привидів" під час повторних запитів.
Рівень ізоляції Serializable
є найвищим рівнем ізоляції транзакцій, що забезпечує повну ізоляцію між транзакціями. Він гарантує, що транзакції виконуються послідовно, якби вони виконувалися одна за одною, виключаючи всі можливі аномалії паралельного виконання, такі як dirty reads, non-repeatable reads та phantom reads.
При рівні ізоляції Serializable
база даних забезпечує, що:
Це досягається за допомогою блокувань, які можуть значно зменшити продуктивність, оскільки блокуються не тільки окремі рядки, але й цілі діапазони значень.
Serializable
і читає всіх користувачів, у яких amount > 25
. Вона отримує два рядки: Jon і Jane.amount = 30
, але ця операція блокується, оскільки T1 ще не завершилася.COMMIT
), звільняючи блокування.Рівень ізоляції Serializable
забезпечує, що навіть додавання нових рядків не змінює результати запитів, виконаних іншими транзакціями. Це досягається шляхом блокування необхідних ресурсів, що може впливати на продуктивність, але гарантує повну ізоляцію транзакцій.