Oracle Magazine - Русское издание (Октябрь 2007)

Сергей Маркеленков,
компания РДТЕХ
http://www.rdtex.ru, markelenkov@rdtex.ru

Oracle: малоизвестные факты. Часть 2.
Когда транзакция считается зафиксированной?

Попробуйте ответить на такой с виду простой вопрос: когда транзакция считается зафиксированной?

Уверен, что большинство из вас ответят приблизительно следующее: “когда после выдачи сеансом команды commit (явно или неявно в рамках выполнения DDL-команды и т.п.) вся журнальная информация транзакции и маркер ее фиксации будут процессом LGWR записаны на диск, а сеанс после этого получит управление”. Да, по сути все правильно. Однако процесс фиксации – это не атомарная операция. Несмотря на то, что этот процесс почти всегда весьма быстрый, во время выполнения фиксации транзакции серверному процессу требуется последовательно произвести ряд необходимых действий.

Если обратиться к учебнику семинара “O9iSTM-Space and Transaction Management” (это один из пяти семинаров для экспертов по СУБД Oracle, читаемых в Учебном центре компании РДТЕХ. На этих семинарах рассматриваются различные недокументированные возможности, внутренние структуры Oracle, алгоритмы выполнения транзакций, тонкая настройка экземпляра, разрешение аварийных ситуаций и т.п.), то там можно найти краткое описание того, что делает серверный процесс при фиксации транзакции:

Таблица 1. Что делает серверный процесс при фиксации транзакции

  1. Finds an SCN value
  2. Updates the transaction table
  3. Puts the current undo block into the free blocks pool (under some conditions)
  4. Creates a commit record in the redo log buffer
  5. Flushes the redo log buffer to disk (for durability)
  6. Releases locks held on rows and tables

Надо отметить, что здесь перечислены не все действия. Во время фиксации транзакции еще производится быстрая очистка блоков данных, обновление материализованных представлений, созданных с опцией refresh on commit, проверка отложенных ограничений (deferred constraints), очистка временных таблиц, созданных с опцией on commit delete rows и т.п. Но в контексте обсуждаемой темы эти нюансы не имеют значения, поэтому остановимся только на тех действиях, которые перечислены в таблице.

Таким образом, пока не будут выполнены все вышеуказанные действия, сеанс, выполняющий фиксацию, будет считать, что транзакция им еще не зафиксирована. Обратите внимание на то, что сброс журнального буфера на диск (который, разумеется, выполняется процессом LGWR, а не серверным процессом) указан пятым пунктом, а вторым пунктом идет обновление слота в таблице транзакций заголовка сегмента отмены. Именно по содержимому этого слота другие сеансы определяют, зафиксирована ли данная транзакция или нет.

То есть, возможна ситуация, когда сеанс, фиксирующий транзакцию, выполнил обновление слота в таблице транзакций (пункт 2 Таблицы 1), но не успел еще выполнить все остальные действия, ключевым из которых является именно запись в журнальный буфер маркера фиксации и сброс процессом LGWR всей журнальной информации транзакции и этого маркера в оперативные журнальные файлы (пункт 5 Таблицы 1). В этом промежутке времени для сеанса-хозяина транзакция будет считаться еще не зафиксированной. Но все другие сеансы для определения факта фиксации будут обращаться к соответствующему слоту в таблице транзакций (разумеется, если фиксирующим сеансом уже была выполнена быстрая очистка измененного блока (ITL-слота), либо кем-то другим была выполнена отложенная очистка этого блока, то необходимости обращения к этому слоту уже не будет – признак фиксации будет стоять в соответствующем ITL-слоте блока данных) и увидят там признак того, что транзакция уже зафиксирована. На самом деле, пока не закончится выполнение пункта 3, с блока заголовка сегмента отмены не будет снят exclusive pin и ни один другой серверный процесс не сможет прочитать этот блок. Поэтому для всех других сеансов транзакция будет считаться зафиксированной сразу после выполнения фиксирующим сеансом пункта 3 и снятия им exclusive pin с заголовка сегмента отмены.

Давайте рассмотрим пример. Этот тест я проводил на нескольких последних версиях Oracle и на нескольких разных платформах – результат одинаков. При тестах в версии Oracle 10.2 использовался традиционный, используемый по умолчанию механизм фиксации транзакций – параметр инициализации COMMIT_WRITE был установлен в значение ’IMMEDIATE,WAIT’.

Первый сеанс выполняет обновление созданной тестовой таблицы командой update, но пока не фиксирует транзакцию:

--Session1
Connected to:
Oracle9i Enterprise Edition Release 9.2.0.8.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.8.0 - Production

SQL> set sqlprompt "_USER'-Session 1'> " time on
18:06:40 SCOTT-Session 1> select sid from v$mystat where rownum=1;

       SID
