Процесс записи в базе данных Oracle в деталях

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

 

Взаимодействие dbwr и lgwr

Процесс lgwr всегда сохраняет изменения раньше, чем dbwr сохранит блоки данных. Это означает, что после аварии блок данных, находящийся на диске, всегда можно переместить «в будущее», применив записи из журнала, и никогда – «в прошлое». В результате получается очень надежный механизм восстановления. Однако, dbwr обычно сохраняет блоки после проверки значения LRBA: в них – самого раннего момента, когда они изменились – поэтому возникает вопрос: что произойдет, если подошло время сохранить буфер и он оказался изменен за несколько микросекунд до того, как dbwr добрался до него? Вполне возможно, что процесс dbwr будет иногда находить блоки, изменившиеся настолько недавно, что соответствующие записи повторения еще не были записаны в буфер журнала.

В ходе выполнения инкрементальных контрольных точек dbwr будет просто посылать процессу lgwr сообщение с требованием выполнить запись (как обычно, предварительно приобретя защелку redo writing, чтобы проверить, не выполняет ли он очередной цикл записи) перед сохранением проблемного блока данных. Так как похоже, что dbwr не ждет, пока lgwr завершит сохранение (я никогда не видел, чтобы dbwr приостанавливался в ожидании сообщения log file sync), я склонен предположить, что он должен пропустить проблемный блок, двинуться дальше по очереди контрольных точек и по достижении ее конца вернуться обратно, чтобы проверить еще раз – не были ли сохранены соответствующие записи из буфера журнала. Кроме подробной трассировки работы процесса dbwr (что не вызывает у меня энтузиазма), я не вижу другого способа точно определить по имеющейся информации в виде доступных статистик, событий и манипуляций с защелками, что именно происходит в этот момент, но такой заключительный шаг вполне может предприниматься процессом dbwr, чтобы обойти проблемы, связанные с очень низкой скоростью записи буфера журнала – возможно он просто игнорирует блок, позволяя проблеме разрешиться самой 3 секундами позже.

В данный момент я хочу сказать, что посылка сообщения процессу lgwr – не единственный пример взаимодействия dbwr и lgwr. Следует также отметить, что когда dbwr записывает блоки с данными на диск, он сам создает записи повторения, которые называют списками записанных блоков (block written record), для каждого пакета записанных блоков, добавляет их в буфер журнала (используя, как обычно, защелки redo copy и redo allocation) и посылает процессу lgwr сообщение, требуя выполнить запись на диск. И снова он делает это, не останавливаясь в ожидании ответного сообщения log file sync, но предварительно приобретая защелку redo writing, как обычно. Следующий листинг демонстрирует содержимое такого списка записанных блоков: 

REDO RECORD - Thread:1 RBA: 0x0002a7.00000003.013c LEN: 0x0074 VLD: 0x02
SCN: 0x0000.049e100b SUBSCN: 1 08/08/2011 21:15:04
CHANGE #1 MEDIA RECOVERY MARKER SCN:0x0000.00000000 SEQ: 0 OP:23.1
Block Written - afn: 5 rdba: 0x01805b0d BFT:(1024,25189133) non-BFT:(6,23309)
                    scn: 0x0000.049e0fa9 seq: 0x02 flg:0x06
Block Written - afn: 5 rdba: 0x01805b13 BFT:(1024,25189139) non-BFT:(6,23315)
                    scn: 0x0000.049e0fd3 seq: 0x02 flg:0x06
Block Written - afn: 5 rdba: 0x01805b18 BFT:(1024,25189144) non-BFT:(6,23320)
                    scn: 0x0000.049e0fd1 seq: 0x02 flg:0x06
Block Written - afn: 5 rdba: 0x01805b19 BFT:(1024,25189145) non-BFT:(6,23321)
                    scn: 0x0000.049e0fb3 seq: 0x02 flg:0x06

