Как СУБД гарантирует целостность данных при параллельной работе нескольких клиентов
Транзакция — группа SQL-операций, которые выполняются как одно целое: либо все, либо ни одна. Классический пример — перевод денег: нельзя списать с одного счёта без зачисления на другой.
-- Простая транзакция
BEGIN; -- начать транзакцию
UPDATE communities
SET rating = 5
WHERE name = 'TechTalks';
SELECT * FROM communities WHERE name = 'TechTalks';
-- В этой сессии уже видны изменения, в других — нет
COMMIT; -- зафиксировать изменения
-- Или: ROLLBACK; -- откатить все изменения в транзакции
При одновременной работе нескольких клиентов возникают классические аномалии — нарушения консистентности данных.
Чем выше уровень изоляции, тем меньше аномалий, но ниже производительность из-за блокировок.
| Уровень изоляции | Dirty Read | Non-Repeatable | Phantom Read |
|---|---|---|---|
| READ UNCOMMITTED | Возможно¹ | Возможно | Возможно |
| READ COMMITTED (по умолчанию) | Нет | Возможно | Возможно |
| REPEATABLE READ | Нет | Нет | Нет² |
| SERIALIZABLE | Нет | Нет | Нет |
¹ PostgreSQL не реализует Dirty Read, ² PostgreSQL защищает от Phantom при REPEATABLE READ
-- Проверить текущий уровень изоляции
SHOW TRANSACTION ISOLATION LEVEL;
-- Установить уровень для транзакции
BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
-- ...операции...
COMMIT;
-- Установить для сессии
SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE;
Для воспроизведения нужны две параллельные сессии (два окна psql).
| Время | Сессия 1 | Сессия 2 |
|---|---|---|
| t1 | BEGIN;SELECT rating FROM communities WHERE name='TechTalks'; → 4.6 | |
| t2 | BEGIN;SELECT rating FROM communities WHERE name='TechTalks'; → 4.6 | |
| t3 | UPDATE communities SET rating=4 WHERE name='TechTalks';COMMIT; | |
| t4 | UPDATE communities SET rating=3.6 WHERE name='TechTalks';COMMIT; — перезаписывает 4 → 3.6! |
Deadlock — ситуация, когда сессия 1 ждёт ресурс, захваченный сессией 2, а сессия 2 ждёт ресурс сессии 1. Бесконечное взаимное ожидание.
| Сессия 1 | Сессия 2 |
|---|---|
BEGIN;UPDATE communities SET rating=1 WHERE id=1; — заблокировала строку 1 |
BEGIN;UPDATE communities SET rating=2 WHERE id=2; — заблокировала строку 2 |
UPDATE communities SET rating=1 WHERE id=2; — ждёт сессию 2 |
UPDATE communities SET rating=2 WHERE id=1; — ждёт сессию 1 |
PostgreSQL обнаружит deadlock и автоматически откатит одну из транзакций с ошибкой: ERROR: deadlock detected |
|
SELECT … FOR UPDATE для явной блокировки строк перед изменением.