----------
        15

18:06:47 SCOTT-Session 1> create table t(n number);

Table created.

18:06:52 SCOTT-Session 1> insert into t values(1);

1 row created.

18:06:56 SCOTT-Session 1> commit;

Commit complete.

18:06:59 SCOTT-Session 1> select * from t;

         N
----------
         1

18:07:05 SCOTT-Session 1> update t set n=2;

1 row updated.

18:07:10 SCOTT-Session 1>
 

Второй сеанс не видит изменений, сделанных командой update первого сеанса. Пока все как обычно:

--Session2
Connected to:
Oracle9i Enterprise Edition Release 9.2.0.8.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.8.0 - Production

SQL> set sqlprompt "_USER'-Session 2'> " time on
18:07:18 SCOTT-Session 2> select * from t;

         N
----------
         1

18:07:39 SCOTT-Session 2>
 

Сеанс 3 “замораживает” работу процесса LGWR перед выдачей первым сеансом команды commit с целью приостановить фиксацию транзакции на пункте 5 Таблицы 1 – на фазе ожидания сброса информации из журнального буфера на диск:

--Session3
Connected to:
Oracle9i Enterprise Edition Release 9.2.0.8.0 - Production
With the Partitioning, OLAP and Oracle Data Mining options
JServer Release 9.2.0.8.0 - Production

SQL> set sqlprompt "_USER'-Session 3'> " time on
18:07:51 SYS-Session 3> select pid,spid,name from v$process p,v$bgprocess b
18:07:59   2  where p.addr=b.paddr and b.name='LGWR';

       PID SPID         NAME
---------- ------------ -----
         4 2816         LGWR

18:07:59 SYS-Session 3> oradebug setorapid 4
Windows thread id: 2816, image: ORACLE.EXE
18:08:05 SYS-Session 3> oradebug suspend
Statement processed.
18:08:10 SYS-Session 3> 
 

Сеанс 1 фиксирует транзакцию, выполняя поочередно все действия, перечисленные в Таблице 1. Дойдя до пункта 5, он ждет сброса процессом LGWR маркера фиксации транзакции из журнального буфера на диск (ожидание может возникнуть и раньше – на п.4 в случае, если в журнальном буфере нет свободного места):

--Session3
18:07:10 SCOTT-Session 1> commit;
-- сеанс "завис"
 

Так как управление Сеансу 1 еще не передано, он считает транзакцию еще не зафиксированной. Несмотря на то, что маркер фиксации не сброшен на диск, и что Сеанс 1 не получил подтверждения фиксации транзакции, другой сеанс (Сеанс 2) видит сделанные командой update первого сеанса изменения. Причина этого заключается в том, что Сеанс 1 уже обновил слот своей транзакции в заголовке сегмента отмены и проставил в него признак того, что транзакция зафиксирована. То есть, все другие сеансы с этого момента считают транзакцию Сеанса 1 зафиксированной:

--Session2
18:08:22 SCOTT-Session 2>
18:08:23 SCOTT-Session 2> select * from t;

         N
----------
         2

18:08:23 SCOTT-Session 2>
 

Видно, что Сеанс 1 ждет сброса журнального буфера на диск. Кстати, если вы обратите внимание на столбец SECONDS_IN_WAIT представления V$SESSION_WAIT, то увидите, что он не обновляется. Причина этого в том, что ответственный за его периодическое обновление – процесс LGWR, который мы “заморозили”:

--Session3
18:08:35 SYS-Session 3> 
   select sid,substr(event,1,30),seconds_in_wait,state from v$session_wait 
          where sid=15;

       SID SUBSTR(EVENT,1,30)             SECONDS_IN_WAIT STATE
---------- ------------------------------ --------------- ---------------
        15 log file sync                                0 WAITING

18:08:50 SYS-Session 3> 
   select sid,substr(event,1,30),seconds_in_wait,state from v$session_wait 
          where sid=15;

       SID SUBSTR(EVENT,1,30)             SECONDS_IN_WAIT STATE
---------- ------------------------------ --------------- ---------------
        15 log file sync                                0 WAITING

18:08:52 SYS-Session 3>
 

После аварийного сбоя экземпляра мы убеждаемся, что, как и положено, изменений, сделанных командой update, в базе данных нет, так как маркер фиксации так и не был записан в оперативные журнальные файлы:

--Session3
18:08:54 SYS-Session 3> shutdown abort
ORACLE instance shut down.
18:09:06 SYS-Session 3> startup
ORACLE instance started.

Total System Global Area   68230420 bytes
Fixed Size                   453908 bytes
Variable Size              50331648 bytes
Database Buffers           16777216 bytes
Redo Buffers                 667648 bytes
Database mounted.
Database opened.
18:09:24 SYS-Session 3> select * from scott.t;

         N