Как видите, список записанных блоков – это всего лишь список адресов блоков вместе с их последними номерами SCN. Я расскажу, для чего создаются такие списки, когда мы перейдем к обсуждению вопросов восстановления, далее в этой статье. Обратите внимание, что список в этом примере описывает четыре блока в файле с абсолютным номером (afn:) 5 и относительным номером 6. Код, создавший символический дамп, не знает, был ли получен блок из большого файла с табличными пространствами (bigfile tablespace) или нет, именно поэтому он предлагает две интерпретации rdba: – одну как BFT: и вторую, как non-BFT:.

Примечание. В Oracle имеется множество функций, доступных пользователю, которые принимают в качестве аргумента номер файла, но иногда невозможно понять, какой это номер – абсолютный или относительный – и проверить на практике это тоже сложно, потому что по умолчанию Oracle стремится обеспечить совпадение относительных и абсолютных номеров, пока число файлов в базе данных не достигнет 1024. Для проведения экспериментов можно установить событие 10 120 перед добавлением файла в базу данных – это заставит Oracle создать файл с разными абсолютным и относительными номерами.

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

 

Процесс записи данных и списки LRU

Как я уже говорил, иногда, процессу dbwr требуется скопировать буфер на диск до того, как этот буфер достигнет «старого» конца очереди контрольных точек. Чтобы понять самую важную (и, пожалуй, самую сложную) причину этого, нам нужно вернуться обратно к спискам LRU (или REPL). На рис. 1 изображен один небольшой рабочий набор данных, показывающий чуть более точно, как действует алгоритм LRU/TCH.

Как вы можете прочесть в этой статье, Oracle стремится по умолчанию хранить примерно 25% буферов рабочего набора данных в списке REPL_AUX, исходя из предположения, что буферы в этом списке вероятнее всего содержат блоки, которые можно использовать повторно для чтения новых блоков в память, и затем переместить их в список REPL_MAIN, в позицию, на которую указывает cold_hd. Список REPL_AUX обычно содержит копии, полученные в результате согласованного чтения или блоки, доступ к которым осуществлялся только один раз (то есть блоки, которые не должны оказаться в «горячей» части списка REPL_MAIN), а также блоки, которые не требуется копировать на диск. Но в жизни не всегда все складывается идеально. Иногда возникают аномалии. Кроме того, когда система находится под высокой нагрузкой, список REPL_AUX может опустеть и почти наверняка вызвать аномалии.

REPL_MAIN и REPL_AUX в БД Oracle

Рис. 1. REPL_MAIN и REPL_AUX

Рассмотрим далее самый тяжелый случай, когда выполняется интенсивное чтение произвольных блоков. После чтения большого числа блоков список REPL_AUX опустеет, буферы из него будут перенесены в список REPL_MAIN и постепенно начнут смещаться в конец цепочки. Теперь вспомните, как выполняется поиск буфера для повторного использования: сеанс сканирует список REPL_AUX в поисках подходящего кандидата, то есть, буфера, который не закреплен, не требует записи на диск и имеет значение счетчика обращений меньше 2 (потому что буферы с высокими значениями счетчика перемещаются сеансом в «горячий» конец списка REPL_MAIN). Если подходящего кандидата не нашлось, сеанс начинает сканировать список REPL_MAIN. Сканирование продолжается, пока не будет достигнут предел (определяется параметром _db_block_max_scan_pct и составляет 40% от размера рабочего набора данных), после чего сеанс пошлет процессу dbwr сообщение (чтобы тот записал несколько блоков на диск и освободил место в пространстве буферов) и приостановится в ожидании сообщения free buffer waits, которым dbwr известит его, что содержимое каких-то буферов было сохранено на диске и они свободны для повторного использования.

И здесь начинается путаница, из-за того, что мы ввели еще одну пару связанных списков: WRITE_MAIN и WRITE_AUX (которые, как пару, часто называют LRU-W). Ниже приводятся соответствующие записи из x$kcbwds

SQL> desc x$kcbwds
Name                          Null?    Type
----------------------------- -------- --------------------
...
NXT_WRITE                              RAW(4)
PRV_WRITE                              RAW(4)
NXT_WRITEAX                            RAW(4)
PRV_WRITEAX                            RAW(4)
CNUM_WRITE                             NUMBER
ANUM_WRITE                             NUMBER
...

