Library cache mutex x oracle что это

Обновлено: 04.07.2024

a. транзакция 1 изменяет данные:
* в буферный кэш скидывается грязный блок
** в блоке данных записывается список недавних транзакий ITL (включая сейчас выполняющуюся)
** если транзакция сейчас выполняется, то также проставляется признак блокировки строки в блоке
* процесс lgwr пишет:
** в redo журнал повтора - новое значение, новый scn, список ITL
** в undo журнал отката - старое значение и старый scn
* процесс dbwr пишет:
** асинхронно с задержкой записывает данные в блок базы
*** сразу после commit записывается не более 30% данных
*** остальная часть записывается отложенно при следующем select из строк этой таблицы.
Детектировать можно через события "db block change, consisten gets - examination"
Т.е. не стоит удивляться, что первый select после большого update будет выполняться очень долго.

b. транзакция 2 читает данные:
* считываются данные из буферного кэша или напрямую с диска
* просматривается список ITL на наличие незавершенных транзакций
* в любом случае (не зависимо от ITL) сверяется SCN транзакции и SCN блока (ITL незавершенной транзакции)
** если SCN блока/ITL оказывается больше запрашиваемого, то данные берутся из сегментов отката UNDO через ссылку из ITL
*** поиск по ITL может продолжиться и дальше рекурсивно, если SCN запроса опять меньше SCN из undo
*** если данные в undo не находятся, то это является причиной ошибки "snapshot too old"
* в случае недавнего обновления блока, строка может оказаться помеченной как сейчас обновляемая и со старым SCN в списке ITL выполняется операция отложенная очистка:
** берутся данные из UNDO сегмента, смотрится, что транзакция подтверждена
** сбрасывается флаг блокировки в строке блока и ITL (что повторно генерирует redo логи при select таблицы)
** в случае отсутствия блока в буферном кэше и чтения с диска дополнительно изменяется номер SCN на максимальный (т.к. отсутствие блока в кэше говорит об однозначно последней версии на диске)

Примечание: как многоверсионность сделана в Postgree:

2. Восстановление данных
В случае падения бд, данные в кэше теряются, но у нас остается устаревшая версия бд в файлах данных (т.к. данные пишутся асинхронно с задержкой или даже отложенно) и актуальные redo логи. То при старте бд файлы данных добиваются до актуального значения по этим логам.
Т.е. мы всегда имеем самую актуальную версию блока (как было сказано раньше) либо в буферном кэше (который дополнительно тестируется по undo) или на диске, если кэша нет (тогда тестировать уже ничего не надо)

Redo лог также может использоваться в репликации. В зависимости от вида лога - текст или бинарные данные, будут разные преимущества и недостатки:

Бинарные логи (именно этот режим в Oracle и через него работает физический standby):
- передаются все изменения (включая зависимые структуры)
- применение лога блокируется, если кто-то пишет в таблицу
- невозможность репликации между версиями
+ малая ресурсоемкость

Текстовая репликация (можно добиться в Oracle через log miner - логический standby):
+ меньше данных передавать по сети (только сам текст, без зависимых изменений)
+ меньше блокировок (обрабатывается как обычный запрос)
+ возможность репликации между разными версиями
- ресурсоемко при применении (обрабатывается как обычный запрос, обновляя зависимые структуры)

3. Общий принцип кэширования блоков в память при чтении
В общем виде кэш - это хэш массив связанных списков.
Список внутри выстраивается по частоте обращения к блоку и имеет указатель на середину, названную холодной (cold) точкой.
Просмотр мест под новый прочитанный блок начинается с конца списка, проверяется что буфер не грязный (уже сброшен на диск) и не закреплен (сейчас не редактируется).
Варианты последующих действий с последним, самым редкоиспользуемым блоком:
а. в конце оказывается буфер со счетчиком обращений = 1, то он открепляется от списка, а новый блок помещается в среднюю точку cold. Для этого ссылки соседей открепляются друг от друга и перенацеливаются на новый блок, который становится новой серединой.
б. в конце буфер со счетчиком > 1, тогда последний блок открепляется, счетчик обращений делится на 2 и этот блок перемещается в начало списка.
Середина списка также смещается влево, одновременно делая число обращений = 1 у новой элемента-середины, если к ней никто не обращался за время смещения от начала до середины.
Новый блок помещается в центр списка cold.
Более подробное описание можно видеть тут: Oracle: Lru буферный кэш

