Рубрики
Без рубрики

Руководство для начинающих по аномалии перекоса записи и ее отличиям между 2PL и MVCC

Узнайте, что такое явление перекоса записи и как с ним можно справиться с помощью 2PL (Двухфазная блокировка) или MVCC (Управление параллелизмом нескольких версий).

Автор оригинала: Vlad Mihalcea.

В отличие от SQL Server, который по умолчанию использует 2PL (Двухфазную блокировку) для реализации стандартных уровней изоляции SQL, Oracle, PostgreSQL и MySQL InnoDB engine используют MVCC (Управление параллелизмом нескольких версий), поэтому обработка аномалии Перекос записи может отличаться от одной базы данных к другой.

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

В следующих примерах мы будем использовать следующие объекты базы данных:

Бюджет ИТ-отдела нашей компании составляет 100 000 долларов в месяц. Это должно учитывать все зарплаты и бонусы, чтобы мы никогда не превышали бюджет. Элис, генеральный директор компании, решает дать премию всем сотрудникам ИТ-отдела за то, что они очень хорошо поработали с последним выпуском продукта. Однако премия не должна превышать бюджет. Тем временем Боб, отдел кадров компании, наконец-то смог нанять Кэрол, которая является отличным разработчиком, поэтому мы можем позволить себе платить ей столько, сколько позволяет наш бюджет.

Чтобы проиллюстрировать, как аномалия перекоса записи может нарушить сериализуемость, рассмотрим следующие шаги:

  1. Алиса читает сумму всех зарплат в ИТ-отделе, которая составляет 90 000
  2. Боб также читает сумму всех зарплат в ИТ-отделе и решает, что будет выплачивать Кэрол зарплату в размере 9 000 в месяц, поскольку бюджет теперь составит 99 000.
  3. Алиса решает дать 10% бонус всем сотрудникам ИТ-отдела, так как бюджет должен составлять 99 000, верно?

Очевидно, что одна из этих двух транзакций должна не сохранить расписание сериализуемых транзакций. Эта аномалия называется Перекос записи, и мы собираемся посмотреть, как это явление обрабатывается различными СУБД.

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

Все предстоящие тесты доступны на GitHub , поэтому вы можете легко запустить их в своей любимой СУБД и проверить, допускает ли определенный уровень изоляции явление, которое обычно следует предотвращать.

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

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

Далее мы рассмотрим, как различные базы данных, использующие механизм двухфазной блокировки, могут предотвратить нашу проблему с бюджетом Алисы и Боба.

MySQL

MySQL имеет несколько механизмов хранения, но нас интересует только транзакционный механизм InnoDB, который также является механизмом хранения по умолчанию с версии 5.5. Даже если InnoDB использует MVCC в своей основе, для уровня сериализуемой изоляции MySQL получает общую физическую блокировку для каждой строки или диапазона строк, выбранных данным SQL-запросом . Поскольку каждая таблица является кластеризованным индексом в MySQL, InnoDB использует базовую структуру индекса для предоставления записей, пробелов или даже блокировок следующего ключа .

При повторном запуске нашего варианта использования на уровне сериализуемой изоляции MySQL регистрируется следующий результат:

Как указывалось ранее, в 2PL используется механизм предотвращения конфликтов, поэтому оператор ВСТАВКИ Боба блокируется, поскольку Алиса удерживает блокировку общего предиката, которая распространяется на всех сотрудников, содержащихся в ИТ-отделе. Транзакция Боба ожидает определенного периода времени, и, поскольку транзакция Алисы все еще удерживает блокировку, оператор Боба завершается ошибкой с исключением времени ожидания.

SQL Server по умолчанию использует 2PL, поэтому, если вы хотите, чтобы получение блокировки завершилось быстро, вы можете использовать директиву NOWAIT .

Блокировки приводят к конфликту, а конфликт влияет на масштабируемость. Соотношение между конкуренцией и масштабируемостью определяется Универсальным законом масштабируемости Нила Гюнтера (USL) . По этой причине исследователи изучили дополнительный механизм управления параллелизмом, чтобы обеспечить более высокую производительность и пропускную способность при одновременном предотвращении проблем с целостностью данных.

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

Даже если MVCC использует меньше блокировок, чем 2PL, эксклюзивные блокировки все равно получаются каждый раз, когда мы изменяем запись, так как в противном случае может произойти грязная запись, и атомарность будет нарушена.