Имена столбцов повторяют шаблон именования списков замены: у нас имеется две пары указателей (nxt_write, prv_write, nxt_writeax и prv_writeax), ссылающихся на основной и вспомогательный списки, cnum_write – общее число буферов в двух списках, и anum_write – число буферов во вспомогательном (auxiliary) списке.

В процессе поиска свободных буферов, сеанс переносит любые измененные (и не закрепленные) буферы из сканируемого списка замены в список WRITE_MAIN. Когда свободных буферов не обнаруживается, он посылает сообщение процессу dbwr, а тот, понимая с какой целью был вызван, записывает буферы из списка WRITE_MAIN на диск, после чего посылает ожидающему сеансу сообщение free buffer waits.

Таким образом, даже при том, что в горячей части списка REPL_MAIN тоже могут находиться «грязные» буферы, которые хранятся измененными уже несколько минут, сеанс переместит в список записи буферы, попавшие в кэш и изменившиеся лишь несколько мгновений тому назад. Но, погони за хвостом очереди контрольных точек недостаточно. Из-за особенностей работы алгоритма LRU/TCH, непопулярные, «грязные» буферы могут быть скопированы задолго до того, как успеют достигнуть «старого» конца очереди контрольных точек.

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

На рис. 2 показана примерная схема работы сеанса от момента начала поиска свободного буфера. Заштрихованные прямоугольники обозначают незакрепленные блоки, которые не могут использоваться, потому что содержат изменения. Остальные блоки между начальной и конечной точками сканирования – это либо закрепленные блоки (и, возможно, «грязные») или имеющие значение счетчика обращений, позволившее переместить их в «горячий» конец списка REPL_MAIN.

Цикл обхода списков записи в СУБД Oracle

Рис. 2. Цикл обхода списков записи

Когда обнаруживается незакрепленный «грязный» буфер, он переносится из списка REPL_xxx в список WRITE_MAIN. Обратите внимание, насколько это отличается от порядка обработки очереди контрольных точек – буфер не исключается из списка замены перед включением его в очередь контрольных точек, поэтому он может «находиться в двух местах сразу», но буфер может быть включен только в один из четырех подсписков: WRITE/REPL/MAIN/AUX. Когда сеанс не обнаруживает свободного буфера (или когда список записи достигает критического размера), он приостанавливает сканирование списков замены и посылает сообщение процессу dbwr. Процесс dbwr просыпается и просматривает список WRITE_MAIN, попутно перенося буферы из WRITE_MAIN в WRITE_AUX. Не забывайте, что измененный буфер не может быть скопирован на диск, пока не будет сохранена соответствующая запись из журнала – в этот момент dbwr может сравнить адреса RBA, решить, что буфер нельзя перемещать из WRITE_MAIN в WRITE_AUX, и послать событие процессу lgwr. Завершив сканирование, dbwr закрепляет блоки в списке WRITE_AUX в монопольном режиме и приступает к записи на диск. После записи и очистки буферов он перемещает их из списка WRITE_AUX в список REPL_MAIN и (потому что теперь буферы «чистые») исключает их из очереди контрольных точек. Затем dbwr возвращается назад и выполняет повторное сканирование WRITE_MAIN в поисках блоков, которые были пропущены во время первого прохода.

 

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

Инкрементальные контрольные точки появились достаточно поздно в жизни Oracle – в версии Oracle 8i, одновременно с алгоритмом LRU/TCH. До этого единственной известной контрольной точкой была контрольная точка переключения файлов журнала. Всякий раз, когда lgwr заполняет файл оперативного журнала, он должен сократить содержимое, потому что иначе файлы оперативного журнала очень быстро закончатся, и потребуется повторно использовать их. Данное требование обусловливает необходимость применения контрольной точки.

