Инспектор транзакций: управление конфликтами в СУБД

В этой статье представлена авторская концепция «Инспектор транзакций», направленная на оптимизацию работы с транзакциями в системах управления базами данных (СУБД). Мы предлагаем использовать инвертированный индекс для выявления конфликтующих транзакций. Перед выполнением новой транзакции инспектор проверяет, пересекается ли ее множество задействованных строк с множеством задействованных строк уже работающих транзакций, сопоставляя инвертированный индекс новой транзакции с общим инвертированным индексом активных транзакций. Если конфликтов нет, транзакция выполняется в режиме READ UNCOMMITTED, при этом общий инвертированный индекс обновляется как при старте транзакции, так и после её завершения. Также рассматриваются вопросы обработки конфликтов, если пересечение есть. Данный подход позволяет заранее точно определить, с какими транзакциями и по каким записям может возникнуть конфликт, что облегчает обработку этого конфликта. Мы надеемся, что предложенная концепция может способствовать улучшению работы СУБД.

Введение

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

Эти проблемы можно проиллюстрировать на примере поведения главного героя фильма «Теория заговора», Джерри Флетчера. Его параноидальное отношение к окружающему миру приводит к тому, что он запирает все в своем доме, включая холодильник и каждый контейнер с едой. Это символизирует его страх перед внешними угрозами и желание контролировать каждую деталь своей жизни.

Аналогично, транзакции в режиме SERIALIZABLE действуют с максимальной осторожностью, блокируя доступ к ресурсам для других транзакций до завершения текущей. Хотя это гарантирует защиту от конфликтов и аномалий, такое поведение также ведет к задержкам и снижению производительности системы. Транзакции «изолируют» себя от других, что может привести к блокировкам и ожиданиям, аналогично тому, как Джерри Флетчер изолирует себя от мира.

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

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

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

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

Концепция «Инспектор транзакций»

В нашей системе предполагается использование инвертированного индекса, аналогичный тому, который применяется в полнотекстовом поиске, но адаптированный для работы с транзакциями в системах управления базами данных (СУБД). Вместо слов, как в традиционном инвертированном индексе, мы имеем записи в базе данных, а вместо документов — транзакции.

Инвертированный индекс активных транзакций состоит из следующих компонентов:

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

2.    Списки идентификаторов транзакций: Для каждой записи из словаря создается список идентификаторов активных транзакций, которые взаимодействуют с данной записью. Этот список содержит информацию о том, какие транзакции уже используют или планируют использовать конкретные записи.

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

  Транзакция T1: обновляет запись R1

Транзакция T2: читает запись R1 и обновляет запись R2

  Транзакция T3: читает запись R2

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

  R1: {(T1, update), (T2, select)}

  R2: {(T2, update), (T3, select)}

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

При выполнении новой транзакции «Инспектор» использует инвертированный индекс для проверки пересечения множеств задействованных записей. Перед началом выполнения новой транзакции система анализирует, какие записи она будет использовать, и сопоставляет их с общим инвертированным индексом активных транзакций. Если пересечений не обнаружено, новая транзакция может быть выполнена в режиме READ UNCOMMITTED.

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

Пример работы «Инспектора транзакций» без конфликтов

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

  Транзакция T1: обновляет запись R1.

  Транзакция T2: читает запись R2.

  Транзакция T3: читает запись R3.

Шаг 1: Структура инвертированного индекса

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

  R1: {(T1, update)}

  R2: {(T2, select)}

  R3: {(T3, select)}

Шаг 2: Выполнение новой транзакции

Теперь предположим, что мы хотим выполнить новую транзакцию T4, которая также читает запись R2. Перед выполнением этой транзакции «Инспектор» проверяет инвертированный индекс для выявления возможных конфликтов.

Шаг 3: Проверка пересечений

«Инспектор» анализирует записи, которые будут задействованы в транзакции T4. В данном случае это запись R2. Он сопоставляет её с инвертированным индексом:

  Для R2 в индексе указано, что с ней взаимодействует транзакция T2 (чтение).

Шаг 4: Определение наличия конфликта

Поскольку обе транзакции (T2 и T4) только читают запись R2, «Инспектор» определяет, что пересечение есть, но его можно проигнорировать, т.к. оно не вызовет конфликт.

Шаг 5: Выполнение транзакции

Так как конфликтов не обнаружено, новая транзакция T4 может быть выполнена в режиме READ UNCOMMITTED. Это позволяет ей получить доступ к данным без блокировок. Перед стартом и после завершения транзакции, инвертированный индекс обновляется для отражения нового состояния.

Обработка конфликтов

1. Потерянное обновление

Проблема: Потерянное обновление возникает, когда две транзакции одновременно обновляют одну и ту же запись, и одно из обновлений теряется. Например, если транзакция T1 обновляет запись R1, а затем транзакция T2 также обновляет R1, то изменения, внесенные T1, могут быть потеряны.

Решение в системе «Инспектор транзакций»:

Система использует инвертированный индекс для предварительной проверки пересечений. Перед выполнением новой транзакции «Инспектор» анализирует, какие записи будут задействованы. Если новая транзакция пытается обновить запись, которая уже используется другой активной транзакцией для обновления, «Инспектор» инициирует процесс обработки конфликта. Мы предлагаем следующий вариант разрешения конфликта: приостановка T2 на шаге до обновления R1 до того момента, как T1 обновит R1; в случае отката T1, также откатывается и T2. В случае если T2 завершается раньше (и не произошло отката), перед коммитом происходит вторая приостановка в ожидании коммита/отката T1.

bccaf2ab97bae15e7b9ca57ac862b20f.jpg

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

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

Решение в системе «Инспектор транзакций»:

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

3. Неповторяемое чтение

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

Пример:

  Транзакция T1 читает значение записи R1.

  Транзакция T2 обновляет запись R1.

  Транзакция T1 снова читает запись R1 и получает новое значение, отличное от первого.

Решение в системе «Инспектор транзакций»:

Если T2 начала свое выполнение до того, как T1 прочитала R1, произойдет приостановка T1 на шаге до чтения R1, пока T2 не изменит R1. Логика такая же, как на схеме в потерянном обновлении.

Если T2 начала свое выполнение после того, как T1 уже прочитала R1, то системой выбирается более оптимальный вариант из двух следующих:
  перезапуск T1, если в инвертированном индексе указано, что T1 будет обращаться к записи R1 еще раз;
либо по достижении T2 записи R1 приостановка T2 до повторного чтения T1 записи R1.

4. Фантомное чтение

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

Решение в системе «Инспектор транзакций»:

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

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

Дополнительные компоненты системы

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

1. Инвертированный индекс транзакции

Описание: Каждая транзакция имеет свой личный инвертированный индекс, который позволяет отслеживать, с какими полями транзакция уже взаимодействовала и какие поля она планирует использовать в дальнейшем. Этот индекс создается на этапе анализа транзакции и обновляется по мере её выполнения.

Функциональность:

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

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

2. Контроллер общего инвертированного индекса

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

Функциональность:

  В общем инвертированном индексе фиксируется режим каждой транзакции (свободный или режим обработки конфликтов).

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

© Habrahabr.ru