Сегменти відкату в СУБД Oracle
Дмитро Безруков,
ЗАТ "ФОРС-ХОЛДИНГ"
Сегменти відкату (rollback segments), звані також undo сегментами (наприклад, USN - Undo Segment Number) призначені для виконання трьох основних завдань:
- Виконання команди rollback.
- Відновлення примірника після збою (instance recovery).
- Забезпечення несуперечності зчитувальних з бази даних (read consistency)
Перший і другий пункти висвітлюються в літературі досить докладно і толково, а ось повного опису функціональних можливостей 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 хвіст сегмента відкату намагається наздогнати голову. Якщо всі транзакції фіксовані і в даному сегменті відкату немає активних транзакцій, то хвіст сегмента збігається з його головою [прім.7] (рис. 3). Екстент може бути використаний для запису інформації відкату від декількох транзакцій, але в кожному блоці екстента зберігається інформація від тільки однієї транзакції.Вище ми розглянули випадок, коли екстенти додаються в сегменти відкату. Тепер розглянемо механізм видалення екстентів (змія стискається) з сегментів відкоту. Всім відомо, що екстенти можна видалити (повернути в список вільних екстентів FET $) двома способами: за допомогою видачі оператора alter rollback segment ... shrink і автоматично за допомогою завдання слова optimal у фразі storage при створенні або модифікації сегмента відкату. Перший спосіб, хоча і має кілька "підводних каменів", зазвичай питань не викликає. А ось другий спосіб ще як викликає. Звичайне питання в таких випадках: "А чому сегмент відкоту збільшився до 20 мегабайт, а стискатися не хоче, хоча optimal задано 5 мегабайт?". Справа в тому, що, для запуску процедури видалення екстентів з сегмента відкату, потрібно виникнення певної події, а саме, перевірка optimal і видалення екстентів проводиться при пересуванні голови сегмента на наступний екстент. Таким чином, сегмент відкоту не стискується відразу ж по завершенні транзакції, що викликала розширення сегмента відкату. Стиснення відбувається, коли інші транзакції, призначені на цей сегмент відкоту, заповнять поточний екстент до кінця. Кількість стиснень з моменту запуску БД протоколюється в стовпці Shrinks уявлення V $ ROLLSTAT.
Тепер розглянемо одну з проблем, проблему блокуючих транзакцій. яка може виникнути у адміністратора багатокористувацької системи. Припустимо, що будь-якої недисциплінований користувач або оператор почав проводити транзакцію (наприклад, вставив рядок в будь-яку таблицю), не завершив її за допомогою оператора commit і пішов надовго у своїх справах. Інформація відкату для цієї транзакції записалася в якийсь сегмент відкоту, тобто голова сегмента просунулася на якусь відстань і транзакція залишилася активною. Хвіст цієї транзакції знаходиться поруч з головою, можливо, в одному і тому ж блоці. Чи не завершив транзакцію користувач пішов, а безліч інших користувачів продовжують модифікацію даних. Інформація відкату від різних транзакцій записується в оперативні сегменти відкату, в тому числі і в той, куди була призначена транзакція пішов користувача. Голова цього сегмента просувається далі за годинниковою стрілкою і підходить до екстенти, в якому знаходиться хвіст активної транзакції пішов користувача. До цього моменту хвіст цієї активної блокує транзакції став хвостом всього сегмента відкату. Так як голова не може переміститися в екстент, в якому знаходиться хвіст, то додається новий екстент, і голова переміщається в нього. Потім ще один і т.д. незважаючи на те, що між головою і хвостом все блоки всіх екстентів неактивні (всі транзакції були зафіксовані операторами commit). Це може привести до переповнення табличного простору сегментів відкоту. Звідси мораль - адміністратору потрібно придумати систему відстрілу недисциплінованих користувачів, наприклад, використовувати idle time в profile. хоча це і не завжди можливо. З'ясувати, які користувачі блокують сегменти відкату можна за допомогою запуску наступного скрипта:
Чим більше кількість екстентів в сегменті, тим менше ймовірність додавання екстента в сегмент відкоту. З цієї точки зору, двадцять маленьких екстентів в сегменті відкату краще, ніж два великих, що займають таке ж місце.
Тепер трохи про механізм, що підтримує несуперечність даних. Після того, як голова сегмента відкату здійснить повний оборот, інформація відкату від неактивних транзакцій починає записуватись нову. Кількість таких перезаписів (повних обертів) з початку запуску СУБД відображено в стовпці Wraps уявлення V $ ROLLSTAT. Таким чином, теоретично можна зробити все сегменти відкату такими великими, що поточні транзакції не будуть змушувати голови всіх сегментів відкоту здійснювати повний оборот за прийнятне для роботи звіту час, скажімо, кілька годин. Але практично зробити це неможливо по ряду очевидних причин, головна з яких - неможливість передбачити заздалегідь обсяг поновлення БД іншими транзакціями. Таким чином, неможливо забезпечити гарантовану несуперечність читань, тобто гарантована відсутність виникнення помилки ORA-1555 при роботі, наприклад, тривалого звіту (read only транзакції), що істотно збіднює функціональні можливості. Але, на щастя, гарантувати несуперечність читань можна. Для цього потрібно лише штучно заблокувати кожен оперативний сегмент відкоту короткими транзакціями до початку виконання звіту і розблокувати, тобто зафіксувати ці транзакції, після виконання. Отже, можна відкрити кілька додаткових сеансів по числу сегментів відкоту, в кожному з них заблокувати короткою транзакцією сегмент відкоту за допомогою оператора set transaction use rollback segment і запустити звіт. При цьому модифікують сеанси можуть отримати від Oracle повідомлення про помилку через неможливість розширення заблокованого сегмента відкату, але виконує звіт сеанс ніколи не отримає ORA-1555.
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 може в цьому розібратися.