20июн
Поиск блока в буферном кэше, cache buffers chains. Параметры _db_block_hash_buckets , _db_block_hash_latches.
Предположим, что нам нужно провести определенную работу над конкретным блоком. Из словаря базы данных (Data Dictionary) мы знаем его адрес (DBA – data block address) – file#+block#. Есть ли этот блок в кэше, или ораклу нужно читать его с диска? Как такую процедуру организовал ORACLE? Поделюсь своими представлениями об этом.
Все буфера в буферном кэше распределены по двунаправленным связным спискам cache buffer chains (hash chains, Hash Buckets). Это утверждение справедливо и для пустых, неиспользуемых на данный момент буферов. Эти списки предназначены для решения вопроса: «Есть ли необходимый блок в кэше?». В какой именно список попадет буфер, зависит от хэш-таблицы, которая представляет собой массив hash bucket-ов. В этой таблице по каждому hash bucket хранится адрес начала списка «cache buffer chain», который содержит указатели на заголовки буферов или нули, если список пустой. Эта таблица создается при открытии базы данных – при открытии базы создаются hash buckets.
Чем длиннее список по определенному бакету, тем продолжительнее поиск по нему. Поэтому хорошо когда списков(соответственно записей в хэш-таблице) много. Количество записей в этой таблице по умолчанию равно prime(db_block_buffers*2) (до версии 8 было равно db_block_buffers/4). Использование простого числа объясняется желанием избежать аномалий при использовании хэш-функции для построения хэш-таблицы. Количество бакетов значительно увеличено, чтобы уменьшить слишком большое времени ожидания освобождения защелок при поиске блока, а также уменьшения количества CR блоков в конкретной цепочке. Значение параметра инициализации _db_block_hash_buckets определяет точный размер этой таблицы. Смотрите:
select KSPPINM,KSPPDESC,KSPPSTVL,KSPPSTDVL,KSPPSTDF from X$KSPPSV a,x$ksppi b
where a.indx=b.indx and KSPPINM='_db_block_hash_buckets'
select KSPPINM,KSPPDESC,KSPPSTVL,KSPPSTDVL,KSPPSTDF from X$KSPPSV a,x$ksppi b
where a.indx=b.indx and KSPPINM like '_db_block_buffers'
Во время поиска и манипулирования списки hash chains защищаются защелками cache buffers chains latch. Их количество равно по умолчанию степени двойки (параметр _db_block_hash_latches):
for small caches is less than 2052 buffers
2 ^ trunc(log(2, db_block_buffers - 4) - 1)
for larges caches is bigger than 131075 buffers
2 ^ trunc(log(2, db_block_buffers - 4) - 6)
If db_block_buffers is between 2052 and 131075 buffers,
1024 latches .
Значение этого параметра можно посмотреть таким образом:
select power(2, trunc(log(2,(select KSPPSTVL from X$KSPPSV a,x$ksppi b where a.indx=b.indx and KSPPINM like '_db_block_buffers') - 4) - 6)) from dual
select KSPPINM,KSPPDESC,KSPPSTVL,KSPPSTDVL,KSPPSTDF from X$KSPPSV a,x$ksppi b
where a.indx=b.indx and KSPPINM like '_db_block_hash_latches'
Из формул видно, что при увеличении буфера растет количество защелок. (Количество процессоров на сервере не меняется, поэтому конкуренция за защелки cache buffers chains все равно будет возрастать)
Как же формируются hash buckets?
Для каждого буфера, исходя из DBA (10 бит - file#, 22 бита - block#) содержащегося в нем блока, рассчитывается индекс бакета в хэш-таблице или другими словами номер записи в хэш-таблице по следующей формуле (на самом деле формула сложнее):
bucket#=mod(DBA,_db_block_hash_buckets)
Исходя из полученного значения, блок попадает в соответствующую цепочку. Эта процедура наглядно изображена на картинке (для примера используется mod 4):

Все буферы (а точнее, указатели на буферы), попавшие в один bucket организованы в списки (cachе buffer chains, CBC). В общем случае в списке находятся
- нужный нам блок
- CR-блоки (consistent read) (ограничено параметром _db_block_max_cr_dba , т.е. теоретически длина списка не превышает это значение)
- блок с другим DBA, но по которому был получен такой же bucket# (такая коллизия при больших кэшах, когда есть много списков, возможна, но маловероятна).
Эти списки защищены защелками CBC latch, которые отвечают за один или более бакетов.

Как осуществляется поиск нужного блока?
- Рассчитываем bucket# по формуле (см. выше)
- По значению bucket# берем защелку
- По значению bucket# попадаем на нужную цепочку
- В цикле её просматриваем:
Loop until end of chain or buffer found
If buffer.dba = requested.dba & buffer.class = requested.class
Buffer found, exit loop
- Результат работы цикла - два варианта:
- Блок найден: в buffer header'е устанавливаем pin (закрепляем блок за собой) и работаем с ним. Если же буфер используется, на него уже очередь, то защелка освобождается, для целей согласованности блок клонируется и используется. Для использования в несовместимом режиме записываемся в очередь, освобождаем защелку, наращиваем статистику 'buffer busy waits', засыпаем в ожидании.
- Блок не найден: освобождаем защелку и переходим к чтению блока.
В таблице X$BH вся эта кухня отображена таким образом:

http://oraclemaniacs.blogspot.com/2007/01/oracle.html
http://www.dbanotes.net/Books/oracle8i_internal_services_for_waits,_latches,_locks.pdf
http://www.fors.com/velpuri2/PERFORMANCE/dbwr.pdf