Для оптимизации скорости чтений блока данных в кэш, существует еще один список (REPL_AUX) в котором хранятся указатели на чистые/свободные блоки. Именно из него берется чистый блок и перемещается в основной lru список. Также Oracle в фоне перемещает очищенные блоки обратно в REPL_AUX.

Отдельно упомяну оптимизации, которые проводит oracle при полном сканировании таблицы (full table scan).
Если их не производить, то огромная таблица способна быстро вытолкнуть все буферные данные из sga.
Существует 3 стратегии
* Маленькие таблицы = 10% - данные таблицы загружаются в кэш, но не более 25% его размера. Счетчик устанавливается в минимальное значение и больше не увеличивается, за счет чего блоки таблицы быстро уходят из буфера.

Стоит отметить:
* размер таблицы берется из ее статистики и отсутствие статистики может сыграть злую шутку.
* если таблица значительно больше буфера или нужна разово, можно указать oracle необходимость читать таблицу напрямую в приватную память процесса pga, минуя буферный кэш sga "_serial_direct_read = always"


5. RAC - кластер экземпляров oracle
В общем виде rac представляет из себя кластер экземпляров oracle с собственными процессами записи логов (lgwr), данных бд (dbwr), разделяемыми кэшами sga, но с общим файлом логов и базы данных.

Отсюда плюсы:
+ Масштабируемость при увеличении числа пользователей в oltp системе.
Достигается за счет распределния множества небольших запросов на разные кластеры rac.
+ Надежность в случае отказа. При отказе одной из нод кластера: незавершенные и новые запросы подхватятся другими.
Реализуется это за счет:
* Общего dns адреса для группы ip, для выполнения запроса выбирается менее загруженная нода
* Контроль за доступностью за каждой нодой путем опроса соседей и записи состояния в общий диск кворума.
** Если оказывается, что одна нода не пишет в кворум или не отзывается, то она отсоединяется от сети
** Если сама нода перестает видеть большую часть соседей (>50%), то она самостоятельно отключается от сети. Если она видит больше 50% нод, то отключается меньшая часть.

Минусы какие я вижу:
- Плохо подходит для olap систем с небольшим числом тяжелых запросов.
Частично может решаться за счет параллельной обработки на разных нодах, но что тоже только частичное решение, т.к. это влечет увеличения трафика на обмен данными между потоками разных нод.

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

Последовательности (sequence) приобретают новые свойства в распределенной системе:
* По умолчанию сиквенс разносится по нодам по размеру кэша.
Допустим размер кэша = 1000 и число нод 4, тогда нода 1 получает значения 1-1000, нода 2: 1001-2002 и т.д.
Если сиквенс сделать не кэшируемым, то это породит гигантский трафик синхронизации счетчика между нодами.
Т.е. в кластерной системе нельзя надеяться на непрерывность сиквенса.
* Если непрерывность все таки нужна, то можно указать параметр "ordered" при создании и сделать сиквенс не кэшируемым. В этом случае выдача нового значения будет происходить всегда в 1 месте и при инкременте будет получаться защелка.
Этот вариант очень плох, на него не стоит надеяться, т.к. сиквенс становится узким местом распределенной системы, и также не гарантирует непрерывность счетчика при rollback, аналогично некластерным системам.


6. Блокировки и защелки
1. Защелки - ограниченный ресурс БД. Используется для защиты внутренних объектов бд от непреднамеренного изменения.
Защелки бывают 2 видов:
* исключительные - захват защелки блокирует любое одновременное обращение к этой области памяти.
* разделяемые - могут использоваться одновременно несколькими потоками чтения и одним записи.
Принцип работы построен на атомарной операции. Поток перед обращением к области памяти пытается захватить защелку атомарной операцией:
- если защелка уже занята, то процесс уходит в цикл ожидания
- если нет, то установка флага и начала работы (проверка и установка флага проходит атомарно - за один такт процессора)
В разделяемых защелка флаг расширяется из просто флага до цифры: 0x1000005. В нем старший бит означает блокировку защелки на запись (0x1), а младшие биты число читающих сеансов (0005).
- новые читающий сеанс увеличивает младший бит, при окончании чтения бит уменьшается
- записывающий сеанс устанавливает старший бит и блокирует новые чтения (они становятся в очередь) и ждет когда счетчик читающих опустится до 0
Для понимания ограниченности ресурса защелок: на весь буферный кэш выделяется защелок = количество cpu / 2.
Защелки используется в Library cache, буферном кэш и Redo. В представлении v$latch можно видеть статистику использования защелок, сколько запрошено, сколько занято сразу, а сколько ушло в ожидание (v$lathholder - информация о том кто держит защелку)

