Каждый кто более или менее знаком с Oracle знает, что в этой БД нет блокировок на чтение. Достигается это за счет мультиверсионности данных. Более подробно об этом можно почитать в официальной документации.
В приведенной доке можно найти следующее утверждение:
В этой статье я бы хотел рассказать об одной особенности, когда ридеры будут блокировать друг друга.
Не буду тянуть и скажу сразу, ловить мы будем следующего зверя: enq: RC — Result Cache: Contention – внутренняя блокировка, использующая механизм очереди, для координации конкурентного доступа к кэшу. Для начала нам понадобится создать одну таблицу и функцию.
Далее в двух разных сессиях почти параллельно мы должны запустить практически один и тот же скрипт. Сначала запускаем скрипт «Сессия №1», потом переключаемся в другое окно и запускаем второй скрипт «Сессия №2». В общем, надо запустить второй скрипт до того как в первом истечет время сна.
В результате должо получиться следующее:
Первая сессия выполнится чуть более чем за 10 сек и напечатает в output
1
2
wait_time: 0
Вторая сессия тоже будет выполняться около 10 сек и напечатает в output
1
2
wait_time: 8077256
Естественно, последнее число будет у всех разное, оно показывает время ожидания блокировки enq: RC — Result Cache: Contention в микросекундах. Т.е. в моем случае ожидание было 8 сек. Что же произошло?
Когда первая сессия начинает извлекать данные из курсора, она формирует кэш по этому sql-запросу. После того как 2 строки были извлечены, сессия впадает в спячку. В это время вторая сессия начинает читать те же самые строки и т.к. кэш сформирован, то сессия пытается читать результат именно оттуда. И в этот самый момент она натыкается на блокировку. Казалось бы, кэш есть, чего блокируют-то? Дело в том, что первая сессия еще «читает» данные из курсора! Хоть и обе строки уже прочитаны, но еще не стало понятно, что курсор прочитал все что должен был. Это произойдет только после третьего fetch, тогда станет ясно что курсору больше нечего выдавать и блокировка снимется, а до этого момента первая сессия формирует кэш и держит все остальные сессии.
Если инвалидировать кэш и повторить эксперимент заново, но поставить sleep после третьего fetch, то скорее всего блокировку поймать не удастся либо будет совсем маленькое значение времени ожидания, т.к. теперь первая сессия не выполняет ничего долговременного во время формирования кэша.
Механизм result_cache был представлен как новая фича в 11 версии бд. Сам механизм достаточно специфичен, если в нем хорошо разобраться, то, возможно, окажется, что не так уж и много ситуаций есть в вашем проекте, где это действительно можно использовать. Поэтому будьте осторожны прежде чем использовать что-то новое не разобравшить до конца. Недавно был релиз 12 версии бд уже с другими новыми возможностями и поэтому поводу была интересная статья здесь же, на хабре.
В приведенной доке можно найти следующее утверждение:
Readers and writers of data do not block one another
В этой статье я бы хотел рассказать об одной особенности, когда ридеры будут блокировать друг друга.
Не буду тянуть и скажу сразу, ловить мы будем следующего зверя: enq: RC — Result Cache: Contention – внутренняя блокировка, использующая механизм очереди, для координации конкурентного доступа к кэшу. Для начала нам понадобится создать одну таблицу и функцию.
Предварительный скрипт
create table cached_table as
select level id from dual
connect by level < 5;
create or replace function get_event_time return number
is
l_wait_time number;
begin
select nvl(max(se.time_waited_micro), 0)
into l_wait_time
from v$session_event se
where se.sid = (select sid from v$mystat where rownum < 2)
and se.event = 'enq: RC - Result Cache: Contention';
return l_wait_time;
end;
Далее в двух разных сессиях почти параллельно мы должны запустить практически один и тот же скрипт. Сначала запускаем скрипт «Сессия №1», потом переключаемся в другое окно и запускаем второй скрипт «Сессия №2». В общем, надо запустить второй скрипт до того как в первом истечет время сна.
Сессия №1
declare
cursor cur is select /*+ result_cache */ * from cached_table where id between 1 and 2;
rec cur%rowtype;
l_old_event_wait_time number;
l_new_event_wait_time number;
begin
l_old_event_wait_time := get_event_time;
open cur;
fetch cur into rec;
dbms_output.put_line(rec.id);
fetch cur into rec;
dbms_output.put_line(rec.id);
dbms_lock.sleep(10);
fetch cur into rec;
close cur;
l_new_event_wait_time := get_event_time;
dbms_output.put_line('wait_time: '||to_char(l_new_event_wait_time-l_old_event_wait_time));
end;
Сессия №2
declare
cursor cur is select /*+ result_cache */ * from cached_table where id between 1 and 2;
rec cur%rowtype;
l_old_event_wait_time number;
l_new_event_wait_time number;
begin
l_old_event_wait_time := get_event_time;
open cur;
fetch cur into rec;
dbms_output.put_line(rec.id);
fetch cur into rec;
dbms_output.put_line(rec.id);
--dbms_lock.sleep(10);
fetch cur into rec;
close cur;
l_new_event_wait_time := get_event_time;
dbms_output.put_line('wait_time: '||to_char(l_new_event_wait_time-l_old_event_wait_time));
end;
В результате должо получиться следующее:
Первая сессия выполнится чуть более чем за 10 сек и напечатает в output
1
2
wait_time: 0
Вторая сессия тоже будет выполняться около 10 сек и напечатает в output
1
2
wait_time: 8077256
Естественно, последнее число будет у всех разное, оно показывает время ожидания блокировки enq: RC — Result Cache: Contention в микросекундах. Т.е. в моем случае ожидание было 8 сек. Что же произошло?
Когда первая сессия начинает извлекать данные из курсора, она формирует кэш по этому sql-запросу. После того как 2 строки были извлечены, сессия впадает в спячку. В это время вторая сессия начинает читать те же самые строки и т.к. кэш сформирован, то сессия пытается читать результат именно оттуда. И в этот самый момент она натыкается на блокировку. Казалось бы, кэш есть, чего блокируют-то? Дело в том, что первая сессия еще «читает» данные из курсора! Хоть и обе строки уже прочитаны, но еще не стало понятно, что курсор прочитал все что должен был. Это произойдет только после третьего fetch, тогда станет ясно что курсору больше нечего выдавать и блокировка снимется, а до этого момента первая сессия формирует кэш и держит все остальные сессии.
Если инвалидировать кэш и повторить эксперимент заново, но поставить sleep после третьего fetch, то скорее всего блокировку поймать не удастся либо будет совсем маленькое значение времени ожидания, т.к. теперь первая сессия не выполняет ничего долговременного во время формирования кэша.
Механизм result_cache был представлен как новая фича в 11 версии бд. Сам механизм достаточно специфичен, если в нем хорошо разобраться, то, возможно, окажется, что не так уж и много ситуаций есть в вашем проекте, где это действительно можно использовать. Поэтому будьте осторожны прежде чем использовать что-то новое не разобравшить до конца. Недавно был релиз 12 версии бд уже с другими новыми возможностями и поэтому поводу была интересная статья здесь же, на хабре.