Жизненный цикл транзакций

Understanding Transaction Lifetimes
Copyright © Craig Stuntz, 22.06.2004.

Оригинал статьи: http://blogs.teamb.com/craigstuntz/2004/06/22/587/
Перевод: Кузьменко Дмитрий, www.ibase.ru

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

Что такое длинная транзакция?

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

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

С другой стороны, действительно, может потребоваться длительно активная транзакция. Например, InterBase Performance Monitor перечитывает информацию из временных системных таблиц InterBase 7.x каждые 10 секунд. Если бы он использовал короткую транзакцию на каждое перечитывание, то за день он бы стартовал десятки тысяч транзакций. В большинстве случаев это не проблема, но в идеальном случае нужно чтобы была возможность оставлять Performance Monitor работающим 24 часа в сутки 7 дней в неделю. Номера транзакций это знаковые 32-разрядные числа, и необходимо делать backup/restore, как минимум когда номер Next transaction приблизится к 2-м миллиардам. Поэтому, InterBase Performance Monitor использует одну, длительно работающую транзакцию, и использует возможности InterBase 7 чтобы такая транзакция не влияла на производительность сервера.
 

Параметры транзакций

InterBase позволяет указать ряд параметров транзакций, но наиболее важными являются два параметра при определении длительности транзакции: уровень изолированности и read-only. Наиболее часто используемые уровни изолированности транзакций – read committed, который позволяет видеть транзакции чужие commited-изменения, и snapshot, который гарантирует, что транзакция видит только то состояние базы данных, которое было на момент старта этой транзакции (плюс изменения, произведенные самой транзакцией). Транзакция read-only не может модифицировать данные в базе данных, в то время как транзакция read-write – может.
 