Контрольная точка прежде была простым моментом синхронизации. Моментом, когда Oracle мог сказать: «если блок данных изменялся до данного момента времени, то теперь он должен быть на диске со всеми изменениями». Oracle просто отправлял сообщение процессу dbwr, чтобы тот сохранил каждый «грязный» блок на диск, и засекал момент отправки инструкции. Этот момент (записывался как SCN, но только после того, как dbwr подтвердит, что скопировал все данные) превращается в маркер, говорящий: «все записи повторения, сгенерированные до этого момента, избыточны, потому что файлы с данными обновлены до этого момента».

Прежде такие моменты возникали всякий раз, когда заполнялся очередной файл журнала. Процесс dbwr вдруг с неистовством начинал записывать блоки данных на диски. И по завершении (что могло потребовать некоторого времени) Oracle обновлял SCN во всех заголовках файлов данных, чтобы показать, что эти файлы «обновлены» до этого момента времени. Пока базы данных были маленькими (в Oracle 6 объем баз данных был ограничен 63 файлами), обновление заголовков файлов выполнял сам процесс dbwr, но с ростом размеров баз данных, для решения всех задач, связанных с выполнением контрольных точек, в Oracle был добавлен отдельный процесс ckpt (checkpoint process – процесс выполнения контрольных точек). Помимо обновления заголовков файлов, ckpt также отвечает за обновление в управляющем файле (control file) каждые 3 секунды наименьшего значения RBA (или SCN, или обоих) на момент начала выполнения инкрементальной контрольной точки.


Управляющий файл


До сих пор я рассказывал только о файлах данных и файлах журнала, но имеются также другие файлы, входящие в состав активной базы данных Oracle. Управляющий файл (для которого обычно создается одна или две копии) – это файл, описывающий всю остальную часть базы данных. Он содержит списки всех файлов данных, файлов журнала (оперативного и архивного), резервных копий и различные сведения о состоянии файлов. Если вам любопытно узнать, какая информация хранится в управляющем файле, запросите содержимое v$controlfile_record_section.

Операции обновления информации в управляющем файле (например, в ходе выполнения 3-секундной инкрементальной контрольной точки SCN) упорядочиваются блокировкой очереди управляющих файлов (CF enqueue). Обычно операций с управляющим файлом выполняется не так много, но при выполнении большого числа невосстановимых операций (например, при обработке множества нежурналируемых объектов LOB), можно увидеть конкуренцию за обладание блокировкой очереди управляющих файлов, из-за большого числа обновлений, которые требуется выполнить в управляющем файле.


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

Примечание. Мне встречались статьи, где говорилось, что ckpt определяет LRBA, который dbwr должен использовать при выполнении инкрементальной контрольной точки. Данное утверждение выглядит разумным, так как ckpt – это процесс, обновляющий управляющий файл. Однако, вы не найдете никаких признаков, которые говорили бы, что ckpt и dbwr обмениваются сообщениями каждые 3 секунды, поэтому пока не ясно, как ckpt узнает, что запись данных завершена. Возможно dbwr вычисляет LRBA и записывает его в разделяемую память после завершения записи (разумеется, под защитой защелки – раньше я предполагал, что такую функцию выполняет защелка active checkpoint queue latch, но это предположение оказалось ошибочным) и ckpt просто «просыпается», чтобы проверить защелку и значение в памяти.

Контрольная точка восстановления носителя (media recovery checkpoint) и инкрементальные контрольные точки – не единственные типы контрольных точек, и проблемы со временем, которые несет запись большого числа блоков на диск, возникают не только при переключении файлов журнала. Поэтому в dbwr предусмотрен механизм распределения операций записи по приоритетам и несколько связанных списков для обслуживания контрольных точек других типов. Эти связанные списки можно увидеть в x$kcbwds, каждому из них соответствует группа из шести столбцов, по аналогии со списком записи (для экономии места я оставил в листинге только первые столбцы из каждой группы): 

