Продолжается подписка на наши издания! Вы не забыли подписаться?

Сегменты отката в СУБД Oracle

Дмитрий Безруков, ФОРС

Сегменты отката (rollback segments), называемые также undo-сегментами (например, USN – Undo Segment Number) предназначены для выполнения трех основных задач:

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

Также в литературе я не встречал внятного описания механизма функционирования сегментов отката. Самое хорошее описание было, как это ни странно, в старой документации по Oracle версий 3, 4 и 5. Правда, в то время не было еще сегментов отката, а вместо них был before image file, но концепции остались прежними. Тамошняя концепция "голов и хвостов", на мой взгляд, лучше помогает понять схему работы сегментов отката.

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

Непротиворечивость считываемых данных

Проблема непротиворечивости чтений (read consistency) связана с "замораживанием" данных в СУБД на какой-либо момент времени и получения пользователем отчета по этим данным. Во время получения отчета какими-либо пользователями другим пользователям должна быть предоставлена возможность одновременной модификации тех же самых данных (в противном случае резко снижается конкурентоспособность данных), поэтому содержимое БД непрерывно изменяется, а "замороженные" данные представляют собой снимок (snapshot) данных базы на какой-то момент времени. Реализован механизм снимков в СУБД Oracle через сегменты отката (rollback segments или undo segments), в которых хранятся старые версии данных. В случае попытки чтения запросом данных, модифицированных другими пользователями за время его работы, старые версии данных на момент начала работы запроса считываются из сегментов отката. Oracle не обеспечивает гарантированного чтения предыдущих версий данных из-за возможного их затирания в сегментах отката другими пользователями (см. ниже). В случае затирания в сегментах отката старых версий данных пользователю, выполняющему запрос, выдается ошибка "ORA-1555 Snapshot too old, rollback segment too small". Чем дольше выполняется запрос, тем больше вероятность затирания старых версий данных. Разработанная в ФОРСе система GRC предназначена для гарантированного хранения старых версий данных на время работы отчета (см. описание системы на сайте www.forc.ru).

Oracle обеспечивает непротиворечивость на уровне оператора и на уровне транзакции.

Непротиворечивость на уровне оператора

Оператор выборки select языка SQL всегда считывает непротиворечивые данные. Это означает, что если в какой-то момент оператор select считывает какую-либо строку таблицы и через некоторое время считывает эту же строку повторно, то Oracle выдает либо ту же самую информацию либо ошибку ORA-1555.

Непротиворечивость на уровне транзакции

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

Механизм работы сегментов отката

При создании сегмента отката выделяется minextents пока еще пустых экстентов, причем первый блок первого экстента зарезервирован под таблицу транзакций или заголовок сегмента отката. Мы будем говорить, что голова и хвост сегмента отката находятся в начале второго блока первого экстента сразу после заголовка (см. рис. 1). При назначении транзакции на этот сегмент отката, информация отката записывается, начиная со второго блока, причем у каждой активной транзакции существует также голова и хвост в сегменте отката. При модификации транзакцией данных и заполнении сегмента отката голова транзакции продвигается, заполняя блоки первого и, возможно, следующих экстентов. При этом хвост транзакции совпадает с хвостом сегмента отката (см. рис. 2.). Посмотреть, где находится голова сегментов отката с точностью до экстента и блока можно в таблице V$ROLLSTAT в столбцах Curext и Curblk соответственно (первый экстент имеет номер 0).

Рисунок 1

Рисунок 2

После того, как транзакция будет зафиксирована оператором commit, хвост сегмента отката переместится к голове (см. рис. 3). Здесь голова и хвост будут указывать на первый байт следующего за хвостом транзакции блока. Запись информации отката от следующей назначенной на данный сегмент транзакции будет помещаться в блоки экстентов, передвигая голову сегмента и транзакций далее от хвоста по часовой стрелке. После фиксации транзакции информация отката, записанная в экстенты 1 и 2, становится "неактивной" и может быть использована для получения отчетом старых версий данных, то есть для обеспечения непротиворечивости (см. выше.)

Длинная транзакция во время записи информации передвигает голову сегмента отката. При заполнении экстента голова перемещается в следующий экстент, потом в следующий за этим, и так далее по кругу. Говорят, что сегменты отката имеют кольцевую структуру. Это означает, что после заполнения четвертого экстента, запись продолжается в первый или, что то же самое, голова перемещается в первый экстент. При этом соблюдается важное правило: хвост сегмента отката никогда не может переместиться в тот экстент, где находится голова. Таким образом, если голова сегмента отката находится в экстенте 4, последний блок которого заполнен (хвост переместился в конец экстента), и для продолжения транзакции требуется дальнейшее перемещение хвоста по часовой стрелке, то в сегмент отката добавляется пятый экстент (змея растягивается) и голова перемещается в его первый блок (рис. 4). Количество растяжений с момента запуска (startup) базы данных протоколируется в столбце Extends представления V$ROLLSTAT.

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

Рисунок 3

Рисунок 4

