0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Oracle Databaseでデッドロック

Posted at

概要

デッドロックはデータベースにおける基本的なものだと思います。ググるといろいろと情報が出てくると思うのですが、最近データベースを触っていないこともあり、リハビリとしてOracle Databaseを使用したデッドロックの再現をしてみようと思います。また、ロックの取得状態や関連統計についても書いておきます。

環境

  • Oracle Database 19c Release 3
  • Oracle Linux 7.7

ここでやりたいこと

マニュアル にも記載されている、おそらく最も一般的なシナリオだと思われる**「2つのトランザクションがたすき掛けで同じレコードを更新」**した場合のデッドロックについて確認していこうと思います。イメージとしては以下のような流れです。

時刻 セッション1 セッション2 状態
t1 レコード1を更新 - セッション1がレコード1の行ロック獲得
t2 - レコード2を更新 セッション2がレコード2の行ロック獲得
t3 レコード2を更新 - セッション1はレコード2の行ロック開放を待機
t4 - レコード1を更新 セッション2はレコード1の行ロック開放を待機(デッドロック発生)

やってみる

まずは適当な検証用テーブルとデータを準備します。

検証用テーブルの作成
create table test_tab (id number, value varchar2(25));

検証用データの登録
insert all
into test_tab values(1, 'HOGE_1')
into test_tab values(2, 'HOGE_2')
select * from dual;

commit;

テーブルの状態は以下のような感じです。確認で使用するため、オブジェクト番号なども併せてSELECTしています。

select
  id,
  value,
  dbms_rowid.rowid_object(rowid) data_object_id,
  dbms_rowid.rowid_block_number(rowid) block_number,
  dbms_rowid.rowid_row_number(rowid) row_number
from test_tab
order by id;
        ID VALUE                     DATA_OBJECT_ID BLOCK_NUMBER ROW_NUMBER
---------- ------------------------- -------------- ------------ ----------
         1 HOGE_1                             23374          132          0
         2 HOGE_2                             23374          132          1

これで準備は完了です。ここからたすき掛けでの更新を行っていきますが、その前に検証で使用する2つのセッションのSIDを以下のSQLで確認しておきます。

select sys_context('USERENV','SID') sid from dual;

今回は以下となります。

- セッション1 セッション2
SID 42 39

(時刻t1)セッション1でid=1のレコードを更新

update test_tab set value='updated by session 1' where id=1;

select * from test_tab;
        ID VALUE
---------- -------------------------
         1 updated by session 1
         2 HOGE_2

(時刻t2)セッション2でid=2のレコードを更新

update test_tab set value='updated by session 2' where id=2;

select * from test_tab;
	ID VALUE
---------- -------------------------
         1 HOGE_1
         2 updated by session 2

(時刻t3)セッション1からid=2のレコードを更新

update test_tab set value='updated by session 1' where id=2;
★ロック開放待ちで待機

既にid=2のレコードは時刻t2にセッション2(SID=39)で更新されているため、セッション1(SID=42)からは行ロックの獲得が行えず、待機状態になります。

このとき、どのような種類のロックが取得/要求されているかの一覧はv$lockから確認することができます。今回のセッションID、表ロック(TM)と行ロック(TX)のみに絞ってSELECTしてみます。

select
  sid,
  type,
  id1,
  id2,
  lmode,
  request,
  block
from v$lock 
  where sid in (42,39) and type in ('TX', 'TM') 
  order by 1,2;
       SID TY        ID1        ID2      LMODE    REQUEST      BLOCK
---------- -- ---------- ---------- ---------- ---------- ----------
        39 TM      23374          0          3          0          0
        39 TX     327706        478          6          0          1 <-1
        42 TM      23374          0          3          0          0
        42 TX     196633        677          6          0          0
        42 TX     327706        478          0          6          0 <-2

ということで、今回実行している両セッションにおいて、表ロック(TM)と行ロック(TX)が獲得されています。(★1)の箇所はBLOCK列が1となっているため、他セッションをブロックしていることがわかります。また、(★2)の箇所はREQUEST列が6となっているため。排他モードでのロック獲得を要求していることがわかります。そのため、(★1)が時刻t2、(★2)が時刻t3における操作に対応したロックとなります。

なお、ID1, ID2の意味はロックモード(LMODE)で異なりますが、これらはv$lock_typeから確認できます。