2. Мьютекс - принцип работы в общем схож с защелками, но в отличии от них создается на месте объекта динамически. Это убирает проблему ограниченного кол-ва, но не дает центрального механизма их контроля. В случае сбоя работы процесса, защелка может остаться навсегда занятой.

Oracle двигается к замене блокировок на mutex, что дает неограниченное число поддерживаемых блокировок и уменьшает время на ожидания.

События связанные с защелками и мьютексами:
Блокировки библиотечного кэша:
* Cursor: mutex . - блокировка курсоров при сборе статистики
* Cursor: pin S . - поиск субплана и парсинг
Блокировки буферного кэша:
* latch: cache buffer chains - конкуренция за горячий блок
* latch: cache buffer chains LRU chains - кэш слишком маленький, блоки быстро замещаются из LRU
* Buffer busy waits - множество процессов конкурируют за чтение/вставку в один блок в кэше
* Free buffer waits - дбврайтер медленно скидывать грязные блоки на диск

Так что же такое library cache pin? Попробуем на примерах разобраться в том, что представляет собой это ожидание и чем его массовое возникновение так опасно для нормальной работы СУБД.

Защёлка

Защёлка library cache pin может захватываться в разделяемом и монопольном режимах. Разделяемый режим используется для чтения структур объекта библиотечного кэша, без права их изменения. Обычно это выполнение или исследование SQL или PL/SQL объекта. Защёлка в монопольном режиме, наоборот, приобретается для изменения информации в динамических областях объекта библиотечного кэша. Потребность в этом возникает в случаях разбора объекта, его переопределения, компиляции, а также при изменении привилегий к нему. Некоторые из этих действий совершаются сеансами самих пользователей, другие выполняются только администраторами баз данных.

Пожалуй, здесь мы ненадолго остановимся и для лучшего понимания механизма работы защёлки library cache pin немного коснёмся устройства самого библиотечного кэша.

Библиотечный кэш

Библиотечный кеш расположен в памяти разделяемого пула и в общем случае состоит из связанного набора довольно сложных структур. В самом верху этого набора расположена так называемая хэш-таблица, которая представляет собой массив бакетов (ячеек таблицы). Каждый бакет имеет свой индекс и содержит адрес начала двунаправленного списка дескрипторов объектов библиотечного кэша (Library Cache Object Handle). Дескрипторы в свою очередь указывают на структуры самих объектов библиотечного кэша (Library Cache Object или LCO). Обычно в бакете расположен список с всего одним дескриптором LCO, но бывает и так, что в результате так называемой хэш-коллизии в список указателей может попадать и несколько объектов.

Объект библиотечного кэша может содержать до восьми блоков данных, на которые ссылается с помощью дескрипторов. Каждый блок имеет заголовок, содержащий флаг статуса и счётчик pin, указывающий на количество закреплений данного блока. Информация, хранимая в блоках данных, разнообразна и зависит от типа объекта библиотечного кэша. С этим же связано так же то, что в структуре LBO могут присутствовать не все блоки данных. Ниже представлена краткая информация о содержимом блоков данных LBO:

И так, теперь мы в общих чертах представляем, как устроен библиотечный кэш Oracle и знаем, для чего в системе нужна защёлка library cache pin. Можно приступать к практической части статьи. Но прежде чем это делать, пройдёмся немного по описанию динамических таблиц, непосредственно связанных с библиотечным кэшем.

Динамические таблицы

Из всего списка динамических таблиц Oracle касающихся библиотечного кэша, пожалуй, можно выделить всего три таблицы. Это таблица x$kgllk, которая содержит все структуры блокировок библиотечного кэша, таблица x$kglpn, содержащая структуры состояний закреплений в библиотечном кэше и таблица x$kglob, хранящая в себе все ресурсы, находящиеся в библиотечном кэше. Так же в процессе изучения нам понадобятся ещё две вспомогательных динамические таблицы x$ksuse и x$ksled. Первая из них хранит всю информацию о текущих сеансах Oracle, вторая, наименования всех ожиданий Oracle.

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