SQL> desc x$kcbwds
Name                          Null?    Type
----------------------------- -------- --------------------
...
NXT_WRITE                              RAW(4)
PRV_WRITE                              RAW(4)
NXT_WRITEAX                            RAW(4)
PRV_WRITEAX                            RAW(4)
CNUM_WRITE                             NUMBER
ANUM_WRITE                             NUMBER
...
NXT_XOBJ                               RAW(4)
NXT_XRNG                               RAW(4)
NXT_REQ                                RAW(4)
NXT_PNG                                RAW(4) -- 8.1
NXT_L2W                                RAW(4) -- 11.2
NXT_L2R                                RAW(4) -- 11.2
NXT_L2K                                RAW(4) -- 11.2
...

Списки хранят ссылки на буферы, которые требуется скопировать по разным причинам, и разные причины определяют разные приоритеты для записи. В табл. 6.2 перечисляются списки и описываются их функции.

Краткое название Описание
XOBJ Связанный список буферов, которые должны быть записаны максимально быстро из-за специфики выполнения запросов на уровне объектов, таких как усечение (truncate) или удаление (drop). Наблюдать за содержимым этого списка очень сложно, так как он опустошается почти мгновенно.
XRNG Описывается как список диапазонов повторного использования. Это еще один список для буферов, подлежащих записи с наивысшим приоритетом – в данном случае для таких операций, как alter tablespace read only.
REQ Назначение неизвестно – возможно список буферов, запись которых затребовал механизм Cache Fusion (в этом случае соответствует списку, прежде известному как список ping, и может потребовать записи блоков вне очереди).
PNG Исчез в 9i. Связанный список буферов, которые должны быть записаны на диск, потому что другой экземпляр запросил блок (или блок, охватываемый той же блокировкой).
L2W 11.2: DB Flash Cache Write list (?) – в настоящее время назначение неизвестно.
L2R 11.2: DB Flash Cache Read list (?) – в настоящее время назначение неизвестно.
L2K 11.2: DB Flash Cache Keep list (?) – в настоящее время назначение неизвестно.

Примечание. Я ничего не могу сказать о списках L2*, которые, как я предполагаю, имеют отношение к механизму Flash Cache, реализованному в Oracle 11.2. В этой же версии появилась вторая защелка для цепочки LRU буферов в кэше для каждого рабочего набора данных, хотя этот факт не нашел отражения в x$kcbwds. Я еще не видел новую защелку в действии и пока предполагаю, что она может быть связана с тремя списками L2*.

По описаниям различных списков в табл. 6.2 видно, что существуют весьма массивные операции, требующие скопировать массу буферов на диск в самое короткое время. И каждая из таких операций имеет форму контрольной точки. Итак, можно увидеть признаки (скрытые непосредственно в выполняемых файлах Oracle) следующих типов контрольных точек – я извлек имена из выполняемых файлов Oracle командой strings –a oracle и воспроизвел результаты в табл. 6.3, где также дал мое понимание, когда и почему выполняется каждая контрольная точка.

Имя контрольной точки Когда выполняется
Instance recovery checkpoint После завершения восстановления экземпляра.
Media recovery checkpoint Сопровождает переключение файлов журнала.
Thread checkpoint Сопровождает переключение файлов журнала – хотя в последних версиях Oracle данная контрольная точка может выполняться спустя значительный промежуток времени после переключения.
Interval checkpoint В моменты, определяемые параметрами log_checkpoint_interval и log_checkpoint_timeout.
Tablespace checkpoint В ответ на одну из команд: alter tablespace begin backup; alter tablespace offline;
PQ tablespace checkpoint Перед тем, как сеанс сможет выполнить чтение в режиме прямого доступа (direct path reads). До версии 11g такое было возможно только для параллельных запросов, но в 11g стали поддерживаться и последовательные запросы.
Close database checkpoint В ответ на одну из команд: alter database close; shutdown [normal | immediate | transactional]
Incremental checkpoint Каждые 3 секунды, благодаря механизму таймаутов, встроенному в процесс контрольных точек (ckpt).
Local database checkpoint В ответ на команду: alter system checkpoint local
Global database checkpoint В ответ на команду: alter system checkpoint global
Object reuse checkpoint В ответ на команду усечения объекта.
Object checkpoint В ответ на команду удаления объекта.
RBR checkpoint Недавнее нововведение, пополнившее список контрольных точек. Полное имя этой контрольной точки: «Reuse Block Range checkpoint». Я наблюдал ее появление по завершении перестройки индекса. Поэтому, можно предположить, что она выполняется для вновь созданных объектов.
Multiple object checkpoint Еще одно недавнее нововведение. Как можно предположить из названия, эта контрольная точка, по всей видимости, выполняется, когда требуется выполнить контрольную точку сразу для нескольких объектов, например, после удаления сегментированного объекта.