select
  type,
  name,
  id1_tag,
  id2_tag
from v$lock_type
  where type in ('TX', 'TM')
  order by 1;
TYPE  NAME            ID1_TAG                        ID2_TAG
----- --------------- ------------------------------ ------------------------------
TM    DML             object #                       table/partition
TX    Transaction     usn<<16 | slot                 sequence

したがって、表ロック(TM)のID1からオブジェクト番号がわかるため、test_tab表に対して取得されたロックであることがわかります。行ロック(TX)のID1, ID2については更新を行っているトランザクション情報を示しているようなので、v$transactionからアクティブトランザクションと紐づけられそうです。

また、要求されているロックや競合に関する同様の情報はDBA_WAITERSからも確認することができます。

select 
  waiting_session,
  holding_session,
  lock_type,
  mode_held,
  mode_requested,
  lock_id1,
  lock_id2
from dba_waiters
  where waiting_session=42;
WAITING_SESSION HOLDING_SESSION LOCK_TYPE       MODE_HELD  MODE_REQUE   LOCK_ID1   LOCK_ID2
--------------- --------------- --------------- ---------- ---------- ---------- ----------
             42              39 Transaction     Exclusive  Exclusive      327706        478

最後に、セッション1(SID=42)の情報をv$sessionから確認してみます。

select
  sid,
  event,
  state,
  row_wait_obj#,
  row_wait_block#,
  row_wait_row#,
  round(wait_time_micro/1000000,2) as "WAIT TIME[S]",
  blocking_session 
from v$session 
  where sid=42;
       SID EVENT                               STATE      ROW_WAIT_OBJ# ROW_WAIT_BLOCK# ROW_WAIT_ROW# WAIT TIME[S] BLOCKING_SESSION
---------- ----------------------------------- ---------- ------------- --------------- ------------- ------------ ----------------
        42 enq: TX - row lock contention       WAITING            23374             132             1       444.57               39

待機イベント"enq: TX - row lock contention"により待機しています。ROW_WAIT_XXX列から、ロック獲得待ちの対象となっているオブジェクト番号/ブロック/行の情報がわかるため、冒頭のDBMS_ROWIDから確認した結果と照らし合わせることで、test_tab表のid=2の行における更新でロック競合が発生していることがわかります。

(時刻t4)セッション2からid=1のレコードを更新

ここでの更新によりデッドロックが発生します。デッドロックの発生は統計値"enqueue deadlocks"に反映されるため、デッドロック発生前後での値の変化もv$sysstatから確認しておきます。

select
  statistic#,
  name,
  class,
  value
from v$sysstat
  where name = 'enqueue deadlocks';
STATISTIC# NAME                      CLASS      VALUE
---------- -------------------- ---------- ----------
        44 enqueue deadlocks             4          0

デッドロックはまだ発生していないため、この時点では0となっています。
更新をおこない、デッドロックを発生させます。

update test_tab set value='updated by session 2' where id=1;
★ロック開放待ちで待機

こちらも同様に、既にid=1のレコードは時刻t1にセッション1(SID=42)で更新されているため、セッション2(SID=39)からは行ロックの獲得が行えず、待機状態になります。この操作によって、2つのセッションからたすき掛けでの更新が行われたたためデッドロックとなりますが、Oracle Databaseではデッドロックが検知されるとエラーとともに自動的に解消(ロールバック)されます。セッション1では時刻t3における処理がロールバックされ、ORA-60エラーによりデッドロック状態が解消されたことが通知されます。

SQL> update test_tab set value='updated by session 1' where id=2;
update test_tab set value='updated by session 1' where id=2
                    *
1でエラーが発生しました。:
ORA-00060: リソース待機の間にデッドロックが検出されました。

ということで、デッドロックを無事に再現できました。この状態でセッション1側をcommit/rollbackすることで、セッション2での処理を進めることができます。

最後に、統計値"enqueue deadlocks"を確認しておきます。

select
  statistic#,
  name,
  class,
  value
from v$sysstat
  where name = 'enqueue deadlocks';
STATISTIC# NAME                      CLASS      VALUE
---------- -------------------- ---------- ----------
        44 enqueue deadlocks             4          1

デッドロックの発生により統計値がインクリメントされており、デッドロックの発生を確認することが可能です。

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?