Попробуем соединить имеющиеся в нашем распоряжении динамические таблицы, и начнем, пожалуй, с таблицы x$kglpn. Именно в ней храниться информация обо всех текущих закреплениях объектов в библиотечном кэше. Единственное чего там нет, это наименований самих этих объектов, в отличии, к примеру, от динамической таблицы x$kgllk. В то же время в таблице x$kglpn есть поле kglpnhdl. Оно содержит адрес дескриптора KGL объекта в библиотечном кэше и указывает на поле kglhdadr из таблицы x$kglob. Связав две таблицы по перечисленным выше полям, мы вычислим имя ресурса закреплённого в библиотечном кэше. В дальнейшем нам остаётся только связать таблицу x$kglpn с таблицей x$ksuse, таким образом, мы определим сеанс, осуществивший закрепление объекта в библиотечном кэше. Оставшаяся таблица x$ksled нужна нам для вывода наименования текущего события, происходящего в сеансе. Впрочем, если вместо таблицы x$ksuse использовать динамическое представление v$session, то данная таблица просто не понадобиться.

В результате полученных связок динамических таблиц мы получим запрос, который будет показывать нам все закрепления сеансов пользователей применительно к какому-то определённому объекту:

Данным объектом в нашем случае будет тестовая процедура, которую мы создадим чуть позже, а пока приведем пример запроса для другой динамической таблицы x$kgllk:

Запрос предназначен для вывода информации о блокировках библиотечного кэша, выбранного нами объекта. Проще говоря, он будет выводить состояние блокировок library cache lock и в дальнейшем позволит прояснить некоторые моменты закрепления объектов. По соединению таблиц запрос аналогичен предыдущему SQL выражению, хотя здесь можно было бы обойтись и без таблиц x$kglob и x$ksuse. В таблице x$kgllk большинство полей уже имеют необходимую нам информацию.

Создание теста

После того как мы определились с запросами, которые помогут нам наблюдать состояние исследуемых защёлок, можно приступать к созданию теста. В качестве тестовой базы данных у нас будет выступать редакция Oracle Database 10G Express Edition, а в качестве клиентского приложения инструмент администратора ZhiSQL for Oracle.

Для начала создадим трёх пользователей user1, user2, user3. Они будут выступать в качестве простых клиентов, выполняющих какую-то процедуру. Пользователь user3 при этом не будет иметь права на её запуск. Далее создадим пользователя user_dba. Это будет администратор, который имеет право компилировать указанную процедуру и выполнять другие привилегированные действия. В качестве последнего действия создадим саму процедура sleep_proc расположенную в схеме test, единственным действием которой будет засыпание на 60 секунд.

И так, приступим к созданию необходимых нам тестовых объектов:

Разделяемый режим защёлки

Тестовые объекты созданы. Теперь можно приступать и к изучению защёлки. Для начала произведём имитацию обычной работы пользователей. Образуем два сеанса от имени пользователей user1, user2 и запустим на выполнение в этих сеансах процедуру sleep_proc:

Процедура приостанавливает работу сеанса на 60 секунд. Этого времени нам с избытком хватит для просмотра состояния защёлки library cache pin с помощью запроса, составленного нами ранее:

Получение защёлки в разделяемом режиме позволяет расширить параллелизм в использовании объекта несколькими сеансами. Но разве нельзя было бы обойтись для этого приобретением только одной защёлки library cache lock? Для того, что бы прояснить этот вопрос, нам придётся сделать дамп библиотечного кэша и исследовать его структуру.

Дамп библиотечного кэша

Дамп библиотечного кэша образуется при выполнении команды ALTER SESSION SET EVENTS 'immediate trace name library_cache level 32' и представляет собой текстовый файл, создаваемый по пути, определяемым значением параметра инициализации user_dump_dest. Число 32 в команде обозначает уровень трассировки библиотечного кэша. В нашем случае мы будем делать дамп кэша на самом детальном уровне.

И так, выполним предыдущий пример, и в момент времени, когда тестовая процедура исполняется, запустим на выполнение приведённую выше команду:

Образовавшийся файл дампа получился довольно большой. Поэтому здесь приведена только его часть, относящаяся к тестовой процедуре sleep_proc:

Вспомним устройство библиотечного кэша. В самом верху, приведенного отрезка дампа, после слова BUCKET мы видим номер бакета (его индекс в хэш-таблице). Бакет содержит список дескрипторов объектов библиотечного кэша. В нашем случае в списке находиться один дескриптор, его значение приведено чуть ниже, сразу после слова LIBRARY OBJECT HANDLE . Далее идут составные части структур объекта библиотечного кэша. Мы видим имя объекта, нашу тестовую процедуру TEST.SLEEP_PROC, название пространства имён TABL, сразу после слова namespace. В названии TABL, кстати, на самом деле нет ничего необычного. Это наименование обозначает пространство имён для определения таблиц и спецификации хранимых объектов PL/SQL.

Следом за названием пространства имён нам следует обратить внимание на значение, идущее сразу после идентификатора pin. В нашем примере оно ровно значению S и это свидетельствует о том, что объект закреплён в библиотечном кэше в разделяемом режиме. Так же, немного левее мы можем наблюдать режим защёлки library cache lock. Значение расположено сразу после lock= и равно значению N (NULL). Это означает, что защёлка library cache lock может быть захвачена другим сеансом в любом режиме, вплоть до монопольного. Данный момент мы будем наблюдать на примере чуть позже, а пока вернёмся к тексту дампа.

Если посмотреть значение строки напротив 4 блока в столбце pins, мы увидим, что оно равно 2. Это и есть закрепления в разделяемом режиме тестовой процедуры sleep_proc, которые осуществили два наших сеанса при её выполнении. Теперь всё становиться понятно. Защёлка library cache lock приобретается только на дескриптор библиотечного кэша, причём при выполнении PL/SQL объекта она не препятствует захвату LBO в монопольном режиме другим сеансом. В тоже время, защёлка library cache pin защищает критически важные блоки данных уже внутри LBO. В нашем случае это будет машинно-зависимый код. Сеансу, который хочет изменить объект библиотечного кэша достаточно захватить защёлку library cache lock в монопольном режиме на дескрипторе LBO, при этом сеансы, пытающиеся вновь выполнить или изменить этот же объект, будут ожидать освобождения защёлки. Затем сеанс делает запрос на установку защёлки library cache pin, пытаясь изменить данные в блоках данных LBO. Если блок уже закреплён, то сеанс будет ожидать освобождения его закрепления, в нашем примере это произойдёт после того как тестовая процедура sleep_proc отработает в созданных нами сеансах.

Такой режим работы последовательного захвата защёлок library cache lock и library cache pin в монопольном режиме, позволяет заранее резервировать сеансом изменения в структурах объекта библиотечного кэша, даже не смотря на то, что данные этих структур всё ещё могут использоваться другими сеансами. Посмотрим, как это будет выглядеть практически.

Монопольный режим защёлки

Монопольный режим захвата защёлки library cache pin выполняется в основном в результате прямых административных действий над объектом. Защёлка в таком режиме приобретается при изменении кода объекта, его перекомпиляции, изменения прав, сбора статистики, очистки или удаления объекта. Данный режим защёлки устанавливается всегда, когда требуется изменить информацию в структурах объекта библиотечного кэша.

Проведём небольшой тест. Образуем два сеанса. В первом, от имени пользователя user1 запустим на выполнение процедуру sleep_proc:

Посмотрим закрепления процедуры в библиотечном кэше:

Видно, что сеанс пользователя user1 закрепил наш объект в разделяемом режиме (PIN MODE равно 2). В то же время сеанс пользователя user_dba пытается приобрести защёлку library cache pin на объект в монопольном режиме (PIN_REQ равен 3), но вынужден ожидать освобождения защёлки сеансом 38.

Посмотрим, что же происходит в это время с защёлкой library cache lock:

Результат предсказуем. Защёлка library cache lock была захвачена сеансом 38 в так называемом NULL режиме, при выполнении процедуры sleep_proc. Параллельно сеанс 29 так же приобрёл защёлку на дескриптор LBO в монопольном режиме, препятствуя другим сеансам получить доступ к объекту библиотечного кэша. Кстати, проверим, так ли это. В приведённом выше примере, образуем ещё один сеанс пользователя user_dba и выполним в нём команду компиляции процедуры sleep_proc:

Сеанс находится в ожидании. Посмотрим, осуществил ли он запрос на приобретение защёлки library cache pin:

Из полученной информации видно, что сеанс 22 не ожидает закрепления нашего тестового LBO. Тогда чего он ждёт? Выведем список блокировок library cache lock:

Становиться ясно, что сеанс 29 пытается откомпилировать процедуру sleep_proc, но ждёт снятия закрепления с объекта в библиотечном кэше. Для того чтобы другие сеансы не пытались выполнить или изменить объект, он выставляет защёлку на дескриптор LBO в монопольном режиме (LCK_MODE=3). Поэтому сеанс 22 , пытающийся откомпилировать процедуру в тот же момент времени, вынужден ждать освобождения защёлки library cache lock (LCK_REQ=3). В дальнейшем все сеансы, которые хотят не только изменить объект, но и выполнить его вынуждены будут ожидать освобождения этой защёлки. Такая ситуация может нарушить нормальную работу базы данных, особенно если объект часто используется.

Возникающие опасности

Хотя защёлка library cache pin сама по себе обычно не приносит большого вреда системе, так как в большинстве случаев захватывается в разделяемом режиме, её приобретение в монопольном режиме может наделать немало бед. Рассмотрим примеры.

Далее образуем сеанс пользователя user_dba и запустим в нём процедуру:

Сеанс зависает в бесконечном ожидании. Что же случилось? Все становиться понятно, если вывести список закреплений нашего тестового объекта:

Сеанс ожидает освобождение закрепления LBO, которое он сам же и сделал. Не стоит напоминать, что вместе с ним будут ждать, и все последующие сеансы, пытающиеся выполнить эту процедуру. Ожидание такое бесконечно и может быть прервано только уничтожением сеанса.

Следующий пример не столько опасен, как предыдущий, но тоже может приводить к неприятным ситуациям. Вернём для начала процедуру sleep_proc в первоначальное состояние. Далее образуем три сеанса от имени пользователей user1, user2, user3. В первом сеансе запустим процедуру sleep_proc на выполнение:

В третьем сеансе, от имени пользователя user3, попытаемся переопределить процедуру. При этом не стоит забывать, что пользователь user3 не только не имеет прав на переопределение этой процедуры, но у него вообще отсутствуют права даже на её запуск. Единственные привилегии, которые он имеет это роль connect. Выполним команду:

Сеанс завис. Снова пробуем выполнить во втором сеансе от имени пользователя user2 процедуру sleep_proc:

Сеанс зависает. Что же происходит? Ищем виновника сложившейся ситуации. Для этого выведем список закреплений объекта:

Становиться ясно, что хотя пользователь user3 и не имеет никаких прав на переопределение объекта, защёлка library cache pin на объект LBO всё равно им приобретается. Проверка прав на доступ к объекту проводится, по-видимому, гораздо позже. В конечном счёте, такая ситуация может привести к блокировке доступа к объекту, правда только лишь на время его закрепления другими сеансами. Но осознание того, что любой пользователь, не смотря на имеющиеся привилегии, может пусть, хотя и кратковременно заблокировать доступ к объекту, довольно неприятно.

Если такие ситуации всё же случились, как диагностировать возникающие проблемы? Попробуем прояснить этот вопрос.

Диагностика

В предыдущих материалах мы рассматривали возможность просмотра списка закреплений объекта LBO в различных режимах. Но такой вид наблюдения не очень подходит для повседневного мониторинга сеансов ожидающих защёлку library cache pin. Гораздо удобнее будет пользоваться для этого динамическим представлением v$session. Если отслеживать события ожиданий активных сеансов, которые отображаются в этом представлении, можно без труда обнаружить длительное ожидание захвата защёлки library cache pin каким либо сеансом. Выполним, к примеру, во время нашего теста с монопольным режимом захвата защёлки следующий запрос:

В результатах запроса мы видим, что сеанс 26 ожидает захвата защёлки library cache pin. Из дополнительных параметров этого ожидания мы можем узнать много интересующей нас информации. Так, первый параметр ожидания, отображаемый в столбце p1raw, покажет нам дескриптор объекта библиотечного кэша, защелку на который пытается получить сеанс. Если нам требуется узнать имя этого объекта, мы можем сделать дополнительный запрос к динамической таблице x$kglob по значению этого дескриптора:

После того, как мы более или менее узнали дополнительную информацию о защёлке, монопольного захвата которой ожидает сеанс, можно принимать решение, продолжать ли дальше ожидание или уничтожить ожидающий сеанс. Если действие, которое должно выполниться этим сеансом всё же необходимо, можно попробовать уничтожить сеансы, из-за которых происходит ожидание. Для этого достаточно вывести список всех закреплений объекта LBO в разделяемом режиме из динамической таблицы x$kglpn, как мы это делали ранее, и провести уничтожение выбранных сеансов.

Выводы

