Введение
В мире баз данных, безопасность и надежность являются фундаментальными аспектами, на которых строится эффективная работа с данными. Одной из ключевых составляющих безопасности в контексте транзакционных операций является уровень изоляции транзакций.
В этой статье я хочу на понятных примерах рассказать и показать: что вообще такое изоляции, для чего они нужны и как их можно использовать.
Если вы новичок в области баз данных или опытный разработчик, стремящийся углубить знания, эта статья предлагает вам полезную информацию и практические советы. Давайте начнем наше увлекательное путешествие в мир уровней изоляции транзакций, чтобы обрести уверенность и мастерство в работе с этой ключевой составляющей систем управления базами данных.
Ныряем в теорию
Для начала поговорим о кислоте(ACID)
ACID гласит, что во время выполнения транзакции другие транзакции не должны оказывать влияния на результат. Однако, обеспечение полной изоляции транзакций может быть затратным и иметь несколько неприятных последствий, включая:
Потерянное обновление: В случае, когда две параллельные транзакции изменяют одни и те же данные, возникает неопределенность в итоговом состоянии данных. Результат обновления может быть непредсказуемым, поскольку обе транзакции конфликтуют и могут перезаписать изменения друг друга.
"Грязное" чтение: В рамках транзакции могут возникать промежуточные результаты запроса из параллельных транзакций, которые ещё не завершились. Это может привести к непоследовательности результатов и некорректному отображению данных.
Неповторяющееся чтение: Если в рамках одной транзакции выполняются запросы с одними и теми же условиями, результаты этих запросов могут быть разными. Это происходит из-за того, что другие параллельные транзакции уже внесли изменения в данные между выполнением запросов, что приводит к несогласованности результатов.
Фантомное чтение: В результате повторного выполнения одного и того же запроса в рамках транзакции могут появляться и исчезать строки данных, которые были модифицированы параллельными транзакциями. Это создает впечатление, что некоторые строки "появляются из ниоткуда" или "исчезают магическим образом".
Все эти нежелательные последствия возникают при использовании уровней изоляции транзакций, которые не полностью гарантируют абсолютную изоляцию. В следующих разделах мы рассмотрим различные уровни изоляции транзакций в PostgreSQL и их влияние на эти аспекты, а также предоставим практические примеры, чтобы помочь вам лучше понять и выбрать наиболее подходящий уровень изоляции для ваших задач.
Существуют 4 уровня изоляции транзакций, которые позволяют устранить недостатки, связанные с параллельным выполнением транзакций. Давайте рассмотрим их в порядке возрастания уровня изоляции:
READ_UNCOMMITTED : (Чтение неподтвержденных данных): В этом уровне могут возникать грязные чтения, неповторяющиеся чтения, фантомные чтения и потерянное обновление. Здесь транзакции не полностью изолированы друг от друга, поэтому они могут видеть нефиксированные изменения других транзакций.
READ_COMMITTED : Грязные чтения предотвращены на этом уровне, но могут возникать неповторяющиеся чтения, фантомные чтения и потерянное обновление. Транзакции видят только подтвержденные изменения от других транзакций.
REPEATABLE_READ : Грязные чтения, неповторяющиеся чтения и потерянное обновление предотвращены на этом уровне, но могут возникать фантомные чтения. Результаты запросов внутри транзакции остаются постоянными даже в присутствии изменений, внесенных другими транзакциями.
SERIALIZABLE : На этом уровне транзакции полностью изолированы друг от друга. Исключается влияние одной транзакции на другую во время выполнения. Этот уровень предотвращает все нежелательные эффекты, связанные с параллельным выполнением транзакций.
Каждый из этих уровней изоляции имеет свои собственные особенности и применим в различных сценариях. В следующих разделах нашей статьи мы подробно рассмотрим READ_COMMITTED, REPEATABLE_READ и SERIALIZABLE так как будем оперировать с Postgresql.
В PostgreSQL вы можете запросить любой из четырёх уровней изоляции транзакций, однако внутри реализованы только три различных уровня, то есть режим Read Uncommitted в PostgreSQL действует как Read Committed. Причина этого в том, что только так можно сопоставить стандартные уровни изоляции с реализованной в PostgreSQL архитектурой многоверсионного управления конкурентным доступом.
Так же в документации Postgres есть следующая табличка описывающее то, как различные уровни изоляции решают неприятные следствия описанные ранее:
Уровень изоляции |
«Грязное» чтение |
Неповторяемое чтение |
Фантомное чтение |
Аномалия сериализации |
---|---|---|---|---|
Read uncommited (Чтение незафиксированных данных) |
Допускается, но не в PG |
Возможно |
Возможно |
Возможно |
Read committed (Чтение зафиксированных данных) |
Невозможно |
Возможно |
Возможно |
Возможно |
Repeatable read (Повторяемое чтение) |
Невозможно |
Невозможно |
Допускается, но не в PG |
Возможно |
Serializable (Сериализуемость) |
Невозможно |
Невозможно |
Невозможно |
Невозможно |
По моему мнению информацию запоминается немого куда луче, когда она подкреплена устоявшимся и запоминающимся визуальным рядом, поэтому немного изменим формат этой таблицы и пройдемся по ней еще раз, обозначив каждый вид изоляции своим персонажем, чей уровень крутости будет напрямую зависеть от уровня изоляции (это никоем образом не уменьшает значимость каждого уровня, каждый из них незаменим в своем бизнес-кейсе):
Read Committed: Предположим, у вас есть транзакция A, которая начинает чтение данных из таблицы с заказами. Затем транзакция B вносит изменения в таблицу заказов и фиксирует их.
На уровне изоляции Read Committed транзакция A не будет видеть изменения, внесенные транзакцией B, поскольку эти изменения еще не были зафиксированы. Транзакция A будет видеть только зафиксированные данные, которые были доступны на момент начала своей транзакции.
Repeatable Read: Предположим, у вас есть транзакция A, которая начинает чтение данных из таблицы с товарами. В то же время, транзакция B добавляет новый товар в эту таблицу.
При использовании уровня изоляции Repeatable Read, транзакция A будет видеть данные, как если бы они были зафиксированы в момент начала транзакции A. То есть, даже если транзакция B добавила новые данные, транзакция A не будет видеть эти данные, поскольку она ориентируется на снимок данных, сделанный на момент начала.
Serializable: Предположим, у вас есть две транзакции A и B, которые одновременно пытаются изменить баланс счета на определенную сумму. Транзакция A начинает свою операцию до того, как транзакция B начинает свою операцию.
На уровне изоляции Serializable обеспечивается последовательная изоляция транзакций. Если транзакция A уже начала енение баланса счета, то транзакция B не сможет начать свою операцию до того времени, пока транзакция A не завершится. Это предотвращает конфликты и обеспечивает строгую целостность данных.
Советы по применению
Рассмотрим некоторые конкретные бизнес-примеры, в которых каждый из уровней изоляции транзакций может быть полезен.
Read Committed: Допустим, у вас есть электронный магазин, где пользователи могут оформлять заказы. При использовании уровня изоляции Read Committed каждая транзакция будет видеть только зафиксированные данные, что обеспечит целостность заказов и предотвратит возможность просмотра незафиксированных изменений в других заказах.
Repeatable Read: Представьте, что у вас есть онлайн-банк, где пользователи могут осуществлять переводы между своими счетами. Использование уровня изоляции Repeatable Read гарантирует, что каждый перевод будет основан на согласованном снимке данных, и предотвращает возможность появления фантомных переводов или непредвиденных изменений баланса счетов между операциями.
Serializable: Пусть у вас будет система управления складом, где разные сотрудники могут одновременно добавлять или изменять записи о наличии товаров. Использование уровня изоляции Serializable обеспечит строгую целостность данных и минимальное количество конфликтов при одновременном доступе к складским записям.
Очень важно анализировать требования вашего конкретного бизнеса и взвешивать потребности в целостности данных, производительности и одновременном доступе при выборе подходящего уровня изоляции транзакций.
Помните, чем выше уровень изоляции, тем выше расходы производительности на операцию
Примеры
Read Committed:
-- Транзакция A
BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED;
SELECT * FROM orders WHERE status = 'pending';
-- В другой параллельной транзакции будет внесено изменение в статус заказа
SELECT * FROM orders WHERE status = 'pending';
COMMIT;
-- Транзакция B
BEGIN TRANSACTION;
-- Изменение статуса заказа в параллельной транзакции
UPDATE orders SET status = 'fulfilled' WHERE order_id = 123;
COMMIT;
-- Транзакция A видит только зафиксированные изменения и не учитывает изменения, сделанные транзакцией B, до фиксации.
Repeatable Read:
-- Транзакция A
BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SELECT * FROM products;
-- В другой параллельной транзакции будет добавлен новый товар
SELECT * FROM products;
COMMIT;
-- Транзакция B
BEGIN TRANSACTION;
-- Добавление нового товара в параллельной транзакции
INSERT INTO products (product_id, name) VALUES (1001, 'New Product');
COMMIT;
-- Транзакция A видит только данные, как если бы они были зафиксированы в момент начала транзакции A, и не учитывает новый добавленный товар.
Serializable:
-- Транзакция A
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
UPDATE accounts SET balance = balance + 100 WHERE account_id = 1;
COMMIT;
-- Транзакция B
BEGIN TRANSACTION;
-- Попытка выполнить ту же операцию, что и транзакция A
UPDATE accounts SET balance = balance + 100 WHERE account_id = 1;
COMMIT;
-- В результате использования уровня изоляции Serializable возникнет блокировка, и транзакция B будет ожидать завершения транзакции A, чтобы избежать конфликтов и обеспечить последовательность выполнения операций.
Заключение
В заключении можно подчеркнуть, что глубокое понимание уровней изоляции транзакций имеет существенное значение при разработке и проектировании баз данных.
Выбор уровня изоляции должен основываться на спецификах вашего приложения и требованиях к целостности данных. Более высокий уровень изоляции может обеспечить большую надежность данных, но может также привести к снижению производительности из-за возникновения блокировок и ожиданий..
Помните, что выбор правильного уровня изоляции должен быть продуманным решением, учитывающим требования вашего приложения и бизнес-логику.
Благодарю вас за прочтение до конца! Пожалуйста, оставляйте свои комментарии, пожелания и возражения - это будет большим опытом для меня!
Комментарии (6)
kuza2000
19.10.2023 14:10Ой разгромят сейчас...
ИМХО, начать надо с того, что есть версионники, а есть блокировочники, и работают они немножко по разному...
kuza2000
19.10.2023 14:10/зануда-вкл
Однако, обеспечение полной изоляции транзакций может быть затратным и иметь несколько неприятных последствий, включая:
Все же, обеспечение полной изоляции не имеет этих последствий. Они как раз про неполную изоляцию.
/зануда-выкл
prika148
19.10.2023 14:10+1На мой взгляд, персонажи здесь использованы контринтуитивно - я бы расположил их наоборот
kuza2000
19.10.2023 14:10+1И что-то мне кажется, что Repeatable Read описан неправильно. Описано добавление строк, это фантомы. Repeatable Read - это про изменение данных уже существующих строк.
Если ошибаюсь - поправьте.
Valerdos_UA
19.10.2023 14:10Read commited - как возможны неповторяемые чтения и фантомы в транзакции А, если она оперирует снимком, сделанным на ее начало?
Algrinn
Да, существуют DBA-ориентированные системы, в которых архитектор базы данных главный. Обычно БД Oracle. В фирме сидят PL/SQL разработчики, которые конфигурируют БД, создают схему и пишут для неё сложные SQL запросы в транзакциях или PL/SQL запросы. Там обычный серверный Java программист не главный и никакого ORM там нет. А в обычных фирмах там ставят PostgreSQL или MySQL и оставляют все настройки по дефолту. Главное, чтобы БД не развалилась от жирных SQL-лин, которые нагенерирует ORM. :-)