Контроль целостности в ADB (изоляция транзакций) ================================================ Контроль целостности позволяет согласовать конкурентные запросы с правильными результатами, обеспечивая целостность базы данных. Традиционные базы данных используют протокол двухфазной блокировки, что предотвращает транзакцию от изменения данных, которые были прочитаны другой параллельной транзакцией, и не позволяет любой параллельной транзакции считывать или записывать данные, обновленные другой транзакцией. Блокировки, необходимые для координации транзакций, добавляют нагрузку на базу данных, уменьшая общую пропускную способность. В **ADB** используется **PostgreSQL Multiversion Concurrency Control** (**MVCC**) для управления параллельными транзакциями heap-таблиц. С **MVCC** каждый запрос работает со снапшотом базы данных, создаваемым в тот момент, когда запрос объявляется. Пока выполняется снапшот, запрос не может видеть сделанные другими параллельными транзакциями изменения. Это гарантирует, что запрос видит последовательное представление базы данных. Запросы, которые читают строки, никогда не блокируют транзакции, пишущие строки. И наоборот, запросы, которые записывают строки, не могут быть заблокированы транзакциями, которые читают строки. Это позволяет значительно увеличить параллелизм в сравнении с традиционными базами данных, в которых используется блокировка для координации доступа между транзакциями, которые считывают и записывают данные. .. important:: Append optimized таблицы управляются с помощью иной модели управления параллелизмом, нежели чем модель MVCC, которая обсуждалась в данном разделе. Они предназначены для приложений *write-once, read-many*, что никогда (или только очень редко) подразумевает обновления на уровне строк Снапшоты -------- Модель **MVCC** основывается на способности системы управлять несколькими версиями строк данных. Запрос работает со снапшотом базы данных. Снапшот -- это набор строк, которые видны в момент транзакции. Снапшот гарантирует, что запрос имеет действительный и корректный снимок базы данных на время выполнения запроса или транзакции. Каждой транзакции присваивается уникальный идентификатор (*XID*), *32-битное* значение. Оператор **SQL**, не являющийся частью транзакции, обрабатывается как одиночная транзакция (с добавлением *BEGIN* и *COMMIT*). Это похоже на *autocommit*, используемый в некоторых базах данных. Когда транзакция вставляет строку, *XID* сохраняется со строкой в столбце *xmin*. Когда транзакция удаляет строку, *XID* сохраняется в столбце системы *xmax*. Обновление строки рассматривается как *delete* и *insert*, поэтому *XID* сохраняется в *xmax* текущей строки и *xmin* только что вставленного ряда. Столбцы *xmin* и *xmax* вместе со статусом завершения транзакции определяют диапазон транзакций, для которых видна текущая версия строки. Транзакция видит результаты работы всех транзакций меньше, чем *xmin*, но не может видеть результаты работы любой транзакции больше или равной *xmax*. Операции с несколькими действиями также должны записывать, какая команда в транзакции вставляла строку (*cmin*) или удаляла строку (*cmax*), чтобы транзакция могла видеть изменения, сделанные предыдущими командами в транзакции. Последовательность команд имеет смысл только во время выполнения транзакции, поэтому она сбрасывается до *0* в самом начале. Каждый сегмент **ADB** имеет свою собственную последовательность *XID*, которая не может сравниваться с *XID* других инcтансов. Мастер координирует распределенные транзакции с сегментами, использующими идентификационный номер сеанса, называемый *gp_session_id*. Сегменты сопоставляют идентификаторы распределенных транзакций с их локальными *XID*. Мастер координирует распределенные транзакции с помощью двухфазового протокола фиксации. Если транзакция не выполняется на одном из сегментов, транзакция прекращается на всех сегментах, и данные возвращаются в первоначальный вид. Столбцы *xmin*, *xmax*, *cmin* и *cmax* для любой строки можно увидеть при помощи оператора *SELECT*: :command:`SELECT xmin, xmax, cmin, cmax, * FROM tablename;` Поскольку команда *SELECT* выполняется на мастере, *XID* являются идентификаторами распределенных транзакций. В случае если команда выполняется в отдельной базе данных сегментов, значения *xmin* и *xmax* соответствуют локальным значениям *XID*. Зацикливание ID Транзакций -------------------------- Модель **MVCC** использует идентификаторы транзакций (*XID*), чтобы определить, какие строки видны в начале запроса или транзакции. *XID* -- это 32-битное значение, поэтому база данных может теоретически выполнить более четырех миллиардов транзакций до того, как значение переполнится и обнулится. Тем не менее, в базе данных **ADB** используется арифметика по модулю *2^32*, что позволяет *XID* зацикливаться. Для любого *XID* может быть около двух миллиардов предыдущих и новых *XID*. Это работает до тех пор, пока текущая версия строки примерно через два миллиарда транзакций неожиданно не станет новой строкой. Чтобы предотвратить это, **ADB** имеет специальный *XID*, называемый *FrozenXID*, который всегда считается старше обычного *XID*, с которым он сравнивается. *Xmin* строки должен быть заменен на *FrozenXID* в течение двух миллиардов транзакций, и это одна из функций, выполняемых командой *VACUUM*. Очистка (vacuuming) как минимум раз в два миллиарда транзакций предотвращает зацикливание *XID*. База данных **ADB** отслеживает *XID* и предупреждает, когда требуется произвести очистку (операция *VACUUM*). Когда значительная часть идентификаторов больше недоступна и до того, как происходит зацикливание *XID*, выдается предупреждение: :command:`WARNING: database "database_name" must be vacuumed within number_of_transactions` :command:`Transactions` Если операция *VACUUM* не выполняется, база данных **ADB** перестает создавать транзакции, чтобы избежать возможной потери данных. При достижении лимита, система сообщает об ошибке: :command:`FATAL: database is not accepting commands to avoid wraparound data loss in database` :command:`"database_name"` Параметры конфигурации сервера *xid_warn_limit* и *xid_stop_limit* управляют отображением предупреждений и ошибок. Параметр *xid_warn_limit* показывает количество идентификаторов транзакций перед значением *xid_stop_limit*. А параметр *xid_stop_limit* показывает количество *XID* перед тем, как происходит зацикливание и выдается ошибка. Режимы изоляции транзакций -------------------------- Стандарт **SQL** описывает три явления, которые могут возникать при запуске транзакций базы данных одновременно: + *Грязное чтение* – явление, которое возникает, когда транзакция считывает незафиксированные данные из другой параллельной транзакции; + *Неповторяющееся чтение* – ситуация, когда при повторном чтении в рамках одной транзакции ранее прочитанные данные оказываются измененными; + *Чтение фантомов* -- ситуация, когда при повторном чтении в рамках одной транзакции одна и та же выборка дает разные множества строк. Стандарт **SQL** определяет четыре режима изоляции транзакций, которые должны поддерживать системы баз данных: .. table:: Четыре режима изоляции транзакций +-----------------+----------------------+------------------------+-------------------+ | Уровень | Грязное чтение | Неповторяющееся чтение | Чтение фантомов | +=================+======================+========================+===================+ | Read Uncommitted| Недоступно в ADB | Возможно | Возможно| +-----------------+----------------------+------------------------+-------------------+ | Read Committed | Невозможно | Возможно | Возможно | +-----------------+----------------------+------------------------+-------------------+ | Repeatable Read | Невозможно | Невозможно | Недоступно в ADB | +-----------------+----------------------+------------------------+-------------------+ | Serializable | Невозможно | Невозможно | Невозможно | +-----------------+----------------------+------------------------+-------------------+ Arenadata DB реализует только два различных уровня изоляции транзакций, при этом можно запросить любой из четырех описанных уровней. Уровень *READ UNCOMMITTED* ведет себя как *READ COMMITTED*, а уровень *SERIALIZABLE* откатывается к *REPEATABLE READ*. Оператор **SQL** *SET TRANSACTION ISOLATION LEVEL* устанавливает режим изоляции для текущей транзакции. Режим должен быть установлен перед любыми операциями *SELECT, INSERT, DELETE, UPDATE* или *COPY*: :: BEGIN; SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; ... COMMIT; Режим изоляции также может быть указан как часть инструкции *BEGIN*: :command:`BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;` Режим изоляции транзакции по умолчанию можно изменить с помощью свойства *default_transaction_isolation*. Удаление неиспользуемых строк из таблиц --------------------------------------- Обновление или удаление строки оставляет предыдущую версию строки в таблице. Если строка с истекшим сроком действия указана в активных транзакциях, ее можно удалить, а занимаемое ею пространство повторно использовать. За это действие отвечает команда *VACUUM*. Когда недействительные строки накапливаются в таблице, дисковые файлы должны быть расширены для размещения новых строк. Увеличенная нагрузка на ввод/вывод дисков, используемых для обработки запросов, негативно влияет на производительность. Эта ситуация называется раздутие (bloat), и её следует контролировать с помощью регулярной чистки. Команда *VACUUM* может работать одновременно с другими запросами. Она удаляет неиспользуемые строки со страниц и переупаковывает оставшиеся строки для консолидации свободного места. Если после очистки осталось много свободного пространства, в таблицу свободного пространства добавляется страница. Позднее, когда базе данных требуется пространство для новых строк, она обращается к карте свободного пространства таблицы. Если ни одна страница не найдена, добавляются новые страницы. *VACUUM* не консолидирует страницы и не уменьшает размер таблицы на диске. Пространство, которое эта команда восстанавливает, доступно только через карту свободного пространства. Чтобы размер файлов на диске не рос, важно хотя бы один раз в день запускать *VACUUM*. Также важно запускать *VACUUM* после выполнения транзакции, которая обновляет или удаляет большое количество строк. Команда *VACUUM FULL* перезаписывает таблицу без неиспользуемых строк, сводя ее к минимальному размеру. Для создания новой таблицы должно быть достаточно места на диске. При этом таблица блокируется до тех пор, пока команда *VACUUM FULL* не завершится. Это очень ресурсоемко по сравнению с обычной командой *VACUUM*, и ее можно избежать или отложить путем регулярной чистки. Лучше всего запускать *VACUUM FULL* в течение периода технического обслуживания. Альтернативой *VACUUM FULL* является воссоздание таблицы с помощью инструкции *CREATE TABLE AS* с последующим удалением старой таблицы. Карта свободного пространства находится в общей памяти и отслеживает свободное пространство для всех таблиц и индексов. Каждая таблица или индекс использует около *60 байт* памяти, и каждая страница со свободным пространством занимает *6 байт*. Два параметра конфигурации системы определяют размер карты свободного пространства *max_fsm_pages* и *max_fsm_relations*. + :command:`max_fsm_pages` Данный параметр устанавливает максимальное количество дисковых страниц, которые могут быть добавлены в общую карту свободного пространства. Для каждого слота страницы потребляется *6 байт* общей памяти. Значение по умолчанию - *200000*. Этот параметр должен быть установлен как минимум в *16* раз больше значения *max_fsm_relations*. + :command:`max_fsm_relations` Параметр устанавливает максимальное количество отношений, которые отслеживаются в карте свободного пространства. Этот параметр должен быть установлен больше, чем общее количество *таблиц + индексов + системных таблиц*. Значение по умолчанию - *1000*. Для каждого отношения к каждому сегменту потребляется около *60 байт* памяти. Рекомендуется устанавливать параметр на более высокое значение. Если карта свободного пространства слишком маленькая, дисковые страницы с доступным пространством не добавляются на карту, и это пространство не может быть повторно использовано до тех пор, пока не будет запущена, по меньшей мере, следующая команда *VACUUM*. Это приводит к росту файлов. Можно запустить *VACUUM VERBOSE tablename*, чтобы получить отчет по сегменту о количестве удаленных строк, количестве затронутых страниц и количестве страниц с полезным свободным пространством. Чтобы узнать, сколько страниц таблица использует во всех сегментах, необходимо запросить системную таблицу *pg_class*. Чтобы получить точные данные обязательно следует сначала выполнить *ANALYZE* для таблицы. :command:`SELECT relname, relpages, reltuples FROM pg_class WHERE relname = 'tablename';` Другим полезным инструментом является *gp_bloat_diag* в схеме *gp_toolkit*, который идентифицирует раздутие (bloat) в таблицах путем сравнения фактического количества используемых таблицей страниц с ожидаемым числом.