Пошук уроків, статей та іншого контенту
Порівняєте аномалії конкурентного доступу та визначите, які з них допускає кожен рівень ізоляції.
Коли кілька транзакцій одночасно читають і змінюють ті самі дані, результат читання може залежати від моменту виконання запиту. Такі небажані ефекти називають аномаліями конкурентного доступу.
У цьому уроці розглянемо:
Dirty Read — брудне читання;
Non-repeatable Read — неповторюване читання;
Phantom Read — фантомне читання.
Рівень ізоляції транзакції визначає, які з цих аномалій дозволені.
Dirty Read виникає, коли транзакція читає зміни іншої транзакції до того, як та виконала COMMIT.
Наприклад:
Транзакція A змінює ціну товару з 100 на 80.
Транзакція A ще не завершилася.
Транзакція B читає ціну й отримує 80.
Транзакція A виконує ROLLBACK.
Насправді ціна знову стає 100, але транзакція B уже бачила значення 80, якого ніколи не було в зафіксованих даних.
Такі дані називають «брудними», оскільки вони можуть бути скасовані.
Створімо тестову таблицю:
DROP TABLE IF EXISTS products;
CREATE TABLE products (
id integer PRIMARY KEY,
name text NOT NULL,
price integer NOT NULL
);
INSERT INTO products (id, name, price)
VALUES (1, 'Keyboard', 100);У першому з'єднанні виконайте:
BEGIN;
UPDATE products
SET price = 80
WHERE id = 1;
-- Транзакція ще не завершенаУ другому з'єднанні:
BEGIN;
SELECT price
FROM products
WHERE id = 1;Результат буде:
price
-------
100PostgreSQL не покаже незавершену зміну першої транзакції. Тепер у першому з'єднанні виконайте:
ROLLBACK;У PostgreSQL dirty read не допускається навіть для рівня READ UNCOMMITTED. Цей рівень фактично працює так само, як READ COMMITTED.
Non-repeatable Read виникає, коли одна транзакція двічі читає той самий рядок і отримує різні значення, тому що між читаннями інша транзакція змінила та зафіксувала цей рядок.
Послідовність може бути такою:
Транзакція A читає ціну товару: 100.
Транзакція B змінює ціну на 80 і виконує COMMIT.
Транзакція A повторює той самий SELECT.
Транзакція A отримує вже 80.
Перше й друге читання виконуються в межах однієї транзакції, але результат відрізняється.
READ COMMITTEDREAD COMMITTED є рівнем ізоляції за замовчуванням у PostgreSQL.
У першому з'єднанні:
BEGIN ISOLATION LEVEL READ COMMITTED;
SELECT price
FROM products
WHERE id = 1;Результат:
price
-------
100Не завершуйте цю транзакцію. У другому з'єднанні виконайте:
BEGIN;
UPDATE products
SET price = 80
WHERE id = 1;
COMMIT;Тепер знову виконайте запит у першому з'єднанні:
SELECT price
FROM products
WHERE id = 1;Результат:
price
-------
80У READ COMMITTED кожна команда отримує власний знімок даних на момент початку виконання. Тому два SELECT в одній транзакції можуть побачити різні зафіксовані версії рядка.
Завершіть першу транзакцію:
COMMIT;REPEATABLE READУ REPEATABLE READ усі звичайні запити транзакції працюють з одним знімком даних.
У першому з'єднанні:
BEGIN ISOLATION LEVEL REPEATABLE READ;
SELECT price
FROM products
WHERE id = 1;У другому з'єднанні змініть і зафіксуйте ціну:
BEGIN;
UPDATE products
SET price = 120
WHERE id = 1;
COMMIT;У першому з'єднанні повторіть читання:
SELECT price
FROM products
WHERE id = 1;Транзакція продовжить бачити попереднє значення, яке було доступним на момент створення її знімка.
price
-------
80Після завершення транзакції нова транзакція вже побачить 120:
COMMIT;
SELECT price
FROM products
WHERE id = 1;Phantom Read виникає, коли одна транзакція двічі виконує запит за умовою, а між цими запитами інша транзакція додає або видаляє рядки, що відповідають цій умові.
На відміну від неповторюваного читання, тут змінюється не значення вже наявного рядка, а сам набір рядків.
Наприклад:
Транзакція A шукає всі товари з ціною не меншою за 100.
Вона отримує два рядки.
Транзакція B додає ще один товар із ціною 150 і виконує COMMIT.
Транзакція A повторює той самий запит.
Тепер вона отримує три рядки.
Новий рядок є «фантомом» для першої транзакції.
READ COMMITTEDДодайте ще один товар:
INSERT INTO products (id, name, price)
VALUES (2, 'Mouse', 50);У першому з'єднанні:
BEGIN ISOLATION LEVEL READ COMMITTED;
SELECT id, name, price
FROM products
WHERE price >= 100
ORDER BY id;Результат:
id | name | price
----+----------+-------
1 | Keyboard | 120У другому з'єднанні додайте товар і зафіксуйте транзакцію:
BEGIN;
INSERT INTO products (id, name, price)
VALUES (3, 'Monitor', 150);
COMMIT;У першому з'єднанні повторіть запит:
SELECT id, name, price
FROM products
WHERE price >= 100
ORDER BY id;Тепер результат міститиме новий рядок:
id | name | price
----+----------+-------
1 | Keyboard | 120
3 | Monitor | 150Отже, READ COMMITTED допускає phantom read.
Завершіть транзакцію:
COMMIT;REPEATABLE READ у PostgreSQLУ PostgreSQL REPEATABLE READ використовує один знімок даних для всієї транзакції. Тому нові зафіксовані рядки, додані іншою транзакцією, не з'являться в повторному читанні.
BEGIN ISOLATION LEVEL REPEATABLE READ;
SELECT id, name, price
FROM products
WHERE price >= 100
ORDER BY id;Якщо інша транзакція після цього додасть товар, повторний запит у першій транзакції все одно використовуватиме початковий знімок:
SELECT id, name, price
FROM products
WHERE price >= 100
ORDER BY id;У PostgreSQL phantom read не виникає на рівні REPEATABLE READ.
Це важлива особливість реалізації PostgreSQL. У формальному стандарті SQL рівень REPEATABLE READ може допускати phantom read, але PostgreSQL забезпечує сильнішу поведінку.
PostgreSQL підтримує такі рівні:
READ UNCOMMITTED;
READ COMMITTED;
REPEATABLE READ;
SERIALIZABLE.
READ UNCOMMITTEDУ PostgreSQL цей рівень поводиться так само, як READ COMMITTED.
Dirty read: не допускається.
Non-repeatable read: допускається.
Phantom read: допускається.
Приклад оголошення:
BEGIN ISOLATION LEVEL READ UNCOMMITTED;Використання цього рівня не дає PostgreSQL дозволити читання незавершених змін.
READ COMMITTEDЦе рівень за замовчуванням.
Dirty read: не допускається.
Non-repeatable read: допускається.
Phantom read: допускається.
Кожна команда бачить дані, зафіксовані до початку саме цієї команди.
REPEATABLE READDirty read: не допускається.
Non-repeatable read: не допускається.
Phantom read: не допускається в PostgreSQL.
Усі запити транзакції бачать один і той самий знімок даних.
SERIALIZABLEDirty read: не допускається.
Non-repeatable read: не допускається.
Phantom read: не допускається.
Цей рівень намагається забезпечити результат, еквівалентний послідовному виконанню транзакцій. Якщо PostgreSQL виявить конфлікт, одна з транзакцій може завершитися помилкою serialization failure.
У такому випадку застосунок має повторити всю транзакцію:
BEGIN ISOLATION LEVEL SERIALIZABLE;
-- Операції транзакції
COMMIT;Якщо під час COMMIT виникне конфлікт, клієнт може отримати помилку на кшталт could not serialize access due to .... Повторювати потрібно всю транзакцію, а не лише останній запит.
Для кожного рівня можна узагальнити поведінку так:
READ UNCOMMITTED у PostgreSQL фактично дорівнює READ COMMITTED.
READ COMMITTED допускає неповторювані та фантомні читання.
REPEATABLE READ фіксує знімок транзакції та запобігає всім трьом аномаліям у PostgreSQL.
SERIALIZABLE забезпечує найсуворішу ізоляцію, але може вимагати повторного запуску транзакцій через конфлікти.
Вищий рівень ізоляції не завжди означає кращу продуктивність. Він може призводити до більшої кількості конфліктів і повторних виконань транзакцій. Рівень потрібно вибирати відповідно до вимог конкретної операції.
READ UNCOMMITTED дозволяє dirty read у PostgreSQLУ PostgreSQL це не так. READ UNCOMMITTED автоматично поводиться як READ COMMITTED.
Якщо змінилося значення вже наявного рядка — це non-repeatable read.
Якщо змінився набір рядків, які відповідають умові запиту, — це phantom read.
READ COMMITTEDУ READ COMMITTED знімок створюється для кожної команди окремо. Два SELECT в одній транзакції можуть бачити різні зафіксовані дані.
REPEATABLE READ однаковим у всіх СУБДНазви рівнів ізоляції стандартизовані, але конкретна реалізація може бути сильнішою за мінімальні вимоги стандарту. У PostgreSQL REPEATABLE READ не допускає phantom read.
На рівні SERIALIZABLE конфлікт може завершити транзакцію помилкою. Застосунок повинен повторити всю транзакцію з початку.
Dirty Read — читання незакомітованих змін іншої транзакції.
Non-repeatable Read — повторне читання того самого рядка повертає інше значення.
Phantom Read — повторний запит повертає інший набір рядків.
PostgreSQL не допускає dirty read на жодному зі своїх рівнів.
READ COMMITTED допускає non-repeatable read і phantom read.
REPEATABLE READ у PostgreSQL запобігає всім трьом аномаліям завдяки стабільному знімку транзакції.
SERIALIZABLE забезпечує найсуворішу ізоляцію, але конфліктні транзакції можуть доводитися повторювати.