Представьте, что вы работаете в кафе, в котором один официант проверяет запас тортов на кухне, а другой заполняет заказ на торт от нового клиента. В идеальном мире они оба должны работать с одинаковыми данными о количестве тортов, чтобы избежать ошибок вроде "двойного бронирования". Но в реальном мире могут возникнуть проблемы параллельного выполнения операций.
Вот три основные неприятности, которые могут произойти:
Dirty Read (грязное чтение): Один запрос видит изменения, которые сделаны другим, но ещё не зафиксированы. Если эти изменения будут откатаны, первый запрос окажется наивным, как студент на первом собеседовании.
Non-Repeatable Read (неповторяющееся чтение): Один запрос дважды читает те же данные, но между этими чтениями кто-то другой успевает их изменить. Это как прийти на вокзал, увидеть расписание поездов, вернуться через минуту — и обнаружить, что поезд был отменён. Ну или твой билет купили, пока ты искал деньги :)
Phantom Read (фантомное чтение): Один запрос видит подмножество строк, но между двумя выполнениями кто-то добавляет новые строки, которые влияют на результат. Это как если бы ваша компания проиграла тендер, а потом все заявки кроме вашей и заявки жены мера были отменены.
Уровни изоляции транзакций
Теперь, когда мы знаем о проблемах, пора заглянуть в инструмент, который PostgreSQL предоставляет для их решения — уровни изоляции транзакции. Это как установка правил для взаимодействия между параллельными транзакциями. Чем выше уровень изоляции, тем больше гарантий, что транзакции не будут друг другу мешать. Однако за это приходится платить сниженными "скоростью обслуживания", то есть производительностью.
Уровни изоляции в PostgreSQL
Read Uncommitted (Чтение неподтверждённых данных):
- Позволяет чтение изменений, которые еще не были зафиксированы (да, это грязное чтение во всей его красе).
- В PostgreSQL такой уровень на самом деле реализован как
Read Committed, так что в чистом виде он не поддерживается. PostgreSQL отказывается его реализовывать, так как этот подход слишком ненадёжный.
Read Committed (Чтение зафиксированных изменений):
- Предотвращает грязное чтение.
- Транзакция видит только те данные, которые были зафиксированы к моменту выполнения её команды.
- Однако возможны
Non-Repeatable ReadиPhantom Read.
Repeatable Read (Повторяемое чтение):
- Гарантирует, что данные, которые вы читаете, останутся неизменными во время транзакции.
- Предотвращает грязное чтение и неповторяющееся чтение.
- Однако фантомные строки все ещё возможны.
Serializable (Сериализуемый):
- Гарантирует, что транзакции выполняются так, как если бы они выполнялись последовательно, одна за другой.
- Предотвращает все три проблемы: грязное чтение, неповторяющееся чтение и фантомные строки.
- Самый строгий — и самый медленный — уровень изоляции.
Почему изоляция транзакций важна?
Теперь представьте базу данных интернет-магазина, в котором тысяча пользователей пытаются одновременно оформить заказ. Без грамотно настроенного уровня изоляции можно столкнуться с огромным количеством конфликтов: от "пропавших" товаров до дублирующихся заказов.
Выбор нужного уровня изоляции помогает справляться с конкурентным выполнением транзакций, обеспечивая баланс между производительностью и целостностью данных. Например:
- В системах аналитики чаще выбирают минимальные уровни изоляции (например, Read Committed), так как точность данных не всегда критична.
- В финансовых системах предпочтителен уровень Serializable, чтобы избежать ошибок в расчётах или дублирования операций.
Примеры применения уровней изоляции
- Read Committed
Этот уровень обеспечивает, что вы никогда не прочитаете данные, которые могли быть откатаны.
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN;
-- Прочитать данные счета.
SELECT balance FROM accounts WHERE account_id = 1;
-- Если другая транзакция обновит баланс, эти данные немедленно обновятся.
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
COMMIT;
- Repeatable Read
Этот уровень гарантирует, что если вы читаете данные, они останутся неизменными для вас на протяжении транзакции.
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN;
-- Прочитать данные счета.
SELECT balance FROM accounts WHERE account_id = 1;
-- Даже если другая транзакция обновит этот баланс, вы не увидите изменения.
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
COMMIT;
- Serializable
В этом уровне транзакция работает так, как если бы она была единственной в системе.
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN;
-- Прочитать данные счёта.
SELECT balance FROM accounts WHERE account_id = 1;
-- Любая другая транзакция, пытающаяся изменить эти данные, будет заблокирована до завершения вашей транзакции.
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
COMMIT;
Как выбрать уровень изоляции?
Выбор уровня изоляции зависит от ваших требований:
- Если скорость важнее точности, и вы готовы терпеть фантомные строки — выбирайте
Read Committed. - Если точность важна, но вы все еще хотите сохранить высокую производительность — используйте
Repeatable Read. - Если вам нужна 100% гарантия корректности данных, даже в ущерб скорости — вам подходит
Serializable.
Будьте осторожны! Строгие уровни изоляции могут привести к блокировкам и снижению производительности системы. Разумное решение — компромисс между производительностью и согласованностью данных.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