Базы данных Oracle - статьи

         

Механизм взаимоблокировки


Вначале создадим тестового пользователя zh и выдадим ему все необходимые привилегии:

Подключение к: Oracle Database 10g Express Edition Release 10.2.0.1.0 – Production

SYSTEM@XE> CREATE USER zh IDENTIFIED BY test DEFAULT TABLESPACE users;   Пользователь создан

SYSTEM@XE> GRANT connect, resource, alter session TO zh;   Grant succeeded

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

ZH@XE> CREATE TABLE t1 (c1 NUMBER PRIMARY KEY, c2 VARCHAR2(50));   Таблица создана

ZH@XE> INSERT INTO t1 (c1) VALUES(1);   Вставлено: 1 строка

ZH@XE> INSERT INTO t1 (c1) VALUES(2);   Вставлено: 1 строка

ZH@XE> COMMIT;   Commit complete

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

Первый сеанс:

ZH@XE(28)> ALTER SESSION SET EVENTS '10046 trace name context forever, level 12';   Session altered

ZH@XE(28)> UPDATE t1 SET c2 = 'Строка1' WHERE c1 = 1;   Изменено: 1 строка

Второй сеанс:

ZH@XE(24)> UPDATE t1 SET c2 = 'Строка2' WHERE c1 = 2;   Изменено: 1 строка

В результате выполненных нами действий в существующих сеансах были открыты две транзакции. В первом сеансе была выставлена блокировка транзакции (TX) на первую строку в исключительном режиме. Такая же блокировка выставлена и во втором сеансе, но на вторую строку.  Убедиться в этом мы можем, сделав небольшой запрос к системному представлению v$lock:



SYSTEM@XE> SELECT * FROM v$lock WHERE sid IN (24, 28) AND type = 'TX';   ADDR     KADDR    SID TYPE ID1    ID2 LMODE REQUEST CTIME BLOCK -------- -------- --- ---- ------ --- ----- ------- ----- ----- 296EE5D8 296EE6F4 28  TX   262166 169 6     0       1110  0    296FA680 296FA79C 24  TX   327718 163 6     0       879   0   

Рассмотрим более подробно содержимое этого запроса. Столбец SID здесь содержит идентификаторы первого и второго сеансов (28 и 24). Содержимое столбца TYPE указывает на тип блокировки, в нашем случае блокировки транзакции (TX). Столбец LMODE хранит значение 6, что соответствует установившемуся исключительному режиму блокировки.


В результате мы видим, что в каждом из сеансов имеется по одной TX-блокировке в установившемся исключительном режиме. Определить, к какой транзакции относится каждая из этих TX-блокировок, можно с помощью содержимого дополнительных столбцов ID1 и ID2. В них находятся составные части идентификаторов транзакций, декодировать которые можно с помощью следующего запроса:

SYSTEM@XE> SELECT sid, TRUNC(id1/POWER(2,16)) rbs, BITAND(id1, POWER(2,16)-1)+ 0 slot, id2 seq FROM v$lock WHERE sid IN (24, 28) AND type = 'TX';   SID RBS SLOT SEQ --- --- ---- --- 28  4   22   169 24  5   38   163   Выбрано: 2 строки

Расшифровав значения столбцов ID1 и ID2, мы получили номер сегмента отката, слот и  номер изменения транзакции. Эти значения полностью совпадают со значениями из представления v$transaction и все вместе представляют собой идентификатор транзакции в шестнадцатеричном виде:

SYSTEM@XE> SELECT s.sid, t.xidusn, xidslot, xidsqn FROM v$transaction t, v$session s  WHERE t.addr = s.taddr;   SID XIDUSN XIDSLOT XIDSQN --- ------ ------- ------ 24  5      38      163   28  4      22      169     Выбрано: 2 строки

Данный идентификатор нам ещё встретится в дальнейшем, когда мы будем разбирать содержимое трассировочного файла взаимной блокировки, а пока продолжим последовательность начатых нами действий и изменим в первом сеансе содержимое второй строки:

ZH@XE(28)> UPDATE t1 SET c2 = 'Строка2' WHERE c1 = 2;

Ожидание …