Как указывалось ранее, SQL Server предлагает два уровня изоляции на основе MVCC: Изоляция моментальных снимков с фиксацией чтения и Изоляция моментальных снимков. Разница между этими двумя уровнями изоляции заключается в моменте времени, используемом для создания стабильного снимка данных. Для уровня изоляции, зафиксированного для чтения, моментальный снимок относится к началу текущего выполняемого запроса, в то время как для изоляции моментального снимка моментальный снимок относится к началу текущей транзакции.

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

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

Oracle предлагает два уровня изоляции на основе MVCC: Фиксированный для чтения и сериализуемый, поэтому управление параллелизмом на основе 2PL отсутствует. Хотя Oracle называет его Сериализуемым , самый высокий уровень изоляции на самом деле является вариантом изоляции моментальных снимков, который подвержен аномалии Искажения записи .

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

По этой причине стоит проверять каждый вариант использования, потому что могут быть крайние случаи, когда алгоритм MVCC не может обнаружить перекос записи, который был бы предотвращен в 2PL.

PostgreSQL

В отличие от других механизмов баз данных, использующих MVC, PostgreSQL делает еще один шаг вперед и реализует уровень сериализуемой изоляции моментальных снимков (SSI), который представляет собой очень сложный механизм управления параллелизмом , который может обнаруживать перекос записи.

В нашем примере PostgreSQL 9.5 способен обнаруживать явление перекоса записи при использовании агрегатной функции для всех записей сотрудников в ИТ-отделе:

PostgreSQL SSI удается обнаружить перекос записи, поскольку транзакция Алисы откатывается из-за сбоя сериализации.

Возврат результирующего набора вместо агрегированного значения результата

Давайте посмотрим, что произойдет, если мы выберем зарплаты в качестве результирующего набора вместо агрегированного значения:

PostgreSQL SSI удается обнаружить перекос записи, и транзакция Алисы откатывается.

[Alice]: PSQLException: ERROR: could not serialize access due to read/write dependencies among transactions
         Detail: Reason code: Canceled on identification as a pivot, during write.
         Hint: The transaction might succeed if retried.

Возврат результирующего набора только в транзакции Алисы

Однако, если только Алиса читает записи сотрудников в ИТ-отделе, в то время как Боб просто выдает инструкцию insert, не читая текущих сотрудников, PostgreSQL больше не предотвращает вторую запись.

Теперь вы можете подумать, что это проблема с реализацией сериализуемости в PostgreSQL, но на самом деле это не так. Сериализуемость означает, что две транзакции могут быть переупорядочены таким образом, чтобы они были эквивалентны одному последовательному выполнению. В этом примере, если бы две транзакции были выполнены одна за другой, что означает, что сначала выполняется Алиса, а затем транзакция Боба, результат был бы точно таким же, как на предыдущей диаграмме. Более того, сериализуемость не подразумевает какого-либо физического упорядочения по времени. Это относится только к Линеаризуемости , что означает, что это относится к строгой сериализуемости.

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

Все эти случаи использования должным образом предотвращаются MySQL, поскольку блокировка общего предиката не позволяет Бобу получить эксклюзивную блокировку, чтобы вставить новую строку в тот же диапазон записей, который уже выбран Алисой. Но из-за блокировки MySQL предлагает Строгую сериализуемость (Сериализуемость + Линеаризуемость), поэтому наша проблема предотвращена.

MVCC-отличный механизм управления параллелизмом, но, поскольку он не использует пессимистические блокировки предикатов или диапазонов, он должен обнаруживать аномалии, проверяя текущее расписание транзакций. Это очень сложная задача, и могут быть крайние случаи, когда компонент database engine может не обнаружить какую-либо аномалию, которая в противном случае была бы предотвращена механизмом управления параллелизмом на основе 2PL.

По сравнению с 2PL, который существует с конца 1970-х годов, алгоритм изоляции сериализуемых моментальных снимков является довольно новым, был опубликован в 2008 году и впервые введен в Postgres 9.1 (2011). В области баз данных и распределенных систем проводится много исследований, и в будущем мы могли бы извлечь выгоду из еще более надежных оптимистичных механизмов управления параллелизмом. Между тем, лучше понять компромиссы и ограничения текущих реализаций, чтобы гарантировать, что целостность данных не будет нарушена.

Моя следующая статья продемонстрирует, как вы можете преодолеть аномалии перекоса записи, когда базовый уровень изоляции Сериализуемый на основе MVCC не может предотвратить это должным образом.