ORA-08177 с запертым столом

После

create table l (id int primary key, val int);
insert into l values (0, 0);

и инициализации двух сеансов оракула

alter session set isolation_level=serializable;

следующая последовательность приводит к ORA-08177: can't serialize access for this transaction.

  session 1                           session 2                          
 -----------------------------------+-----------------------------------
  lock table l in exclusive mode                                         
 -----------------------------------+-----------------------------------
                                      lock table l in exclusive mode     
 -----------------------------------+-----------------------------------
  update l set val = 1 where id = 0   [blocked]                          
 -----------------------------------+-----------------------------------
  commit                              [blocked]                          
 -----------------------------------+-----------------------------------
                                      update l set val = 2 where id = 0  
 -----------------------------------+-----------------------------------
                                      [ORA-08177]                        

Почему? Это выглядит совершенно серийно для меня.

Ответ 1

Oracle, в отличие, скажем, SQL Server, работает оптимистично в режиме SERIALIZABLE, то есть он не принимает блокировок. Если есть параллельный доступ во время транзакции, Oracle просто потерпит неудачу, а не повторит попытку.

Кроме того, Oracle не только выкинет это исключение на фактический одновременный доступ, но и если что-то, что затронуто во время транзакции, также было изменено другим потоком с начала транзакции, независимо когда в ходе транзакции были получены данные.

Ответ 2

Когда сериализуемая транзакция пытается изменить данные, которые другая транзакция (сериализуемая или нет) уже была изменена (или даже данные, которые находятся рядом с данными, другая транзакция уже изменилась в том же блоке данных) после того, как эта сериализуемая транзакция началась, вы 'получит сообщение об ошибке ORA-08177. Когда вы устанавливаете уровень изоляции транзакции на serializable, необходимо иметь в виду две важные вещи:

  • Последовательная транзакция может изменить запись только в том случае, если изменения к этой записи, сделанные другой транзакцией (транзакциями), были уже совершены к моменту начала сериализуемой транзакции (а не инструкции).
  • Простая согласованность расширяется до уровня транзакции, а не уровня оператора, так как уровень изоляции транзакции равен read committed (уровень изоляции транзакции по умолчанию).

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

Простой пример, который приводит к ошибке ORA-08177:

-- /* test table */
SQL> create table t1(col) as 
       select 3 from dual;
Table created.

-- sqlplus session #1                     sqlplus #session 2
----------------------------------------------------------------------------
SQL> alter session 
       set isolation_level=serializable;

                                          SQL> alter session set
                                               isolation_level=serializable;

/*
   You start serializable transaction by locking the t1 table in 
   exclusive mode. 

 */

-- serializable transaction #1

SQL> lock table t1 in exclusive mode;    -- serializable transaction # 2
                                         -- snapshot of t1 has been acquired
SQL> update t1 set col = 5               SQL> lock table t1 in exclusive mode;
      where col = 3; 
                                         -- this update does not see changes 
1 row updated.                           -- transaction #1 has already made 
                                         -- to t1 row where col = 3. 
                                         SQL> update t1 set col = 7 
SQL> commit;                                   where col = 3;
                                         ERROR at line 1:
Commit complete.                         ORA-08177: can't serialize access 
                                                    for this transaction 

В приведенной выше ситуации Oracle, чтобы поддерживать вашу базу данных в согласованном режиме, бросает на вас ORA-08177, поскольку она знает о том, что сериализуемая транзакция №1 внесла изменения в строки (строки), где col = 3 и сериализуемая транзакция № 2 не приобрела новый моментальный снимок данных, но работает на старом.

Кажется очень и очень чрезмерным использовать явную исключительную блокировку таблиц и сериализуемую транзакцию вместе: a) огромный удар по concurrency; b) Чтобы избежать ORA-08177, вам необходимо совершить транзакцию или откат транзакции, которая вызывает ошибку ORA-08177 и повторите попытку снова, и как только вы произведете фиксацию или откат, исключительная блокировка таблицы будет немедленно выпущена.