Должен признаться, что не совсем понимаю различия между контрольной точкой локальной базы данных (local database checkpoint) и контрольной точкой потока (thread checkpoint). Учитывая, что термин «поток» (thread) часто употребляется как синоним термина «экземпляр» (instance), а «локальная» база данных – это, по всей видимости, просто единственный экземпляр, мне кажется, что они предполагают один и тот же результат, хотя могут отличаться способами их вызова.

Существует пара контрольных точек, о которых хотелось бы сказать отдельно. Параллельные запросы часто приводят к выполнению чтения в режиме прямого доступа (direct path reads, полное сканирование таблицы или быстрое полное сканирование индекса). Это означает чтение блоков с диска непосредственно в приватную память сеанса, в обход кэша данных. Но это также означает, что если в кэше имеются «грязные» буферы, сеанс не увидит последние версии таких блоков, если только они не будут скопированы на диск перед запуском запроса. Поэтому параллельные запросы начинаются с контрольной точки. Имейте в виду, что в 11g допускается возможность чтения в режиме прямого доступа для последовательных запросов и они также должны начинаться с контрольной точки того же типа.

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

Примечание. Копирование грязных буферов на диск перед удалением объекта выглядит странным только на первый взгляд, в действительности этому есть рациональное объяснение. Начиная с версии 8i, в Oracle появилась поддержка согласованного чтения для DDL (cross-DDL read consistency): если удалить раздел секционированной таблицы (partitioned table) в момент, когда другой сеанс выполняет запрос к этой таблице, другой сеанс все еще будет видеть этот раздел (даже при том, что табличное пространство будет отображаться как свободное). Копируя на диск измененные блоки перед удалением объекта, Oracle позволяет другим сеансам повторно читать необходимые им блоки – по крайней мере, пока какой-то другой сеанс не сумеет выделить память под новый объект. Однако, я не могу придумать веской причины делать то же самое при усечении объекта.

Наконец, когда вы переводите табличное пространство в режим резервного копирования (если вы все еще вручную управляете резервным копированием из операционной системы) или отключаете его, перед изменением состояния табличного пространства Oracle записывает все измененные блоки из этого табличного пространства на диск.

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

Самая большая моя проблема со списками записи – я не могу увидеть их заполненными. Я предполагаю, что они действуют подобно WRITE_MAIN и WRITE_AUX, в том смысле, что сначала сеанс исключает буферы из списка замены и включает в соответствующий MAIN-список, затем процесс dbwr исключает их из MAIN-списка и включает в AUX-список, а после записи на диск – исключает их из AUX-списка и включает во вспомогательный список замены. Но при этом значения столбцов в x$kcbwds, похоже, не обновляются и не отражают факт использования списков. Я даже пытался приостанавливать dbwr (в экземпляре 10.2.0.3), чтобы любые блоки, попадающие в любые списки записи, застревали в списке MAIN, но я не увидел, что параллельные запросы и операции усечения или удаления заполняют какие-либо списки записи, хотя сами команды застревают в ожидании enq: KO - fast object checkpoint (параллельные запросы) и enq: RO - fast object reuse (усечение/удаление).

Как работает запись в Oracle; процесс dbwr и lgwrв - блоки, буферы, контрольная точка  и журнал 

Очереди заголовков буферов

