Автор оригинала: Vlad Mihalcea.
Вступление
В этой статье я собираюсь объяснить, как работает механизм MVCC (Управление параллелизмом нескольких версий), использующий PostgreSQL в качестве эталонной реализации.
В теории управления параллелизмом существует два способа разрешения конфликтов:
- Вы можете избежать их, используя пессимистичный механизм блокировки (например, блокировки чтения/записи, Двухфазная блокировка).
- Вы можете допустить возникновение конфликтов, но вам необходимо обнаружить их с помощью оптимистичного механизма блокировки (например, логические часы, MVCC).
Поскольку MVCC (Управление параллелизмом с несколькими версиями) является таким распространенным методом управления параллелизмом (не только в системах реляционных баз данных, в этой статье я объясню, как это работает.
Какова цель
Когда свойства транзакции ACID были впервые определены, предполагалась сериализуемость. И для обеспечения строгого сериализуемого результата транзакции был использован механизм 2PL (Двухфазной блокировки). При использовании 2PL для каждого чтения требуется получение общей блокировки, в то время как для операции записи требуется эксклюзивная блокировка.
- общая блокировка блокирует авторов, но позволяет другим читателям получить ту же общую блокировку
- эксклюзивная блокировка блокирует как читателей, так и авторов, совпадающих для одной и той же блокировки
Однако блокировка приводит к конфликту, а конфликт влияет на масштабируемость. Закон Амдала или Закон Универсальной масштабируемости демонстрируют, как разногласия могут повлиять на ускорение времени отклика.
По этой причине исследователи баз данных разработали другую модель управления параллелизмом, которая пытается свести блокировку к минимуму, чтобы:
- Читатели не блокируют Авторов
- Писатели не блокируют читателей
Единственный вариант использования, который все еще может привести к конфликту, – это когда две одновременные транзакции пытаются изменить одну и ту же запись, поскольку после изменения строка всегда блокируется до тех пор, пока транзакция, изменившая эту запись, либо не зафиксирует, либо не откатится.
Чтобы указать вышеупомянутое поведение без блокировки чтения/записи, механизм управления параллелизмом должен работать с несколькими версиями одной и той же записи, поэтому этот механизм называется Управлением параллелизмом нескольких версий (MVCC).
В то время как 2PL в значительной степени стандартен, стандартной реализации MVCC не существует, каждая база данных использует несколько иной подход. В этой статье мы будем использовать PostgreSQL, поскольку его реализацию MVCC проще всего визуализировать.
PostgreSQL
В то время как Oracle и MySQL используют журнал отмены для фиксации незафиксированных изменений, чтобы строки могли быть восстановлены до их ранее зафиксированной версии, PostgreSQL хранит все версии строк в структуре данных таблицы.
Что еще более интересно, так это то, что в каждой строке есть два дополнительных столбца:
- – который определяет идентификатор транзакции, в которую была вставлена запись
- – который определяет идентификатор транзакции, удалившей строку
В PostgreSQL идентификатор транзакции представляет собой 32-разрядное целое число, и процесс ОЧИСТКИ отвечает (среди прочего, например, за восстановление старых версий строк, которые больше не используются) за то, чтобы идентификатор не переполнялся.
По этой причине вы никогда не должны отключать ВАКУУМ, так как транзакция может привести к катастрофическим ситуациям .
MVCC (Управление параллелизмом нескольких версий) – Вставка записи
Чтобы понять, как ВСТАВКА работает в MVCC, рассмотрим следующую диаграмму:
- И Алиса, и Боб запускают новую транзакцию, и мы можем увидеть их идентификаторы транзакций, вызвав функцию
txid_current()
PostgreSQL - Когда Алиса вставляет новую
запись
строку, значение столбца устанавливается в идентификатор транзакции Алисы - При уровне изоляции по умолчанию “Чтение зафиксировано” Боб не может видеть вновь вставленную запись Алисы до тех пор, пока Алиса не совершит свою транзакцию
- После того, как Алиса зафиксировала, Боб теперь может видеть только что вставленную строку Алисы
Если идентификатор транзакции превышает значение зафиксированной строки, транзакции разрешается считывать эту версию записи.
Если идентификатор транзакции меньше значения, то решение о том, должна ли запись быть видимой или нет, зависит от уровня изоляции. Для ФИКСАЦИИ ЧТЕНИЯ отметка времени текущего выполняемого оператора становится нижней границей видимости строки. Для ПОВТОРЯЕМОГО ЧТЕНИЯ или СЕРИАЛИЗАЦИИ все считывания относятся к отметке времени начала текущей транзакции.
MVCC (Управление параллелизмом нескольких версий) – Удаление записи
Чтобы понять, как работает УДАЛЕНИЕ в MVCC, рассмотрим следующую диаграмму:
- И Алиса, и Боб запускают новую транзакцию, и мы можем увидеть их идентификаторы транзакций, вызвав функцию
txid_current()
PostgreSQL - Когда Боб удаляет строку
post
, для значения столбца устанавливается идентификатор транзакции Боба - При уровне изоляции по умолчанию “Чтение зафиксировано” до тех пор, пока Бобу не удастся зафиксировать свою транзакцию, Алиса все еще может видеть запись, удаленную ob
- После того, как Боб зафиксировал, Алиса больше не может видеть удаленную строку
В то время как в 2PL модификация Боба заблокировала бы инструкцию Alice read, в MVCC Алисе по-прежнему разрешено просматривать предыдущую версию до тех пор, пока Бобу не удастся совершить свою транзакцию.
Операция УДАЛЕНИЯ физически не удаляет запись, она просто помечает ее как готовую к удалению, и процесс УДАЛЕНИЯ соберет ее, когда эта строка больше не будет использоваться какой-либо текущей текущей транзакцией.
Если идентификатор транзакции превышает значение зафиксированной строки, транзакции больше не разрешается считывать эту версию записи.
Если идентификатор транзакции меньше значения, то решение о том, должна ли запись быть видимой или нет, зависит от уровня изоляции. Для ФИКСАЦИИ ЧТЕНИЯ отметка времени текущего выполняемого оператора становится нижней границей видимости строки. Для ПОВТОРЯЕМОГО ЧТЕНИЯ или СЕРИАЛИЗАЦИИ все считывания относятся к отметке времени начала текущей транзакции.
MVCC (Управление параллелизмом нескольких версий) – Обновление записи
Чтобы понять, как работает ОБНОВЛЕНИЕ в MVCC, рассмотрим следующую диаграмму:
- И Алиса, и Боб запускают новую транзакцию, и мы можем увидеть их идентификаторы транзакций, вызвав функцию
txid_current()
PostgreSQL - Когда Боб обновляет запись
post
, мы видим, что происходят две операции: УДАЛЕНИЕ и ВСТАВКА. Предыдущая версия строки помечается как удаленная путем установки значения столбца в идентификатор транзакции Боба, и создается новая версия строки, для которой значение столбца установлено в идентификатор транзакции Боба - При уровне изоляции по умолчанию Чтение зафиксировано, пока Бобу не удастся зафиксировать свою транзакцию, Алиса все еще может видеть предыдущую версию записи
- После того, как Боб зафиксировал, Алиса теперь может видеть новую версию строки, которая была обновлена Бобом
Вывод
Разрешив несколько версий одной и той же записи, будет меньше споров о чтении/записи записей, поскольку Читатели не будут блокировать авторов, а авторы также не будут блокировать читателей.
Хотя MVCC и не так интуитивно понятен, как 2PL (Двухфазная блокировка), его также не так сложно понять. Однако очень важно понять, как это работает, особенно с учетом того, что аномалии данных обрабатываются иначе, чем при использовании блокировки .