Выбор базы данных для приложения часто включает в себя выбор базы данных, совместимой с ACID, которая обеспечивает согласованность и целостность транзакций. Однако обеспечение согласованности в многопользовательской среде может быть затруднено из-за проблем параллелизма, которые возникают, когда несколько транзакций пытаются одновременно получить доступ или изменить одни и те же данные. В этой статье мы сосредоточимся на «I» в ACID и углубимся в эти проблемы параллелизма, изучая, как мир баз данных назвал и решил их с помощью различных гарантий изоляции. Кроме того, мы рассмотрим, как эти гарантии различаются для разных баз данных и как они влияют на производительность приложений. Понимая эти концепции, мы можем принимать обоснованные решения при выборе базы данных для нашего приложения, обеспечивая ее надежность и согласованность.

Эта серия разделена на три части. Вот список статей серии.

Параллелизм и база данных: роль гарантий изоляции — часть I
Параллелизм и база данных: роль гарантий изоляции — часть II
Параллелизм и база данных: роль гарантий изоляции — часть III

Отказ от ответственности

Большая часть информации на этой странице взята из главы 7: Транзакции книги Designing Data-Intensive Applications, написанной Мартином Клеппманном в 2017 году. Если вы уже читали эту главу, возможно, вы уже встречались с большинством тем. рассматривается в этой статье.

параллелизм

Параллельные транзакции включают две или более транзакций, которые выполняются одновременно и обращаются к одним и тем же данным или ресурсам в системе баз данных. Это может привести к перекрытию временных шкал, когда несколько транзакций могут обращаться к одним и тем же данным или ресурсам в разные моменты времени. Важно отметить, что когда мы говорим «одно и то же время» в этом контексте, это не обязательно означает один и тот же снимок времени. Вместо этого он относится к транзакциям с перекрывающимися временными шкалами, которые могут обращаться к одним и тем же данным или ресурсам в какой-то момент во время их выполнения.

Изоляция транзакции

Параллелизм часто ассоциируется с блокировками, но базы данных уже решили многие связанные проблемы, которые мы можем упустить из виду на уровне изоляции. К сожалению, нам не хватает такой роскоши, как отсутствие параллелизма или бесконечные ресурсы. Рассмотрим два крайних сценария:

  1. приложение обрабатывает один запрос за раз, тем самым устраняя параллелизм, и
  2. база данных обрабатывает одновременные запросы последовательно, избегая проблем параллелизма.

В обоих случаях пропускная способность системы ограничена, а база данных является узким местом в сценарии 2. Усиление параллелизма вызывает больше проблем, которые мы рассмотрим, изучив сценарии с ограничениями базы данных и без них и обсудив, как уровни изоляции могут решить эти проблемы.

Грязная запись

Предположим, что одновременно выполняются две транзакции, A и B. Транзакция A обновляет строку в таблице, а затем приостанавливается без фиксации. Затем транзакция B обновляет ту же строку в таблице с другими данными и фиксирует изменения. Позже транзакция A возобновляет и перезаписывает изменения, сделанные транзакцией B, своими данными перед фиксацией. Это грязная запись, поскольку транзакция A перезаписала незафиксированные изменения, сделанные транзакцией B.

Если мы сможем как-то заблокировать перезапись незафиксированных изменений, то это решит проблему, скажем, УРОВЕНЬ ИЗОЛЯЦИИ I решит эту проблему, Он не позволит перезаписать незафиксированные изменения

set session transaction isolation level read uncommitted; begin; -- T1
set session transaction isolation level read uncommitted; begin; -- T2
update test set value = 11 where id = 1; -- T1
update test set value = 12 where id = 1; -- T2, BLOCKS as T1 has uncommitted changes
update test set value = 21 where id = 2; -- T1
commit; -- T1. This unblocks T2

Все уровни транзакций как в Postgres, так и в MySQL не разрешают грязную запись, что означает, что запись обновляется транзакцией, и эта транзакция еще не зафиксирована. Все остальные транзакции будут ждать, пока эта транзакция либо зафиксируется, либо откатится.

Грязное чтение

Предположим, что одновременно выполняются две транзакции, A и B. Транзакция А считывает строку из таблицы, а затем приостанавливается без фиксации. Затем транзакция B обновляет ту же строку в таблице новыми данными, но еще не зафиксировала изменения. Позже транзакция A возобновляет работу и считывает ту же строку, видя незафиксированные изменения, сделанные транзакцией B. Это грязное чтение, поскольку транзакция A прочитала незафиксированные данные, которые могут быть отменены транзакцией B.