Я предлагаю еще раз взглянуть на дамп заголовка буфера, где мы увидели признак существования еще одного множества скрытых очередей. Ниже приводится копия заголовка буфера, представленного выше, в разделе «Заголовки буферов»: 

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]

Ранее я очень коротко остановился на полях fileq: и objq: и выдвинул предположение, что значение object_ckpt_list в obj-flags может служить признаком наличия еще одного множества очередей объектов. Независимо от того, имеется ли еще одно множество очередей объектов, очереди, отображаемые в дампе, сообщают нам кое-что о контрольных точках на основе объектов и табличных пространств.

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


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

Примечание. Заголовки очередей объектов можно найти в x$kcboqh (kernel cache buffers object queue header – буферы в кэше ядра с заголовками очередей объектов) и отдельные буферы в x$kcbobh (kernel cache buffers object buffer headers – буферы в кэше ядра с заголовками буферов объектов). Так как эти структуры находятся в разделяемой памяти и могут изменяться параллельными процессами, они должны быть защищены защелкой, в данном случае защелкой object queue header operation. Каждый рабочий набор данных имеет собственную очередь объектов, а это означает, что в x$kcboqh можно найти множество строк для единственного объекта, и каждое множество очередей объектов имеет собственную защелку. То есть, число защелок object queue header operation совпадает с числом рабочих наборов данных.

И снова, при выполнении команд tablespace begin backup и tablespace read only все измененные блоки, принадлежащие табличному пространству, должны быть записаны на диск, а это означает необходимость записи всех измененных блоков из каждого файла в табличном пространстве, и в этом может помочь очередь fileq (но ни для одной из них нет соответствующей структуры x$). Однако, если потребуется отключить или удалить табличное пространство, каждый буфер должен получить статус free – а блоки попадают в fileq, только когда они изменяются, поэтому создается впечатление пробела в реализации.

 

Контрольные точки и файлы журнала

После исследования связанных списков и очередей записи мы возвращаемся к влиянию контрольных точек, и в частности – к контрольной точке восстановления носителя (media recovery checkpoint). Основным назначением любой контрольной точки является обновление содержимого файлов данных (или их части) до определенного момента времени. Назначением контрольной точки восстановления носителя является обновление содержимого файлов данных до момента, соответствующего концу заданного файла журнала. Когда контрольная точка этого типа завершает работу, процесс ckpt обновляет все файлы данных до начального номера SCN следующего файла журнала – этого достаточно, чтобы сообщить Oracle, что предыдущий файл журнала повторений теперь стал избыточным – и обновляет информацию обо всех измененных файлах в управляющем файле.

А теперь представьте себе такой сценарий. Сеанс обновляет пару строк в таблице – приобретая для этого приватный поток журнала – и затем приостанавливается на время. Несколько минут спустя текущий файл журнала заполняется до предела и происходит переключение файлов журнала, то есть Oracle требуется использовать текущий номер SCN в качестве начального номера SCN для следующего файла журнала. Но что произойдет в этом случае с записями в приватном потоке журнала, созданными несколько минут назад? Если только Oracle не предпримет какие-то особые меры, приватные записи повторения окажутся в следующем файле журнала, а это значит, что некоторые записи в файле журнала будут иметь номер SCN меньше начального номера SCN файла.

Едва ли я удивлю вас, если скажу, что разработчики в Oracle Corp. видели эту проблему и предусмотрели ее решение, но это ведет нас к интересному побочному эффекту. Реализация Oracle предполагает самый худший сценарий при переключении файлов журнала – то есть, что все активные общедоступные и приватные потоки заполнены – поэтому переключение файлов происходит заблаговременно, в момент, когда свободного места в файле журнала остается ровно столько, чтобы вместить все записи повторения, которые могут храниться в буферах. Например, если имеется 2 активных общедоступных потока по 5 Мбайт каждый и 12 активных приватных потоков по 128 Кбайт каждый, тогда переключение файлов произойдет, когда в файле журнала останется 11.5 Мбайт свободного места (2×5 Мбайт + + 12×128 Кбайт). Если так случится, что к этому моменту в буферах журнала не будет ни одной записи, это пространство останется неиспользованным – именно по этой причине Oracle динамически регулирует число потоков, стараясь свести их число к минимуму. Если вы будете видеть, что архивные файлы журналов систематически оказываются на несколько мегабайт меньше файлов оперативного журнала, теперь вы сможете объяснить причину (в версиях 10g и выше).