Выше мы рассмотрели случай, когда экстенты добавляются в сегменты отката. Теперь рассмотрим механизм удаления экстентов (змея сжимается) из сегментов отката. Всем известно, что экстенты можно удалить (возвратить в список свободных экстентов FET$) двумя способами: при помощи оператора alter rollback segment … shrink и автоматически, при помощи задания слова optimal во фразе storage при создании или модификации сегмента отката. Первый способ, хотя и имеет несколько "подводных камней", обычно вопросов не вызывает. А вот второй способ еще как вызывает. Обычный вопрос в таких случаях: "А почему сегмент отката увеличился до 20 мегабайт, а сжиматься не хочет, хотя optimal задано 5 мегабайт?". Дело в том, что для запуска процедуры удаления экстентов из сегмента отката требуется возникновение определенного события, а именно, проверка optimal и удаление экстентов производится при передвижении головы сегмента на следующий экстент. Таким образом, сегмент отката не сжимается сразу же по завершении транзакции, вызвавшей расширение сегмента отката. Сжатие происходит, когда другие транзакции, назначенные на этот сегмент отката, заполнят текущий экстент до конца. Количество сжатий с момента запуска (startup) БД протоколируется в столбце Shrinks представления V$ROLLSTAT.

Теперь рассмотрим одну из проблем, проблему блокирующих транзакций, которая может возникнуть у администратора многопользовательской системы. Предположим, что какой либо недисциплинированный пользователь или оператор начал проводить транзакцию (например, вставил строчку в какую-либо таблицу), не завершил ее при помощи оператора commit и ушел надолго по своим делам. Информация отката для этой транзакции записалась в какой-то сегмент отката, то есть голова сегмента продвинулась на какое-то расстояние и транзакция осталась активной. Хвост этой транзакции находится рядом с головой, возможно, в одном и том же блоке. Не завершивший транзакцию пользователь ушел, а множество других пользователей продолжают модификацию данных. Информация отката от различных транзакций записывается в оперативные сегменты отката, в том числе и в тот, куда была назначена транзакция ушедшего пользователя. Голова этого сегмента продвигается далее по часовой стрелке и подходит к экстенту, в котором находится хвост активной транзакции ушедшего пользователя. К этому моменту хвост этой активной блокирующей транзакции стал хвостом всего сегмента отката. Так как голова не может переместиться в экстент, в котором находится хвост, то добавляется новый экстент и голова перемещается в него, затем еще один и т.д., несмотря на то, что между головой и хвостом все блоки всех экстентов неактивные (все транзакции были зафиксированы операторами commit). Это может привести к переполнению табличного пространства сегментов отката. Отсюда мораль – администратору нужно придумать систему отстрела недисциплинированных пользователей, например, использовать idle time в profile, хотя это и не всегда возможно. Выяснить, какие пользователи блокируют сегменты отката, можно при помощи запуска скрипта, представленного ниже. Чем больше количество экстентов в сегменте, тем меньше вероятность добавления экстента в сегмент отката. С этой точки зрения двадцать маленьких экстентов в сегменте отката лучше, чем два занимающих такое же место больших.

select S.Username, S.Sid, S.Serial#, T.Start_time, T.Xidusn
  from V$Session S, V$TRANSACTION T, V$ROLLSTAT R
  where S.Saddr = T.Ses_addr
    and T.Xidusn = R.Usn
    and (
      (R.Curext = T.Stat_uext-1)
      or ((R.Curext = R.Extents-1) and (T.Start_ext = 0))
      );

Теперь немного о механизме, поддерживающем непротиворечивость данных. После того, как голова сегмента отката совершит полный оборот, информация отката от неактивных транзакций начинает перезаписываться. Количество таких перезаписей (полных оборотов) с начала запуска СУБД отражено в столбце Wraps представления V$ROLLSTAT. Таким образом, теоретически можно сделать все сегменты отката такими большими, что текущие транзакции не будут заставлять головы всех сегментов отката совершать полный оборот за приемлемое для работы отчета время, скажем, несколько часов. Но практически сделать это невозможно по ряду очевидных причин, главная из которых – невозможность предсказать заранее объем обновления БД другими транзакциями. Таким образом невозможно обеспечить гарантированную непротиворечивость чтений, то есть гарантированное отсутствие возникновение ошибки ORA-1555 при работе, например, длительного отчета (read only транзакции), что существенно обедняет функциональные возможности. Но к счастью, гарантировать непротиворечивость чтений можно. Для этого нужно лишь искусственно заблокировать каждый оперативный сегмент отката короткими транзакциями до начала выполнения отчета и разблокировать, то есть зафиксировать эти транзакции, после выполнения. Следовательно, можно открыть несколько дополнительных сеансов по числу сегментов отката, в каждом из них заблокировать короткой транзакцией сегмент отката с помощью оператора set transaction use rollback segment и запустить отчет. При этом модифицирующие сеансы могут получить от Oracle сообщение об ошибке из-за невозможности расширения заблокированного сегмента отката, но выполняющий отчет сеанс никогда не получит ORA-1555.

Процедура искусственного блокирования сегментов отката должна была бы быть реализована в рамках программного обеспечения Oracle, но к сожалению этого пока нет, по крайней мере в 8.1.6. Чтобы заполнить этот пробел, в ФОРСе была реализована система GRC (Guaranteed Read Consistency), позволяющая блокировать сегменты отката различными способами с целью гарантирования непротиворечивости чтений.

статья была опубликована на сайте www.fors.ru

Copyright © 1994-2016 ООО "К-Пресс"