Подведём небольшой итог изучения защёлки library cache pin и её ожидания:

  • Защёлка приобретается для защиты внутренних структур объекта библиотечного кэша, конкретней - блоков данных этого объекта (кучи памяти).
  • Защёлка имеет два режима: разделяемый и монопольный. Разделяемый режим предназначен только для чтения структур объекта библиотечного кэша и защёлка в таком режиме может приобретаться несколькими сеансами.
  • Разделяемый режим защёлки не даёт другому сеансу захватить защёлку в монопольном режиме. Этот сеанс будет ждать освобождения защёлки, столько сколько нужно. В этом ожидании не используется механизм засыпания (wait posting) защёлки.
  • Монопольный режим защёлки всегда приобретается в случаях изменения структур объекта библиотечного кэша, при этом не важно, имеются ли у сеанса привилегии на доступ к этому объекту или нет.
  • Сеанс, ожидающий закрепление объекта библиотечного кэша в монопольном режиме, будет препятствовать получению другими сеансами доступа к этому объекту. Запрет осуществляется на уровне получения защёлки library cache lock;

В заключение хочется привести некоторые моменты, касающиеся библиотечного кэша и защёлки library cache pin которые не рассматривались в статье, но встречались в процессе изучения материала. Некоторые из них спорны или отсутствуют в старших версиях Oracle, поэтому я не стал включать их в основной тест статьи и оставляю право верить этим заметкам самому читателю:


Для снижения конкуренции (событие ожидания "library cache: mutex X") за объекты в библиотечном кэше предназначена процедура MARKHOT системного пакета DBMS_SHARED_POOL. Данная процедура производит размножение (создает копии) указанного объекта (пакета, процедуры, курсора и т.д.) в библиотечном кэше, и тем самым снижает конкуренцию за мьютексы.

Недавно, общаясь с одним очень крупным заказчиком, высоконагруженные БД которого работают на больших Hi-End серверах, была затронута тема использования технологии Edition Base Redefinition для безостановочного обновления приложений. Этот заказчик очень активно использует процедуру MARKHOT в своих БД.
Основной вопрос, который волновал заказчика, заключался в поддержке процедурой MARKHOT технологии Edition Base Redefiniton: работает ли вышеописанное размножение объектов в кэше для разных версий (editions) объекта?
В явном виде в процедуре MARKHOT не фигурирует версия объекта (edition в которой он был определен). Давайте разберемся в этом вопросе. Поскольку в данный момент заказчик использует Oracle Database 11.2.0.3, проверим именно на этой версии СУБД.

Создадим отдельного пользователя и новую версию (edition), выдав права на использование этой версии и включив потенциальную версионируемость в вновь созданной схеме: Далее подключимся к БД под вновь созданным пользователем test_markhot и создадим две версии одной и той же процедуры: в версиях ORA$BASE и version_2 соответственно. Далее, для удобства откроем отдельную сессию под пользователем SYS, и проверим наличие нашей процедуры MYPROC в библиотечном кэше помеченной как "горячий" объект, то есть для которой был выполнен вызов DBMS_SHARED_POOL.MARKHOT: Все верно: поскольку мы еще не вызывали процедуру MARKHOT, - процедуры MYPROC в списке "горячих" нет. Для удобства сохраним вышеприведенный запрос в файле hotlist.sql
Находясь под пользователем SYS в версии ORA$BASE пометим нашу нашу процедуру как горячую: Как Вы видите, - наша процедура MYPROC была помечена как горячая. При этом вызов dbms_shared_pool.markhot создал handler на процедуру MYPROC в библиотечном кэше.
Попробуем выполнить процедуру MYPROC в версии ORA$BASE (под владельцем - пользователем test_markhot): и проверим содержимое библиотечного кэша (в другой сессии конечно, - под пользователем SYS): Пока все работает штатно: в кэше были создана копия нашей процедуры. Теперь собственно самая главная часть нашего эксперимента: пометим нашу процедуру как горячую в версии VERSION_2 и проверим содержимое library cache. В кэше появился новый объект помеченный как "горячий"! Вызовем нашу процедуру myproc в версии VERSION_2: и еще раз проверим содержимое library cache: Теперь в кэше началось размножение процедуры myproc новой версии!

Процедура dbms_shared_pool.markhot, которая предназначена для установки метки на горячие объекты в library cache, полностью учитывает версии объектов и ее можно совместно использовать с технологией обновления приложений на лету - Edition Base Redefinition!

