div.main {margin-left: 20pt; margin-right: 20pt}
Сегменты отката в СУБД Oracle
Дмитрий Безруков (ЗАО "ФОРС-ХОЛДИНГ")
Эта статья впервые была размещена на сайте http://www.oramag.ru/
Сегменты отката (rollback segments), называемые также undo сегментами
(например, USN – Undo Segment Number) предназначены для выполнения трех основных
задач:
Выполнение команды rollback.
Восстановление экземпляра после сбоя (instance recovery).
Первый и второй пункты освещаются в литературе достаточно подробно и
толково, а вот полного описания функциональных возможностей Oracle по пункту 3 я
не встречал.
Также в литературе я не встречал внятного описания механизма функционирования
сегментов отката. Самое хорошее описание было, как это ни странно, в старой
документации по Oracle версий 3, 4 и 5. Правда, в то время не было еще сегментов
отката, а вместо них был файл before image, но концепции остались прежними.
Тамошняя концепция “голов и хвостов“, на мой взгляд, лучше помогает
понять схему работы сегментов отката.
Давайте сначала поговорим о непротиворечивости, а затем - про устройство
сегментов отката. Конечно, эти темы не охватывают всех аспектов
администрирования сегментов отката, но очень полезны для понимания их структуры
и функционирования.
Непротиворечивость считываемых данных
Проблема непротиворечивости чтений (read consistency) связана с
“замораживанием” данных в СУБД на какой-либо момент времени и получения
пользователем отчета по этим данным. Во время получения отчета какими-либо
пользователями другим пользователям должна быть предоставлена возможность
одновременной модификации тех же самых данных (в противном случае резко
снижается конкурентоспособность данных), поэтому содержимое БД непрерывно
изменяется, а “замороженные” данные представляют собой снимок (snapshot) данных
базы на какой-то момент времени. Реализован механизм таких снимков в СУБД Oracle
через сегменты отката (rollback segments или undo segments), в которых хранятся
старые версии данных. В случае, когда отчетет пытается прочитать данные,
модифицированные другими пользователями за время его работы, старые версии
данных на момент начала отчета считываются из сегментов отката. Oracle не
обеспечивает гарантированного чтения предыдущих версий данных из-за
возможного их затирания в сегментах отката другими пользователями. В случае
затирания в сегментах отката старых версий данных, пользователю, выполняющему
отчет, выдается ошибка “ORA-1555 Snapshot too old, rollback segment too small”.
Чем дольше выполняется отчет, тем больше вероятность затирания старых версий
данных. Разработанная в ФОРСе система GRC предназначена для
гарантированного хранения старых версий данных на время работы отчета.
Oracle обеспечивает непротиворечивость на уровне оператора и на уровне
транзакции.
Непротиворечивость на уровне оператора.
Оператор выборки select языка SQL всегда считывает непротиворечивые
данные. Это означает, что если в какой-то момент оператор select
считывает какую-либо строку таблицы и через некоторое время считывает эту же
строку повторно ,
то Oracle выдает, либо ту же самую информацию, либо ошибку ORA-1555.
Непротиворечивость на уровне транзакции.
Oracle поддерживает следующие типы транзакций:
Транзакция, читающая зафиксированные другими транзакциями данные во время
ее работы. Пользователь может явно начать такую транзакцию при помощи
оператора set transaction read write. Транзакция по умолчанию
(от начала сеанса до первой команды commit/rollback, от
commit/rollback до следующей commit/rollback или до конца
сеанса) является транзакцией этого типа. Перед началом любой транзакции по
умолчанию неявно выполняется оператор set transaction read write. Если
два оператора select в разных частях транзакции этого типа обращаются к одним
и тем же строкам в таблице БД, то возможно получение различных
(противоречивых) результатов. Противоречие возникнет в том случае, если
какой-либо пользователь модифицировал и зафиксировал изменения этих строк на
интервале времени между двумя вышеупомянутыми операторами select. То есть в
рамках одной транзакции возможно чтение данных, модифицированных и
зафиксированных (committed) другими пользователями . Следует отметить,
что появление ошибки ORA-1555 возможно в случае, если Oracle не может
обеспечит непротиворечивость на уровне оператора select, входящего в
данную транзакцию.
Транзакция “только для чтения”. Пользователь может начать такую транзакцию
при помощи оператора set transaction read only и закончить
оператором commit/rollback. Внутри такой транзакции можно
употреблять только операторы select, операторы DML недопустимы.
При обращении операторов select из различных частей транзакции к одним и тем
же строкам данных Oracle выдает либо одинаковые непротиворечивые данные,
находившиеся в БД на момент начала транзакции (set transaction read
only), либо ошибку ORA-1555.
Сериализуемая транзакция. Такая транзакция начинается оператором set
transaction isolation level serializable. Такие транзакции
предназначены для сериализации обновлений при конкурентной работе нескольких
пользователей с одними и теми же данными. При помощи этих транзакций,
одновременно выдаваемых разными пользователями, результат модификации данных в
базе таков, как если бы соблюдалась очередность, то есть сначала один
пользователь начал и закончил транзакцию, после него второй провел свою
транзакцию, потом третий и т.д. Вообще говоря, процесс сериализации транзакций
очень сложен, и не всегда возможно сериализовать одновременно проводимые
несколькими пользователями транзакции. Отметим, что если запись в какую-либо
таблицу или таблицы ведется только одним пользователем, то необходимость в
сериализации отпадает. Сериализуемые транзакции всегда непротиворечивы, то
есть все выборки получают данные из “снимка” базы, сделанного на момент выдачи
оператора set transaction isolation level serializable. При
условии, что запись осуществляется только в рабочие таблицы данного
пользователя, можно трактовать сериализуемые транзакции: как транзакции
только для чтения (read only) с возможностями выдачи операторов DML (insert,
delete и update). Это удобно, когда программа получения сложного отчета
сохраняет результаты в промежуточных рабочих таблицах, из которых затем
отчет выводится на печать. Механизм работы сегментов отката
При создании сегмента отката выделяется minextents пока еще пустых экстентов,
причем первый оракловский блок первого экстента зарезервирован под таблицу
транзакций, или заголовок сегмента отката. Мы будем говорить, что голова
и хвост сегмента отката
находятся в начале второго блока первого экстента сразу после заголовка (см.
рис. 1). При назначении транзакции на этот сегмент отката, информация отката
записывается, начиная со второго блока, причем у каждой активной транзакции
существует также голова и хвост в сегменте отката. При модификации транзакцией
данных и заполнении сегмента отката голова транзакции продвигается, заполняя
блоки первого и возможно следующих экстентов. При этом хвост транзакции
совпадает с хвостом сегмента отката (см. рис 2.). Посмотреть, где находится
голова сегментов отката с точностью до экстента и блока можно в таблице
V$ROLLSTAT в столбцах Curext и Curblk соответственно (первый экстент имеет номер
0).
После того, как транзакция будет зафиксирована оператором commit,
хвост сегмента отката переместится к голове (см. рис. 3). Здесь голова и
хвост будут указывать на первый байт следующего за хвостом транзакции блока.
Запись информации отката от следующей назначенной на данный сегмент транзакции
будет помещаться в блоки экстентов, передвигая голову сегмента и транзакций
далее от хвоста по часовой стрелке. После фиксации транзакции информация отката,
записанная в экстенты 1 и 2, становится “неактивной” и может быть использована
для получения отчетом старых версий данных, то есть для обеспечения
непротиворечивости.
Длинная транзакция во время записи информации передвигает голову сегмента
отката. По заполнении экстента, голова перемещается в следующий экстент, потом в
следующий и так далее - по кругу. Говорят, что сегменты отката имеют кольцевую
структуру (змея). Это означает, что после заполнения четвертого экстента, запись
продолжается в первый или , что то же самое, голова перемещается в первый
экстент . При этом
соблюдается важное правило: хвост сегмента отката никогда не может
переместиться в тот экстент, где находится голова. Таким образом, если
голова сегмента отката находится в экстенте 4, последний блок которого заполнен
(хвост переместился в конец экстента), и для продолжения транзакции требуется
дальнейшее перемещение хвоста по часовой стрелке, то в сегмент отката
добавляется пятый экстент (змея растягивается), и голова перемещается в его
первый блок (рис. 4). Количество растяжений с момента запуска базы данных
протоколируется в столбце Extends представления V$ROLLSTAT.
Если несколько транзакций записывают в сегмент информацию отката
одновременно, то хвост сегмента совпадает с хвостом самой первой
назначенной на сегмент отката активной транзакции, а голова сегмента – с
головой последней записавшей информацию отката активной транзакции. По мере
фиксации транзакций оператором commit хвост сегмента отката пытается
догнать голову. Если все транзакции фиксированы и в данном сегменте отката нет
активных транзакций, то хвост сегмента совпадает с его головой (рис. 3). Экстент
может быть использован для записи информации отката от нескольких
транзакций, но в каждом блоке экстента хранится информация от
только одной транзакции.
Выше мы рассмотрели случай, когда экстенты добавляются в сегменты отката.
Теперь рассмотрим механизм удаления экстентов (змея сжимается) из
сегментов отката. Всем известно, что экстенты можно удалить (возвратить в список
свободных экстентов FET$) двумя способами: при помощи выдачи оператора alter
rollback segment … shrink и автоматически при помощи задания слова
optimal во фразе storage при создании или модификации сегмента
отката. Первый способ, хотя и имеет несколько “подводных камней”, обычно
вопросов не вызывает. А вот второй способ еще как вызывает. Обычный вопрос в
таких случаях: “А почему сегмент отката увеличился до 20 мегабайт, а сжиматься
не хочет, хотя optimal задано 5 мегабайт?”. Дело в том, что, для запуска
процедуры удаления экстентов из сегмента отката, требуется возникновение
определенного события, а именно, проверка optimal и удаление экстентов
производится при передвижении головы сегмента на следующий экстент. Таким
образом, сегмент отката не сжимается сразу же по завершении транзакции,
вызвавшей расширение сегмента отката. Сжатие происходит, когда другие
транзакции, назначенные на этот сегмент отката, заполнят текущий экстент до
конца. Количество сжатий с момента запуска БД протоколируется в столбце
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.Start_uext-1)
or
((R.Curext = R.Extents - 1) and (T.Start_uext = 0))
);Чем больше количество экстентов в сегменте, тем
меньше вероятность добавления экстента в сегмент отката. С этой точки зрения,
двадцать маленьких экстентов в сегменте отката лучше, чем два больших,
занимающих такое же место.
Теперь немного о механизме, поддерживающем непротиворечивость данных.
После того, как голова сегмента отката совершит полный оборот, информация отката
от неактивных транзакций начинает перезаписываться на новую. Количество таких
перезаписей (полных оборотов) с начала запуска СУБД отражено в столбце
Wraps представления V$ROLLSTAT. Таким образом, теоретически можно
сделать все сегменты отката такими большими, что текущие транзакции не будут
заставлять головы всех сегментов отката совершать полный оборот за приемлемое
для работы отчета время, скажем, несколько часов. Но практически сделать это
невозможно по ряду очевидных причин, главная из которых – невозможность
предсказать заранее объем обновления БД другими транзакциями. Таким образом,
невозможно обеспечить гарантированную непротиворечивость чтений, то есть
гарантированное отсутствие возникновение ошибки ORA-1555 при работе, например,
длительного отчета (read only транзакции), что существенно обедняет
функциональные возможности. Но, к счастью, гарантировать непротиворечивость
чтений можно. Для этого нужно лишь искусственно заблокировать каждый оперативный
сегмент отката короткими транзакциями до начала выполнения отчета и разблокировать, то
есть зафиксировать эти транзакции, после выполнения. Следовательно, можно
открыть несколько дополнительных сеансов по числу сегментов отката, в каждом из
них заблокировать короткой транзакцией сегмент отката с помощью оператора set
transaction use rollback segment и запустить отчет. При этом модифицирующие
сеансы могут получить от Oracle сообщение об ошибке из-за невозможности
расширения заблокированного сегмента отката, но выполняющий отчет сеанс никогда
не получит ORA-1555.
Процедура искусственного блокирования сегментов отката должна была бы быть
реализована в рамках программного обеспечения Oracle, но, к сожалению, этого
пока нет, по крайней мере в 8.1.6. Чтобы заполнить этот пробел, в ЗАО
“ФОРС-ХОЛДИНГ” была реализована система GRC (Guaranteed Read
Consistency), позволяющая блокировать сегменты отката различными способами с
целью гарантирования непротиворечивости чтений. В частности, система программной
блокировки сегментов отката предлагается к реализации в настоящее время.
Стоимость системы можно узнать из рекламного листка, представленного на
Web-сайте ЗАО “ФОРС-ХОЛДИНГ” - http://www.fors.ru/.
Примечания
1.Иногда в русскоязычной литературе по базам данных термин consistency
переводят как “целостность”, что, на мой взгляд, неправильно, так как русское
слово “целостность” зарезервировано для перевода английского слова
“integrity”.
2. Предполагается, что на интервале времени между чтениями данная
строка может быть модифицирована другими пользователями с фиксацией изменений
(commit) произвольное количество раз.
3. В стандарте языка SQL транзакции этого типа так и называются: read
committed. Начиная с версии Oracle 8.1.5, для оператора set transaction read
write предусмотрен синоним – оператор set transaction isolation level
read committed.
4. Кстати, начиная с версии сервера Oracle 8.1.5, реализованы
специальные рабочие таблицы, действующие в рамках сеанса или транзакции
пользователя, с которыми операции DML проводятся гораздо быстрее, чем с обычными
таблицами из-за того, что запись обновлений в журналы (redo logs) блокируется.
За счет этого также достигается экономия дисковой памяти в redo logs.
5. Животное, которое следует представлять себе при употреблении
терминов “голова” и “хвост” - змея. Змея стремится свернуться в кольцо и укусить
себя за хвост. Змея резиновая, растягивается и сжимается.
6. Нумерация экстентов особого смысла не имеет и приводится здесь
только для наглядности. Среди всех экстентов сегмента отката особый статус имеет
лишь тот экстент, в первом блоке которого располагается таблица транзакций.
Номер этого экстента в представлении DBA_EXTENTS – 0, и он не может быть удален
из сегмента отката
7. Змея укусила себя за хвост и успокоилась.
8. На самом деле действительность сложнее приведенного мной описания,
так как в информация о зафиксированных транзакциях может быть затерта новой
транзакцией в таблице транзакций, но, к счастью, Oracle может в этом
разобраться.
|