----------
         1

18:09:26 SYS-Session 3>
 

Какие же выводы можно сделать из этого примера? Получается, что Сеанс 2 увидел “не полностью зафиксированные” данные – маркер фиксации транзакции им еще не был записан в оперативные журнальные файлы, что означает откат такой транзакции в случае сбоя экземпляра и следующего за ним восстановления. Чем это может быть чревато?

  • Если Сеанс 2 во время своей работы получит такие незафиксированные данные, как-то их обработает и попытается записать в эту либо другую базу данных (здесь вступит в силу механизм двухфазной фиксации), то есть будет их обрабатывать транзакционно, то в этом случае он тоже зависнет. То есть, при транзакционной обработке все данные будут согласованы и никакого рассогласования не будет.
  • Если же Сеанс 2 будет обрабатывать данные не транзакционно, например, формировать отчет при помощи записи в spool-файл, в результате в отчете могут оказаться данные незафиксированных транзакций (аналогичная ситуация наблюдается при фиксациях транзакций внутри PL/SQL-блока: пока не осуществлен выход из этого блока, маркер фиксации может быть еще не записан на диск. То есть, новая возможность commit write batch nowait, появившаяся в Oracle 10.2, уже неявно использовалась внутри PL/SQL в предыдущих версиях Oracle). Как это, может быть, и ни странно звучит, но, чтобы быть абсолютно уверенным в том, что им прочитаны зафиксированные данные, Сеанс 2 должен начать любую свою транзакцию и зафиксировать ее (в Oracle 10.2 обязательно с параметром commit_write=’wait’). Только после того, как ему вернется управление, можно на 100% гарантировать, что все данные, прочитанные им ранее, зафиксированы.

На первый взгляд может показаться, что мой пример надуманный, и что на практике подобное повториться не может. Это не совсем так. Во-первых, существует небольшая вероятность описанной ситуации даже при нормальной работе базы данных. Во-вторых, такая ситуация легко может возникнуть и без искусственной “заморозки” процесса LGWR. Например, многие знают, что когда база данных работает в режиме ARCHIVELOG, и по какой-то причине журнал, в который хочет переключиться процесс LGWR, не заархивирован (не запущены процессы архивирования, нет места под архивные журналы и т.п.), то все сеансы, производящие модификацию данных, “зависают”. Неопытные администраторы зачастую выполняют в таком случае перезагрузку экземпляра, ожидая таким образом снять проблему. То есть практически один в один происходит то, что продемонстрировано в моем примере:

  • Сеанс 1 обновил данные, но пока не зафиксировал изменения.
  • Другие сеансы сгенерировали журнальную информацию и зависли, т.к. LGWR не освобождает журнальный буфер из-за того, что не может писать в незаархивированный журнал.
  • Проходит какое-то время, Сеанс 1 выполняет команду commit и тоже зависает.
  • Сеанс 2, выполняет в это время построение отчета, при этом не делает никаких изменений в базе данных, а результаты записывает, например, в какой-то файл на компьютер клиента. При построении отчета он, среди прочего, читает данные, измененные Сеансом 1 в последней транзакции и уже зафиксированные с точки зрения Сеанса 2, но еще не зафиксированные с точки зрения Сеанса 1.
  • Администратор, обнаружив “подвисание” экземпляра и не поняв его причину, выполняет его перезапуск (командами shutdown abort или startup force, т.к. команда shutdown с другими параметрами в данном случае не проходит). Либо просто происходит аварийное завершение работы экземпляра.
  • После старта экземпляра будет произведен откат последней транзакции Сеанса 1, т.к. в журнальном файле маркера фиксации этой транзакции нет.
  • Если теперь, сразу после рестарта экземпляра, запустить снова аналогичный отчет, будут получены другие данные, т.к. в первый раз отчет был сформирован на основе “не полностью зафиксированных” данных.

Таким образом, если используется традиционный механизм фиксации транзакций (commit_write=’wait’ в Oracle 10.2 или используется Oracle более ранних версий; а также фиксация производится не внутри PL/SQL-блока), правильный ответ на вопрос, поставленный в заглавии статьи, звучит приблизительно так:

  • для сеанса, выполняющего фиксацию транзакции, транзакция считается зафиксированной после записи маркера фиксации в оперативные журнальные файлы и последующей передачи управления сеансу;
  • для всех других сеансов – после обновления сеансом, производящим фиксацию, слота транзакции в заголовке сегмента отмены и снятия с блока заголовка сегмента отмены exclusive pin.

Что произойдет, если используется Oracle версии 10.2 и выше, и фиксация транзакции производится с параметром ‘nowait’? Думаю, что это достаточно очевидно, и читатели смогут легко об этом догадаться самостоятельно.

E-mail this page