Хочу выразить большую благодарность Сергею Томину за помощь в подготовке этого примера!

1 комментарий:

Мы активно используем EBR в своем приложении (не OeBS).

Не понятно, из каких представлений получить информацию о том, к какой редакции относится объект library cache.
Конкретно, в x$kglob и других я пока не нашел подходящих полей.
Вместе с тем, если сделать dump library cache, то там редакции видны:
ObjectName: Name=VERSION_2.TEST_MARKHOT.MYPROC
ObjectName: Name=TEST_MARKHOT.MYPROC

Не подскажите, из X$ как можно получить информацию о принадлежности объекта library cache к редакции?

Last updated on JUNE 22, 2021

Выполнение, блокировка и закрепление

Предположим, что мне требуется выполнить запрос, для которого в библиотечном кэше уже имеется подходящий дочерний курсор с готовым планом выполнения. В этой ситуации все равно придется выполнить некоторую работу, чтобы найти, подготовить и выполнить его. В худшем случае я просто передам в Oracle фрагмент текста с инструкцией. В ходе обработки текста Oracle выполнит синтаксический анализ, вычислит хэш-значение текста, найдет в библиотечном кэше соответствующий хэш-блок, просмотрит хэш-цепочку, найдет дочерний курсор, проверит его соответствие инструкции и выполнит план. В этой точке вполне может обнаружиться, что план был удален из памяти, и тогда Oracle вынужден будет повторно оптимизировать инструкцию – нарастить статистику parse count (hard) сеанса и увеличить Misses in library cache during execute, если в этот момент выполняется трассировка. Узнать, как часто такое случается, можно с помощью статистики v$librarycache.reloads для строки ‘SQL AREA’.

На разных этапах описываемого процесса необходимо также позаботиться о проблемах конкуренции. Следуя традиционным путем, можно было бы приобрести и удерживать защелку хэш-цепочки в библиотечном кэше в течение всего времени поиска подходящего курсора. Можно было бы создать блокировку библиотечного кэша (блокировку KGL) для кучи 0, чтобы гарантировать, невозможность выталкивания ее из памяти, или создать закрепление библиотечного кэша (закрепление KGL), чтобы исключить возможность выталкивания плана (Куча 6 / SQL Area) до окончания выполнения запроса. Однако, как вы помните, все эти защелки, связанные с библиотечным кэшем, появившиеся в 10g – такие как library cache pin allocation – закрепления и блокировки представляют собой небольшие фрагменты памяти, размещаемые в разделяемом пуле. То есть, для закрепления курсора требуется приобрести несколько защелок и выделить память. Это довольно дорого, особенно если к скорости выполнения предъявляются жесткие требования.

В Oracle реализовано множество стратегий для минимизации «инфраструктурных» затрат на поиск и выполнение инструкций. В 10.2, например, сеансы получили возможность кэширования закреплений KGL (скрытый параметр _session_kept_cursor_pins), подобную возможности кэширования дескрипторов буферов. В 11g блокировки и закрепления KGL, которые может приобрести сеанс, размещаются страницами (а не по отдельности), за счет чего снижается число запросов на выделение памяти и ее фрагментация. Помимо этих последних нововведений, сохраняется также возможность удерживать курсоры открытыми, что по сути означает удержание блокировки библиотечного кэша в течение всего времени и закрепление Кучи 0 в памяти (хотя это не закрепление KGL, в данном случае под «закреплением» подразумевается флаг, установленный диспетчером кучи). Это – функция структуры x$kgllk, официально доступной через v$open_cursor. Имеется также кэш курсоров сеанса (session cursor cache), дающий библиотечным функциям возможность удерживать курсоры открытыми, даже когда пользовательское приложение не требует этого.

Можно сделать еще один шаг вперед, сохранив закрепление (x$kglpn) в куче 6 – план выполнения – установкой параметра cursor_space_for_time в значение true (не самое лучшее решение, если оно не основано на веских причинах, потому что побочный эффект закрепления кучи 6, когда уже закреплена куча 0, ограничивает число незакрепленных воссоздаваемых объектов, которые можно освободить в случае нехватки памяти). Однако, параметр cursor_space_for_time признан устаревшим в 11g, возможно потому, что в Oracle решили, что этот параметр был решением проблемы, которая отсутствует в новейшей стратегии закрепления (реализация которой начата в 10g и близка к завершению в 11g): мьютексах.

Читайте также: