Запись данных в базу Oracle: давайте разберемся...

Стас Белков

Стас Белков

Автор статьи. Известный специалист в мире IT. Консультант по продуктам и решениям Oracle. Практикующий программист и администратор баз данных. Подробнее.

Механизм записи в базу данных OracleНадежность транзакций в Oracle обеспечивается с помощью журнала повторений и процесса lgwr. Благодаря этому отпадает необходимость немедленно копировать буферы с данными на диск сразу после их изменения. Тем не менее, все же желательно (а в большинстве случаев и необходимо), чтобы измененные буферы были сохранены на диске в какой-то момент времени. Как ни крути, но чувство уверенности намного выше, когда фактические данные хранятся на диске. К тому же, многие системы имеют ограниченный объем памяти, поэтому если кэш базы данных имеет объем N блоков, копирование на диск необходимо будет начать при попытке изменить N+1 блок.

Теперь, когда мы знаем, что блоки данных должны копироваться на диск, начинают возникать интересные вопросы: «Когда это должно делаться?» и «Как определить, какие блоки должны копироваться?». Эти вопросы возвращают нас обратно, к процессу lgwr.

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

Примечание. Мало кому известно, что когда сеанс обнаруживает в кэше поврежденный блок данных, он автоматически выполняет только что описанную процедуру – ограничиваясь оперативными журналами – чтобы получить исправленную его копию. С этой целью сеанс закрепляет буфер в монопольном режиме, но в остальном процедура выполняется совершенно прозрачно. Функция восстановления блоков в утилите Recovery Manager (RMAN) предлагает дополнительные возможности и позволяет администратору задействовать архивные файлы журналов. В версии 11.2 появилась возможность автоматического восстановления носителя на уровне блоков (Automatic Block Media Recovery, см. скрытые параметры, такие как _auto_bmr%), позволяющая автоматически использовать архивные файлы журналов (впервые я познакомился с этой функцией, когда она стала причиной серьезных проблем производительности из-за того, что три сеанса одновременно пытались восстановить один и тот же корневой индексный блок, сканируя все архивные файлы журналов за несколько последних дней). Если физически доступна резервная база данных, данная функция обратится к ней, чтобы увидеть – нет ли там последней копии блока, которую можно было бы использовать в качестве отправной точки для восстановления.

На практике, однако, нежелательно, чтобы подобное восстановление выполнялось постоянно. Если необходимость восстановить блок все же возникает, нежелательно, чтобы в этом процессе участвовал слишком большой объем журнала. К тому же ни у кого из нас нет дисков неограниченной емкости, поэтому мы не можем хранить файлы оперативного журнала неограниченного объема. Следовательно, главной целью стратегии процесса записи данных (dbwr) является минимизация работы, которую потребуется выполнить для восстановления блоков данных. Основу этой стратегии составляет учет давности изменений в буферах и копирование «в первую очередь самых старых».

 

Заголовки буферов

Мы снова возвращаемся к связанным спискам и рабочим наборам данных. Давайте для начала рассмотрим содержимое заголовка измененного («грязного») буфера:

BH (11BFA4A4) file#: 3 rdba: 0x00c067cc (3/26572) class: 1 ba: 11B74000
set: 3 blksize: 8192 bsi: 0 set-flg: 0 pwbcnt: 0
dbwrid: 0 obj: 8966 objn: 8966 tsn: 2 afn: 3
hash: [217b55d0,10bf2a00] lru: [15be9774,11bf6748]
lru-flags: hot_buffer
obj-flags: object_ckpt_list
ckptq: [147eaec8,11beab1c] fileq: [147eaed0,11beab24] objq:
[1e5ae154,147ed7b0]
st: XCURRENT md: NULL tch: 9
flags: buffer_dirty gotten_in_current_mode block_written_once
redo_since_read
LRBA: [0x294.539f.0] HSCN: [0x0.4864bf4] HSUB: [57] 