Примечание KDV. Если вы пользуетесь IBX (компоненты InterBase eXpress, в поставке Delphi или C++Builder, возьмите компонент IBTransaction и сделайте на нем двойной клик. Там есть диалог выбора параметров транзакций. Еще можете почитать статью о транзакциях.
Итак, не забираясь в дебри, вот правила для долгоживущих транзакций в InterBase 7.1, Service Pack 1, в зависимости от их параметров:

Параметры транзакций Эффект от долгой работы такой транзакции
Read committed, read only Такая транзакция может работать вечно без отрицательного воздействия на производительность.
Read committed, read-write Эта транзакция может работать вечно без отрицательных последствий для производительности, если вы регулярно делаете commit retaining.
 
Примечание KDV. Для Firebird и предыдущих версий InterBase это не так. CommitRetaining для сервера будет все равно выглядеть старт и длительное существование транзакции, до тех пор пока не будет выполнен Commit. То есть, избежать накопления мусора путем использования CommitRetaining не удастся.

В серверах до момента выпуска IB 7.1 транзакции read committed при совместной работе с snapshot могут вызывать удержание Oldest snapshot, как будто это не read committed, а snapshot-транзакции.
Snapshot, read only, или snapshot, read-write Транзакции snapshot должны завершаться commit-ом чтобы не замедлять производительность сервера. Commit retaining не поможет.
Все это означает, что долгая жизнь транзакций не является проблемой InterBase 7.1 как таковой, пока вам не потребуется выполнить или отменить большое количество растянутых по времени изменений в одной транзакции, или запускать транзакцию snapshot на длительное время.

Это простое объяснение. Если вы хотите подробностей, то читайте дальше....
 

Подробнее о транзакциях

Транзакции при старте нумеруются последовательно. "Старейшая" (oldest) транзакция это та транзакция, которая стартовала раньше всех, и у нее самый меньший номер.

Перед определением некоторых терминов необходимо объяснить, каким образом InterBase реализует rollback. По причинам, которые будут понятны дальше, InterBase работает лучше если делать commit, нежели чем делать rollback транзакции. Поэтому InterBase пытается конвертировать rollback в commit всегда, когда это возможно. Это делается отменой всех сделанных в транзакции изменений и выполнением commit. InterBase может делать это (KDV: а не оставлять по rollback изменения для их последующей уборки сборщиком мусора) в версии 7.1 SP1, до тех пор пока транзакция модифицирует менее 100000 записей. Если пользователь в одной транзакции модифицировал более 100000 записей, то InterBase выполняет "настоящий" rollback. InterBase также делает настоящий rollback, если сервер упал, когда транзакция была активной – транзакция будет переведена в состояние rollback при рестарте сервера. 

Oldest Active Transaction
В соответствии с названием, это старейшая транзакция, которую пользователь стартовал, и до сих пор не завершил по committed или rollback.
Oldest Interesting Transaction 
Первая транзакция с состоянием, отличным от commit. На практике это (1) то же самое что и oldest active transaction или (2) номер самой старой транзакции, завершенной "настоящим" rollback (см. выше) или (3) транзакция в состоянииin-limbo (полузавершенная) двухфазного commit (two-phase commit). Выполнение большого rollback приведет к застреванию OIT, но OIT будет "подвинут" при успешной отработке sweep. OIT также застревает в случае долгоживущей транзакции snapshot, на время жизни такой транзакции. И наконец, длительная транзакция read committed, read-write будет удерживать OIT до тех пор, пока для нее не будет выполнен commit или commit retaining.
Oldest Snapshot Transaction
Это звучит как будто номер самой старой транзакции snapshot, но на самом деле не так. У каждой транзакций есть свой собственный "номер snapshot". Oldest Snapshot Transaction это старейший номер "snapshot number" для всех активных транзакций (обратите внимание, что в InterBase 7.x это число вы можете видеть в tmp$transactions). Транзакция read-only, read committed не имеет номера snapshot. Транзакция read-write, read committed имеет номер snapshot равный своему собственному номеру. Поскольку вызов commit retaining на самом деле означает старт новой транзакции в том же контексте, выполнение commit retaining для транзакций read-committed, read-write будет двигать вперед ее номер snapshot (примечание KDV: на самом деле – не двигает. Можете убедиться на собственном опыте). Для транзакции snapshot, "номер snapshot" это номер Oldest Active Transaction на момент ее старта. Номер Oldest Snapshot Transaction обновляется когда стартует новая транзакция (или выполняется commit retaining), или когда стартует sweep.
Next Transaction
Это просто номер, который будет присвоен следующей транзакции при ее старте.
 

Примеры

Если в базе данных не было больших rollback, и не стартовали транзакции snapshot, то эти четыре числа будут выстроены в следующем порядке, от меньшего к большему:
OIT = OST = OAT -> NT

Если в базе данных был выполнен большой rollback, но транзакций snapshot не было, то порядок будет такой:
OIT -> OST = OAT -> NT

Если в базе данных не было большого rollback, но есть активная транзакция snapshot, то порядок будет:
OST -> OIT = OAT -> NT
 
Примечание KDV. То же самое может быть в предыдущих версиях Interbase при конкурентном взаимодействии транзакций read committed.


Что означают эти числа?

В базе данных InterBase есть специальные страницы, называемые Transaction Inventory Page (TIP). Когда транзакция стартует она получает копию TIP от Oldest Interesting Transaction (OIT). Однако, транзакции read committed используют одну и ту же копию TIP. Транзакции snapshot работают каждая со своей копией. Поэтому, когда застревает OIT, старт транзакций snapshot копирует все больше памяти, чем если бы OIT не застревал. И соответственно, растет копия TIP, используемая транзакциями read committed.
 
Примечание KDV. Все транзакции ниже OIT считаются committed (и являются таковыми на самом деле). Копия TIP берется для определения состояния конкурирующих транзакций, т.е. можно ли конкретной транзакции видеть конкретную версию записи, или нет. Подробнее см. статью о многоверсионности.
Транзакция Oldest Snapshot Transaction контролирует, какие версии записей могут быть собраны как мусор. Перефразируя можно сказать, что это индикатор версий, которые видимы активным транзакциям. До тех пор, пока транзакция snapshot активна, каждая версия записи, которая видима этой транзакции, должна оставаться в базе данных. Поэтому, оставляя активной транзакцию snapshot, или транзакцию read committed read-write transaction, вы способствуете накоплению версий в базе данных.

Процесс сборки мусора (sweep) контролируется разницей между Oldest Interesting Transaction и Oldest Active Transaction (примечание KDV: в версиях InterBase ранее 7.1 данная разница считается между Oldest Interesting Transaction и Oldest Snapshot Transaction). Когда расстояние между транзакциями Oldest Interesting Transaction и Oldest Active Transaction становится больше числа, указанного в заголовке базы данных как sweep interval, стартует sweep (сборщик мусора). (Вы можете установить этот параметр при помощи gfix -housekeeping или IBConsole) Sweep собирает мусорные выерсии записей во всей базе данных, и разблокирует OIT, если ее номер застрял в результате rollback большого количества изменений.

Oldest Active Transaction станет транзакцией "oldest snapshot" при старте очередной транзакции snapshot.

Next Transaction полезна когда идет сравнение с остальными номерами. Пока у вас нет причин стартовать долгоживущие транзакции snapshot, номера next transaction и oldest active transaction должны быть близки друг к другу, с разницей не более нескольких тысяч, даже при сильно нагруженной базе данных. 

В большинстве случаев, номер Oldest Snapshot Transaction должен быть близок к номеру Oldest Active Transaction. Исключением является случай, когда Oldest Active Transaction это транзакция snapshot, и сервер сильно занят (много активных транзакций) в тот момент, когда стартовала Oldest Active Transaction. Транзакция Oldest Interesting Transaction также должна быть близкой к Oldest Active Transaction за исключением случая, когда ранее был сделан большой rollback или застряла транзакция in-limbo transaction (незавершенный two-phase commit).
 

Влияние активных транзакций на производительность

Есть две причины по которой транзакции могут ухудшать производительность сервера:
  • Использование памяти. Когда по той или иной причине застревает Oldest Interesting Transaction, сервер будет потреблять дополнительную память, в особенности для транзакций snapshot. Это замедляет работу сервера и оставляет меньше памяти для других задач, выполняемых сервером. В худшем случае операционная система может начать выгрузку памяти в виртуальную память, что еще сильнее ухудшит производительность.
  • Обратные версии записей. Когда застревает Oldest Snapshot Transaction, сборщик мусора не может убирать версии записей как мусор. Даже если в InterBase 7.1 существенно сократилось число просмотра старых версий записей, производительность все равно определяется количеством обратных версий записей. 
  • Garbage Collection Yield. Есть проблема, происходящая когда идут массовые INSERT или UPDATE и версии накапливаются быстрее, чем сборщик мусора успевает их обрабатывать. Решением является установка параметра SWEEP_YIELD_TIME в 0 в ibconfig. Это устанавливает приоритет треда сборщика мусора таким же как и пользовательский тред, и помогает сборщику мусора успевать за появлением устаревающих версий записей. Потенциально это означает, что основной пользовательский тред, выполняющий запрос, будет работать медленнее, однако ухудшение производительности в этом случае намного ниже, чем влияние накопления большого количества мусорных версий записей в базе данных.
 

Благодарности

Спасибо Charlie Caro, Sriram Balasubramanian и Shaunak Mistry из InterBase R&D за ответы на мои повторяющиеся вопросы по этой теме. Спасибо Bill Todd за дополнительные исследования в этой области.

Copyright © Craig Stuntz
 
Примечание по поводу CommitRetaining: KDV, 20.04.2005
Перевод iBase.ru, 2004, с разрешения автора. Копирование запрещено.

Подпишитесь на новости Firebird в России

Подписаться