Примечание. Когда выполняется архивирование файла оперативного журнала, в архив копируются только использованные фрагменты. Есть несколько причин, почему получившийся архив может оказаться меньше исходного файла, и незаполненность приватного потока журнала – одна из них. Но чаще всего причина заключается в том, что администраторы устанавливают параметр archive_lag_target или определяют задания в cron такие, что переключение файлов журнала выполняется через регулярные интервалы времени.

Предыдущий абзац объясняет, как Oracle гарантирует наличие свободного места в текущем файле журнала для всех имеющихся потоков журналирования на момент контрольной точки восстановления носителя (media recovery checkpoint), но как все «дополнительные» потоки журналирования попадают в файл? В этом оказывает помощь процесс записи данных. В статье про механизм повтора и отмены Oracle упоминалось о концепции механизма IMU flush, который предварительно копирует приватный поток журналирования в общедоступный буфер. Одной из причин вызова механизма IMU flush как раз и является контрольная точка восстановления носителя. В действительности тот же эффект возникает, всякий раз, когда процессу dbwr требуется скопировать буфер на диск и обнаруживается, что буфер находится в приватном потоке. К действиям, о которых сообщает x$ktiff в виде статистики Contention flushes, относятся операции, выполняемые процессом dbwr перед копированием данных: приобретение и освобождение защелки redo allocation для соответствующего потока журналирования, чтобы иметь возможность изменить его состояние на being flushed, копирование (всех) записей из потока в общедоступный буфер журнала, и установка состояния flushed потока (опять же, с приобретением и освобождением защелки).

Если сеанс попытается сгенерировать дополнительные записи повторения, пока dbwr выталкивает приватные буферы журнала, он может столкнуться с проблемами – невозможность использования приватного потока, потому что dbwr удерживает его под своим контролем, и невозможность использования общедоступного потока, потому что иначе более новые векторы изменений могут оказаться в буфере раньше более старых векторов. Поэтому сеанс должен приостановиться, пока dbwr не завершит копирование. Эта ситуация порождает ожидания log file switch (private strand flush incomplete), которые иногда можно увидеть в журнале предупреждений.

Когда dbwr завершит копирование приватных потоков в общедоступный буфер, он пошлет ожидающим процессам сообщение, что они могут продолжать работу, и с этого момента все последующие записи повторения, генерируемые сеансом, будут попадать в общедоступный поток журналирования обычным путем. Однако приватный поток, даже при том, что он все еще будет связан с сеансом, не сможет использоваться другими транзакциями, пока сеанс не выполнит подтверждение. В этом состоит маленькая особенность – записи повторения, созданные сеансом до этого момента, будут учтены статистиками redo entries и redo size процесса dbwr, а соответствующие записи отмены будут учтены статистикой undo change vector size сеанса.

 

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

Oracle и непроцедурный доступ ...
Oracle и непроцедурный доступ ... 8510 просмотров Antoni Tue, 21 Nov 2017, 13:32:50
Видеокурс по администрированию...
Видеокурс по администрированию... 10719 просмотров Илья Дергунов Mon, 14 May 2018, 05:08:47
Oracle: как переделать Primary...
Oracle: как переделать Primary... 1494 просмотров Игорь Воронов Fri, 11 Jun 2021, 16:10:44
Работа с запросами Approximate...
Работа с запросами Approximate... 2271 просмотров Андрей Васенин Mon, 29 Oct 2018, 06:40:46
Войдите чтобы комментировать

Oracle_Admin аватар
Oracle_Admin ответил в теме #8943 6 года 3 нед. назад
Мега Подробный обзор темы! Большое спасибо автору блога!