В этой статье мы будем рассматривать очередь контрольных точек (checkpoint queue (ckptq:), файлов (file queue, fileq:) и объектов (object queue, objq:), из которых наиболее важной является очередь контрольных точек, поэтому я начну с нее.

В данном случае признак flags: говорит о том, что буфер изменен, находится в очереди контрольных точек и (обязательно) в очереди файлов. Интересно отметить, что признак obj-flags: имеет значение object_ckpt_list, которое может быть простым описанием факта присутствия в двух очередях – объектов (object) и контрольных точек (ckpt), но (по причинам, о которых я расскажу потом) может также подсказывать, что существует еще одно множество скрытых очередей, каждая из которых объединяет все измененные (или «грязные») блоки, имеющие отношение к данному объекту.

 

Очереди контрольных точек

Давайте сначала посмотрим, как выглядит очередь контрольных точек. Каждый рабочий набор данных имеет две очереди контрольных точек, о существовании которых несложно догадаться взглянув на имена защелок в структуре x$kcbwds

SQL> desc x$kcbwds
Name                          Null?    Type
----------------------------- -------- --------
...
CKPT_LATCH                              AW(4)
CKPT_LATCH1                             RAW(4)
SET_LATCH                               RAW(4)
NXT_REPL                                RAW(4)
PRV_REPL                                RAW(4)
...

Хотя сами защелки обнаруживаются достаточно просто – они присутствуют в представлении v$latch с именем checkpoint queue latch и соответствуют столбцам addr в v$latch_children – обнаружить конечные точки двух этих очередей в структуре невозможно, потому что в ней вы не увидите столбцов nxt_ckpt и prv_ckpt, аналогичных столбцам nxt_repl и prv_repl. Когда я попробовал изменить несколько блоков и вывести дамп всех заголовков буферов, оказалось совсем несложно с помощью некоторого кода PL/SQL прочитать трассировочный файл и восстановить связанные списки: я нашел два списка с примерно одинаковым числом буферов и адресами концов, очень близкими к некоторым указателям в x$kcbwds. (Подобное исследование указателей fileq: показало, что на каждый файл с данными приходится только одна очередь файлов, с адресами концов, также близкими к некоторым указателям в x$kcbwds.)

Когда сеанс впервые изменяет буфер, он приобретает одну из защелок checkpoint queue latch (раздражающее слово «latch» (защелка) действительно присутствует в имени защелки) для доступа к рабочему набору данных, которому принадлежит буфер, и присоединяет буфер к «свежему» концу очереди, и одновременно устанавливает LRBA: (адрес первого изменения в блоке (Low Redo Block Address) – адрес соответствующей записи повторения в форме log file seq# . log file block#). Это означает, что фактически буферы упорядочены в порядке следования адресов блоков повторения (Redo Block Address, RBA) и не могут попасть в очередь контрольных точек, пока не будут вытолкнуты в буфер журнала. Защелка обычно приобретается в непосредственном режиме (нет смысла использовать режим готовности к ожиданию, потому что высока вероятность, что одна из двух будет свободна) и буфер включается в очередь под защитой защелки. Как мне кажется, для выбора защелки, которая будет опробована первой, сеанс использует механизм рандомизации – возможно, опираясь на адрес блока – и только если не удалось немедленно получить ни одну из защелок, он использует режим готовности к ожиданию при попытке получить вторую защелку.

Запись в базу данных Oracle: буферы и журналы, контрольная точка

Примечание. В адресе блока повторений ссылка на номер файла – это постоянно увеличивающееся значение seq# из v$log_history, а не group# из v$log, которое является всего лишь числом активных файлов журнала в экземпляре. Это означает, что RBA постоянно увеличивается и является разумным выбором на роль механизма упорядочения.

Данная процедура связывания выполняется только при первом изменении буфера – создание копии блока рассматривается как особый случай. Когда таблица обновляется в ходе ее сканирования (и, возможно, в некоторых других случаях), сеанс копирует текущий блок, чтобы создать новую текущую версию (статистика switch current to new buffer). Это означает, что буфер, включенный в очередь контрольных точек, перестает быть последней версией блока, поэтому его следует исключить из очереди и включить в нее новый буфер. Разумеется, новый буфер добавляется не в конец очереди, а на место предыдущей копии, чтобы не нарушался порядок следования буферов. Сделать это совсем несложно, потому что заголовок только что скопированного буфера указывает на два соседних буфера, с которыми требуется связать новую копию (см. рис. 1). В этом более сложном случае сеанс приобретает защелку в режиме готовности к ожиданию.

Примечание. Значение LRBA: в заголовке не изменяется, когда новая копия вставляется в очередь контрольных точек, но значения HSCN: (высший номер SCN) и HSUB: (высший подномер SCN, настраиваемое значение, введенное в 9i для улучшения параллелизма буфера журнала) будут установлены в соответствии с текущим значением SCN. По причинам, обсуждаемым ниже, очень важно, чтобы dbwr мог понять, когда были сделаны самые последние изменения в буфере. (Это значение используется в Oracle также в ходе восстановления блока в памяти, так как определяет точку в журнале, где следует остановиться.)

Весь механизм добавления буферов в очередь и удаления их из нее должен быть защищен защелками, потому что есть вероятность, что несколько сеансов сразу могут попытаться одновременно выполнить данные операции. Кроме того, есть еще один особый процесс, добавляющий буферы в очереди контрольных точек одновременно с пользовательскими сеансами, – это dbwr. Каждые 3 секунды процесс dbwr «просыпается», выполняет какую-то свою работу и прежде чем приостановиться на следующие 3 секунды, взводит для себя таймер.

Подстановка новой копии буфера (заголовка) в очередь контрольных точек

Рис. 1. Подстановка новой копии буфера (заголовка) в очередь контрольных точек

 

Инкрементальные контрольные точки

Когда dbwr «просыпается», он приобретает каждую защелку checkpoint queue latch по очереди (в режиме готовности к ожиданию), чтобы проверить – имеются ли какие-нибудь буферы в очередях, и затем выполняет обход очереди LRBA:, копируя буферы на диск и удаляя их из очереди (отмечает буферы как «чистые», одновременно сбрасывая LRBA:), пока не достигнет буфера, более свежего, чем целевой. Иногда бывает так, что dbwr «просыпается», проверяет «старый» конец каждой очереди, обнаруживает, что в них нет буферов, которые должны быть скопированы, и тут же приостанавливается до следующего пробуждения.

Понятие «более свежий» является весьма непостоянным – существует пять разных параметров, о которых следует помнить, имея дело с разными версиями Oracle, и некоторые из них меняли свой смысл от версии к версии: fast_start_mttr_target, fast_start_io_target, log_checkpoint_timeout, log_checkpoint_interval и _target_rba_max_lag_percentage. На основе этих параметров действует самонастраивающийся механизм, появившийся в 10g. Однако все эти параметры служат одной и той же цели: каждый раз, когда процесс dbwr «просыпается», он обрабатывает целевой адрес блока повторения и копирует все буферы со значением LRBA: ниже целевого. Самым простым для понимания является, пожалуй, параметр log_checkpoint_interval. Он задает число блоков файле журнала и dbwr просто вычитает это число из адреса блока повторения, записанного процессом lgwr последним, и затем копирует буферы со значениями LRBA: более низкими, чем полученный результат. Остальные параметры просто используют другие алгоритмы для определения цели.

Примечание. Понимая, как действует dbwr, вы легко поймете, почему каждый рабочий набор данных имеет две очереди контрольных точек. Пока dbwr выполняет обход одной очереди, все сеансы, желающие добавить свои буферы, будут использовать другую очередь, благодаря стратегии приобретения защелок в непосредственном режиме, в отличие от dbwr, приобретающего защелки в режиме готовности к ожиданию. (Зная, что существует защелка с именем active checkpoint queue latch, можем ли мы предположить, что dbwr обновляет общедоступную переменную, чтобы показать, какую очередь он обрабатывает, и сеансы не предпринимали напрасных попыток добавить буферы в эту очередь?) Иногда, когда dbwr выполняет обход какой-то определенной очереди, может получиться так, что некоторому сеансу потребуется заменить буфер именно в этой очереди, поэтому dbwr должен удерживать защелку очереди контрольных точек все время, пока выполняется обход этой очереди.

Безусловно, это было далеко не полное описание процедуры копирования буферов. Механизм, описывавшийся до сих пор, известен как инкрементальные контрольные точки и является лишь одним из нескольких механизмов, имеющих слова «контрольные точки» в названиях. Однако данный механизм является, пожалуй, наиболее важным, потому что гарантирует, что файлы данных будут хранить достаточно свежий моментальный снимок базы данных. Кроме того, он минимизирует объем работ, которые потребуется выполнить в случае аварии экземпляра для анализа информации в журнале и приведения базы данных в состояние, предшествовавшее аварии.

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

Если процессу lgwr удастся слишком быстро заполнить весь имеющийся объем оперативного журнала, он не сможет затереть первые записи в нем, а это означает, что он не сможет записывать на диск блоки повторений и данные в базе нельзя будет изменить, что в свою очередь означает остановку базы данных. Такая остановка, как правило, является признаком, что файлы журнала слишком маленькие или их недостаточно, чтобы обеспечить успешное преодоление пиковых нагрузок. Иногда это может также означать наличие логических ошибок в программах, из-за которых генерируются гигантские объемы записей повторения, а иногда – необходимость использовать более быстрые устройства для хранения журнала.

Что еще может доставлять нам беспокойство? Во-первых, как я уже отмечал, процесс lgwr всегда сохраняет информацию об изменениях в блоках данных до того, как dbwr запишет сами блоки данных. Во-вторых, иногда возникают ситуации, когда dbwr вынужден сохранить блоки данных до того, как они достигнут «старого» конца очереди контрольных точек, и одна из таких ситуаций имеет решающее значение для производительности (и часто недопонимается или недооценивается).

Вас заинтересует / Intresting for you:

Oracle и непроцедурный доступ ...
Oracle и непроцедурный доступ ... 7404 просмотров Antoni Tue, 21 Nov 2017, 13:32:50
Как устроен поиск блоков данны...
Как устроен поиск блоков данны... 3213 просмотров Дэн Wed, 03 Jan 2018, 17:39:13
Видеокурс по администрированию...
Видеокурс по администрированию... 10563 просмотров Илья Дергунов Mon, 14 May 2018, 05:08:47
СУБД Oracle: обзор характерист...
СУБД Oracle: обзор характерист... 8022 просмотров Antoni Fri, 24 Nov 2017, 07:35:05
Войдите чтобы комментировать

apv аватар
apv ответил в теме #8885 25 янв 2018 11:09
Объяснение более чем..... должен знать заурядный админ баз данных Oracle. Но если хотите стать лучшим спецом, то, конечно, этот материал бесценен!