BEGIN; -- Transaction 1
BEGIN; -- Transaction 2
SELECT * FROM posts WHERE id = 2; -- t1
UPDATE posts SET status = 'verified' WHERE id = 2; -- t2
SELECT * FROM posts WHERE id = 2; -- t1, reads status as 'verified' 
-- trigger verification email
ROLLBACK; -- t2
COMMIT; -- t1

В приведенном выше примере, даже если значение было отменено, мы приняли решение, которое несовместимо с состоянием нашей БД.

Если мы сможем каким-то образом игнорировать незафиксированные изменения, это решит проблему, скажем, УРОВЕНЬ ИЗОЛЯЦИИ II решит эту проблему, Он не будет читать незафиксированные изменения.

Вообще говоря, большая часть базы данных (не все) имеет уровень изоляции READ COMMITTED, который на самом деле является комбинацией УРОВЕНЬ ИЗОЛЯЦИИ I и УРОВЕНЬ ИЗОЛЯЦИИ II, описанных выше. который будет

  • Блокировать запись, если есть незафиксированные изменения какой-либо другой транзакцией того же набора строк
  • Только чтение зафиксированных изменений при чтении данных из базы данных
begin; set transaction isolation level read committed; -- T1
begin; set transaction isolation level read committed; -- T2
update test set value = 101 where id = 1; -- T1
select * from test; -- T2. Still shows 1 => 10
abort;  -- T1
select * from test; -- T2. Still shows 1 => 10
commit; -- T2

Блокировки для грязной записи

В базах данных появление грязных записей часто предотвращается путем реализации блокировок на уровне строк. Всякий раз, когда транзакция стремится изменить объект, такой как строка или документ, она должна сначала получить блокировку этого конкретного объекта, которая должна удерживаться до тех пор, пока транзакция не будет зафиксирована или прервана. Только одна транзакция способна удерживать блокировку для любого данного объекта. Если другая транзакция хочет изменить тот же объект, она должна дождаться завершения первой транзакции, прежде чем сможет получить блокировку и продолжить.

Будет ли то же самое работать с Dirty Reads?

Чтобы предотвратить грязное чтение, одним из решений является использование той же блокировки и требование временного получения блокировки любой транзакцией, которая хочет прочитать объект. Блокировка должна быть снята сразу после процесса чтения, гарантируя, что никакие операции чтения не будут выполняться, пока объект имеет грязное, незафиксированное значение. В течение этого времени блокировка будет удерживаться транзакцией, выполнившей запись.

Однако такой подход, требующий блокировки чтения, может оказаться непрактичным, поскольку длительная транзакция записи может привести к тому, что многие транзакции только для чтения будут ожидать ее завершения. Это может отрицательно сказаться на времени отклика транзакций только для чтения. Замедление в одной части приложения из-за ожидания блокировки может иметь эффект домино в другой части приложения.

Штаты приходят на помощь

В результате база данных запоминает как старое зафиксированное значение, так и новое значение, установленное транзакцией, которая в настоящее время удерживает блокировку записи для каждого записываемого объекта. Пока транзакция выполняется, всем другим транзакциям, читающим объект, присваивается старое значение. Только когда новое значение фиксируется, транзакции переключаются на чтение нового значения.

Итак, Read commited решил вышеуказанные проблемы, давайте посмотрим, что дальше по списку в нашей следующей статье здесь

Если вы нашли эту статью полезной, поставьте ей лайк и подпишитесь на нашу публикацию, чтобы получать больше таких технических статей. Ваша поддержка мотивирует меня продолжать создавать ценный контент для вас. Спасибо за чтение!

Рекомендации



https://dev.mysql.com/doc/refman/8.0/en/innodb-transaction-isolation-levels.html

https://stackoverflow.com/questions/72850415/isolation-level-difference-between-dirty-write-and-lost-update

Клеппманн, М. (2017). Проектирование приложений, интенсивно использующих данные: большие идеи, лежащие в основе надежных, масштабируемых и удобных в сопровождении систем. O’Reilly Media, Inc. Глава 7: Транзакции. https://dataintensive.net/

https://github.com/ept/hermitage

Михалча, Влад. Транзакции и контроль параллелизма. High-Performance Java Persistence, Влад Михалча, 2016, стр. 87–122, https://vladmihalcea.com/books/high-performance-java-persistence/.

https://martin.kleppmann.com/2014/11/25/hermitage-testing-the-i-in-acid.html