Сеанс находиться в ожидании. Оно возникло от того, что первый сеанс пытается установить TX-блокировку в исключительном режиме второй строки, которая уже захвачена TX-блокировкой второго сеанса. Если в это время заглянуть в файл трассировки первого сеанса, то мы увидим там следующие строки:

WAIT #1: nam='enq: TX - row lock contention' ela= 3000022 name|mode=1415053318 usn<<16 | slot=327718 sequence=163 obj#=13766 tim=14923101611

В сеансе постоянно возникает ожидание “конкуренция блокировки строки”. В параметрах этого ожидания  мы видим  уже знакомые нам значения идентификатора транзакции второго сеанса (slot, sequence). Именно эта транзакция установила ранее TX-блокировку второй строки в исключительном режиме и привела к ожиданию. Более детально это можно просмотреть в содержимом представления v$lock:



SYSTEM@XE> SELECT * FROM v$lock WHERE sid IN (24, 28) AND type = 'TX';   ADDR     KADDR    SID TYPE ID1    ID2 LMODE REQUEST CTIME BLOCK -------- -------- --- ---- ------ --- ----- ------- ----- ----- 2A201720 2A201734 28  TX   327718 163 0     6       1074  0    296EE5D8 296EE6F4 28  TX   262166 169 6     0       7048  0    296FA680 296FA79C 24  TX   327718 163 6     0       6817  1      Выбрано: 3 строки

Как мы видим, в представлении появилась новая строка о TX-блокировке со значением 6 в поле REQUEST.  Данное значение означает, что сеанс 28 запросил установку TX-блокировки строки  в исключительном режиме. При этом значения столбцов ID1 и ID2 этого запроса содержат идентификатор транзакции сеанса 24. Это свидетельствует о том, что первый сеанс ожидает освобождения строки, захваченной транзакцией именно второго сеанса.

Итак, на данный момент мы имеем классическую картину ожидания. Но что произойдет, если мы изменим во втором сеансе первую строку? Ведь она уже захвачена первым сеансом:

ZH@XE(24)> UPDATE t1 SET c2 = 'Строка1' WHERE c1 = 1;

Ожидание…

Возникает ожидание, и мы становимся свидетелями типичного случая взаимного блокирования. Второй сеанс ожидает освобождения первой строки, заблокированной в результате, начатой транзакции первого сеанса. В то же время первый сеанс ожидает освобождение второй строки, заблокированной транзакцией второго сеанса.  Такое ожидание могло бы продолжаться вечно, но, как отмечали ранее, Oracle сам определяет возникновение подобных тупиковых ситуаций в течение примерно трёх секунд. В нашем случае он просто отменит выполнение последнего оператора UPDATE в первом сеансе и сгенерирует следующую ошибку:

ZH@XE(28)> UPDATE t1 SET c2 = 'Строка2' WHERE c1 = 2;   UPDATE t SET c2 = 'Строка2' WHERE c1 = 2        * Ошибка в строке 1: ORA-00060: deadlock detected while waiting for resource

Убедимся в том, что транзакции отменены не были. Для этого выполним следующий запрос:

SYSTEM@XE> SELECT s.sid, t.status, t.start_time, t.xid  FROM v$transaction t, v$session s  WHERE t.addr = s.taddr;   SID STATUS START_TIME        XID             --- ------ ----------------- ---------------- 24  ACTIVE 01/21/10 23:14:40 05002600A3000000 28  ACTIVE 01/21/10 23:10:49 04001600A9000000



Выбрано: 2 строки

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

Теперь мы должны решить, что нам делать с этой незавершённой транзакцией в первом сеансе. Повторять отменённый оператор не имеет смысла. Ситуация взаимного блокирования повторится, и ошибка возникнет уже в другом сеансе. Поэтому нам остаётся либо произвести отмену, либо зафиксировать транзакцию. После этого второй сеанс  продолжит выполнение оператора UPDATE, так как необходимая строка для этого будет освобождена.

Что же касается самой Oracle, то в результате всех перечисленных выше действий, приведших к возникновению взаимной блокировки, в файл журнала alert_xe.log будет занесена запись следующего вида:

Fri Jan 22 01:09:58 2010 ORA-00060: Deadlock detected. More info in file c:\oraclexe\app\oracle\admin\xe\udump\xe_ora_2480.trc.

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


Содержание раздела