はい、承知いたしました。Oracleデータベースにおけるテーブルロックのチェック方法について、約5000語の詳細な記事を作成します。
Oracleデータベースにおけるテーブルロックをチェックする方法:詳細解説
はじめに
Oracleデータベースは、複数のユーザーやアプリケーションが同時にデータにアクセスする際に、データの一貫性(整合性)と並行性(コンカレンシー)を保証するための強力なロック機構を備えています。このロック機構は、トランザクションが互いに干渉し合わないようにデータを保護し、データベースが常に正しい状態を維持することを可能にします。
データベースにおけるロックの中でも、特にアプリケーション開発者やデータベース管理者が直面しやすいのが、テーブルロック (TMロック) に起因する競合です。テーブルロックは、トランザクションがテーブル全体、またはその一部に対して行う操作に関連して発生します。このロックによって他のトランザクションが待機状態に陥り、システムのパフォーマンス低下やアプリケーションのハングといった問題を引き起こすことがあります。
本記事では、Oracleデータベースにおけるテーブルロックの仕組みから、発生しているテーブルロックをどのようにチェックし、競合が発生した場合にどのように診断・解決するかまでを、詳細かつ網羅的に解説します。約5000語のボリュームで、初心者から経験者まで幅広く役立つ情報を提供することを目指します。
第1部: ロックの基本
第1章: なぜデータベースロックが必要か?
リレーショナルデータベースにおいて、複数のトランザクションが同じデータに同時にアクセスする状況は頻繁に発生します。もし何の制御もなしに同時アクセスを許してしまうと、以下のようなデータ不整合が発生する可能性があります。
- ダーティリード (Dirty Read): あるトランザクションが、他のトランザクションによってまだコミットされていないデータを読み込んでしまう現象です。その後に、データを変更したトランザクションがロールバックした場合、読み込んだデータは存在しないことになり、不整合が発生します。
- ノンリピータブルリード (Non-repeatable Read): あるトランザクション内で同じデータを複数回読み込んだ際に、他のトランザクションによってデータが変更(更新または削除)され、読み取るたびに結果が変わってしまう現象です。
- ファントムリード (Phantom Read): あるトランザクション内で複数回同じ検索条件でデータを読み込んだ際に、他のトランザクションによって新たなデータが挿入され、読み取るたびに結果セットに含まれる行数が増えてしまう現象です。
これらの問題を回避し、データの一貫性を保証するために、データベースシステムはロック機構を使用してトランザクションの並行実行を制御します。ロックは、トランザクションが特定のデータリソース(行、ページ、テーブルなど)にアクセスする際に取得され、他のトランザクションがそのリソースに干渉することを防ぎます。
Oracleデータベースは、デフォルトのトランザクション分離レベルとして READ COMMITTED
を採用しており、これは標準SQLの READ COMMITTED
にOracle独自の機能であるマルチバージョンコンカレンシーコントロール(MVCC)を組み合わせたものです。MVCCにより、Oracleでは基本的には読み取り操作は書き込み操作をブロックせず、またその逆も同様です。読み取りトランザクションは、トランザクション開始時点またはステートメント開始時点のコミット済みデータを参照するため、ダーティリードは発生しません。ノンリピータブルリードやファントムリードは発生し得ますが、更新や削除といった書き込み操作に関しては、ロックによって厳密な整合性が保たれます。
つまり、Oracleにおけるロックの主要な目的は、書き込み操作(INSERT, UPDATE, DELETE, SELECT FOR UPDATE)間での競合を防ぎ、データの更新における一貫性を保証することにあります。特にテーブルロックは、テーブルに対するDML操作やDDL操作の並行性を制御するために使用されます。
第2章: Oracleデータベースのロックの種類
Oracleデータベースには、さまざまな目的と粒度のロックが存在します。主要なロックタイプをいくつか挙げます。
- トランザクションロック (TX Lock):
- 粒度: 行
- 説明: トランザクションが行を挿入、更新、または削除する際に取得されるロックです。
SELECT ... FOR UPDATE
文でも指定した行に対してTXロックが取得されます。TXロックは、同じ行に対する他のトランザクションからの更新や削除を防ぎます。複数のトランザクションが同じテーブルの異なる行を更新することは可能ですが、同じ行を同時に更新しようとすると、後続のトランザクションはTXロックが解放されるまで待機します。これは最も一般的なロックタイプであり、通常、アプリケーション開発者が意識するのはこのTXロック(行ロック)とそれに伴うテーブルロックです。
- テーブルロック (TM Lock):
- 粒度: テーブル
- 説明: テーブル全体に対するロックです。DML文(INSERT, UPDATE, DELETE)を実行するトランザクションが、そのテーブル内の最初の行ロック(TXロック)を取得する際に、そのテーブルに対して自動的に取得されます。また、
LOCK TABLE
文や、ALTER TABLE
などのDDL文を実行する際にも取得されます。TMロックは、TXロックとは異なり、テーブル全体に対する操作の並行性を制御しますが、そのモードによっては他のトランザクションによるDML操作を許可する場合もあります。本記事の主要なテーマです。
- DDL ロック (Dictionary Lock):
- 粒度: データディクショナリ内のオブジェクト定義
- 説明:
ALTER TABLE
,DROP TABLE
,CREATE INDEX
などのDDL(データ定義言語)文を実行する際に取得されるロックです。DDLロックは、対象オブジェクトの定義変更中に他のトランザクションがそのオブジェクトにアクセスすることを制限し、データディクショナリの一貫性を保護します。DDLロックの中には、排他ロック(他の全てのアクセスをブロック)と共有ロック(特定のアクセスは許可)があります。
- ラッチ (Latch):
- 粒度: メモリ構造 (SGA)
- 説明: SGA内の共有データ構造へのアクセスを保護するための軽量な同期メカニズムです。ロックよりも取得・解放のオーバーヘッドが小さいですが、保持時間は非常に短いです。例えば、バッファキャッシュのブロックやライブラリキャッシュのエントリにアクセスする際にラッチが使用されます。ラッチ競合は深刻なパフォーマンス問題を引き起こす可能性があります。
- ミュテックス (Mutex):
- 粒度: SGA内の特定データ構造へのアクセス
- 説明: 11g以降で導入された、ラッチよりもさらに軽量な同期メカニズムです。ラッチの一部を置き換える形で使用されています。
- その他: ライブラリキャッシュロック、行キャッシュロック(ディクショナリキャッシュロック)、システムロックなど、Oracle内部で使用される様々なロックが存在します。
本記事ではテーブルロック(TMロック)に焦点を当てますが、Oracleのロック機構はこれほど多様な要素から成り立っていることを理解しておくことが重要です。
第3章: テーブルロック (TMロック) の詳細 – モードと互換性
テーブルロック (TMロック) は、テーブル全体に対する操作の並行性を制御します。TMロックは、その目的と必要な排他性に応じて、いくつかのモードを持っています。あるトランザクションが特定のモードのTMロックを保持している場合、他のトランザクションが同じテーブルに対して別のモードのロックを取得できるかどうかは、保持されているロックモードと要求されているロックモードの互換性によって決まります。
TMロックには主に以下の6つのモードがあります(V$LOCK
ビューの LMODE
または REQUEST
列の値で示されます)。
- Null (N) – モード 0:
- 説明: ロックは保持されていません。互換性は常にあります。
- 取得方法: デフォルト状態。
- Sub Shared (SS) [Row Share] – モード 1:
- 説明: 他のトランザクションが同じテーブルで
LOCK TABLE ... IN ROW SHARE MODE
またはSELECT ... FOR UPDATE
を実行することを許可します。ただし、テーブルに対する排他アクセス(LOCK TABLE ... IN EXCLUSIVE MODE
や特定のDDLなど)はブロックします。 - 取得方法:
LOCK TABLE table_name IN ROW SHARE MODE;
文、またはSELECT ... FOR UPDATE
文を実行するトランザクションによって自動的に取得されます(厳密にはTXロック取得時に自動的に取得されるTMロックの初期モード)。
- 説明: 他のトランザクションが同じテーブルで
- Sub Exclusive (SX) [Row Exclusive] – モード 2:
- 説明: 他のトランザクションが同じテーブルで
LOCK TABLE ... IN ROW EXCLUSIVE MODE
または DML 文(INSERT, UPDATE, DELETE)を実行することを許可します。ただし、テーブルに対する共有アクセス(LOCK TABLE ... IN SHARE MODE
)や排他アクセスはブロックします。 - 取得方法:
LOCK TABLE table_name IN ROW EXCLUSIVE MODE;
文、または INSERT/UPDATE/DELETE 文を実行するトランザクションによって自動的に取得されます(厳密にはTXロック取得時に自動的に取得されるTMロックのモード)。
- 説明: 他のトランザクションが同じテーブルで
- Shared (S) [Share] – モード 3:
- 説明: 他のトランザクションが同じテーブルで
LOCK TABLE ... IN SHARE MODE
を実行することを許可しますが、他のトランザクションが DML 文を実行することはブロックします。テーブルに対する排他アクセスもブロックします。 - 取得方法:
LOCK TABLE table_name IN SHARE MODE;
文によって取得されます。
- 説明: 他のトランザクションが同じテーブルで
- Shared Row Exclusive (SRX) [Share Row Exclusive] – モード 4:
- 説明: 他のトランザクションが同じテーブルで
LOCK TABLE ... IN ROW SHARE MODE
またはLOCK TABLE ... IN ROW EXCLUSIVE MODE
を実行することを許可しますが、他のトランザクションがLOCK TABLE ... IN SHARE MODE
や排他アクセスを行うことはブロックします。 - 取得方法:
LOCK TABLE table_name IN SHARE ROW EXCLUSIVE MODE;
文によって取得されます。
- 説明: 他のトランザクションが同じテーブルで
- Exclusive (X) [Exclusive] – モード 5:
- 説明: 他のトランザクションが同じテーブルに対して、いかなるロックモードでもロックを取得することを完全にブロックします。テーブルに対する全てのDMLおよびほとんどのDDL操作をブロックします。
- 取得方法:
LOCK TABLE table_name IN EXCLUSIVE MODE;
文、またはALTER TABLE
などの特定のDDL文によって取得されます。
これらのロックモードの互換性は、以下のマトリクスで表されます(Yes
は互換性あり、同時にロックを保持/要求できることを示します。No
は互換性なし、競合が発生し、後続のトランザクションが待機することを示します)。
保持ロック \ 要求ロック | N (0) | SS (1) | SX (2) | S (3) | SRX (4) | X (5) |
---|---|---|---|---|---|---|
N (0) | Yes | Yes | Yes | Yes | Yes | Yes |
SS (1) | Yes | Yes | Yes | Yes | Yes | No |
SX (2) | Yes | Yes | Yes | No | Yes | No |
S (3) | Yes | Yes | No | Yes | No | No |
SRX (4) | Yes | Yes | Yes | No | Yes | No |
X (5) | Yes | No | No | No | No | No |
このマトリクスからわかるように、最も排他性が高いのはXモードであり、他のどのモードとも互換性がありません。一方、SSモードやSXモードは比較的互換性が高く、複数のトランザクションが同時に同じテーブルに対してDML操作を行うことを可能にしています(ただし、行レベルでのTXロック競合は別途発生します)。
テーブルロックの競合が発生する典型的な例は、あるセッションがテーブルに対してXモードのロック(例えば ALTER TABLE ... ADD COLUMN
や LOCK TABLE ... IN EXCLUSIVE MODE
)を取得しようとした際に、他のセッションが同じテーブルに対してSSやSXモードのロック(DML操作や SELECT FOR UPDATE
)を保持している場合です。この場合、Xモードを要求しているセッションは、他のセッションがTMロックを解放するまで待機します。
第2部: テーブルロックの監視と診断
システムパフォーマンスの低下や特定処理のハングといった問題が発生した場合、テーブルロック競合が原因である可能性を疑い、実際にどのセッションがどのテーブルに対してロックを保持し、どのセッションが待機しているのかを調査する必要があります。Oracleデータベースは、このための強力な動的パフォーマンスビュー(V$ビュー)を提供しています。
第4章: ロック監視の基本 – V$LOCKとV$SESSIONビュー
テーブルロックの監視において最も基本となるのが、V$LOCK
ビューと V$SESSION
ビューです。
-
V$LOCK
ビュー:- 現在システム上でアクティブなロックに関する情報を保持しています。
- 主要な列:
SID
: セッションID。このロックを保持または待機しているセッションを識別します。TYPE
: ロックのタイプを示します(例:TM
はテーブルロック、TX
はトランザクションロック/行ロック)。ID1
,ID2
: ロックを識別する追加情報。TM
タイプの場合、ID1
はロック対象オブジェクトのID(DBA_OBJECTS.OBJECT_ID
)です。LMODE
: セッションがこのロックを現在保持しているモードを示します(0-5)。0はNullモード、1はSS、2はSX、3はS、4はSRX、5はXです。REQUEST
: セッションがこのロックを待機しているモードを示します(0-5)。0は待機なしです。REQUEST > 0
の行は、ロック待機中のセッションを示します。CTIME
: ロックが保持または待機されている時間(秒)を示します。BLOCK
: このロックが他のセッションをブロックしているかどうかを示します。1
はブロックしている、0
はブロックしていない、2
はデッドロックの犠牲者(ただしTMロックでは通常発生しない)。BLOCK = 1
の行は、他のセッションをブロックしているロックを示します。INST_ID
: Oracle RAC環境でのインスタンス番号。
-
V$SESSION
ビュー:- データベースに接続しているすべてのセッションに関する情報(状態、待機イベント、実行中のSQLなど)を保持しています。
- 主要な列:
SID
: セッションID。SERIAL#
: セッションのシリアル番号。SID
と組み合わせてセッションを一意に識別し、セッション切断などに使用します。USERNAME
: データベースユーザー名。PROGRAM
,MACHINE
,OSUSER
: クライアントプログラム、クライアントマシン名、OSユーザー名。STATUS
: セッションの状態(ACTIVE, INACTIVE, KILLEDなど)。WAIT_EVENT
: 現在セッションが待機しているイベント名。テーブルロック待機の場合はenq: TM - contention
と表示されることが一般的です。STATE
: 待機状態の詳細(例:WAITING
,WAITED SHORT TIME ON NON-IDLE EVENT
)。SQL_ID
: 現在実行中のSQLステートメントのID。PREV_SQL_ID
: 最後に実行されたSQLステートメントのID。BLOCKING_SESSION
: このセッションを現在ブロックしているセッションのSID
(10gR2以降)。BLOCKING_SESSION_SERIAL#
: このセッションを現在ブロックしているセッションのSERIAL#
(10gR2以降)。FINAL_BLOCKING_SESSION
,FINAL_BLOCKING_SESSION_SERIAL#
: 待機チェーンの最終的なブロックセッションの情報 (11gR2以降)。
第5章: ロック待機とブロックセッションの特定
V$LOCK
と V$SESSION
を結合することで、どのセッションがロックを待機しており、どのセッションがそれをブロックしているのかを特定できます。
テーブルロック (TYPE = 'TM'
) を待機しているセッションを確認する基本的なクエリは以下のようになります。
sql
SELECT
s.sid,
s.serial#,
s.username,
s.program,
s.machine,
s.osuser,
s.status,
s.state,
s.wait_event,
s.sql_id,
l.request, -- 待機しているロックモード
l.lmode, -- 保持しているロックモード (待機中は通常0)
l.ctime, -- 待機時間 (秒)
l.id1 -- 対象オブジェクトID
FROM
v$session s,
v$lock l
WHERE
s.sid = l.sid
AND l.type = 'TM'
AND l.request > 0 -- ロックを待機しているセッション
ORDER BY
l.ctime DESC;
このクエリの結果から、現在テーブルロックで待機しているセッションの SID
, SERIAL#
, ユーザー情報、待機イベント (wait_event
)、待機時間 (ctime
)、そして待機しているロックモード (request
) がわかります。l.id1
は、待機しているテーブルのオブジェクトIDです。
次に、これらの待機セッションをブロックしているセッションを特定します。V$LOCK.BLOCK = 1
となっている TM
ロックが、他のセッションをブロックしているテーブルロックです。
sql
SELECT
s.sid,
s.serial#,
s.username,
s.program,
s.machine,
s.osuser,
s.status,
s.state,
s.wait_event,
s.sql_id,
l.lmode, -- 保持しているロックモード
l.request, -- 待機モード (ブロックしているセッションでは通常0)
l.ctime, -- ロック保持時間 (秒)
l.id1 -- 対象オブジェクトID
FROM
v$session s,
v$lock l
WHERE
s.sid = l.sid
AND l.type = 'TM'
AND l.block = 1 -- 他のセッションをブロックしているロック
ORDER BY
l.ctime DESC;
このクエリは、現在テーブルロックによって他のセッションをブロックしているセッション(ロックホルダー)の情報を取得します。l.lmode
が、そのセッションが保持しているロックモードです。待機セッションが要求しているモード (request
) と、ブロックしているセッションが保持しているモード (lmode
) の互換性がないために競合が発生していることがわかります。
Oracle 10g R2以降では、V$SESSION
ビューに BLOCKING_SESSION
列が追加されたため、これを利用すると待機セッションから直接ブロックしているセッションを追跡できます。
sql
-- テーブルロックで待機しているセッションとそのブロックセッションを特定
SELECT
waiting_s.sid AS waiting_sid,
waiting_s.serial# AS waiting_serial#,
waiting_s.username AS waiting_user,
waiting_s.program AS waiting_program,
waiting_s.wait_event AS waiting_event,
waiting_s.sql_id AS waiting_sql_id,
waiting_l.request AS requested_lock_mode,
blocking_s.sid AS blocking_sid,
blocking_s.serial# AS blocking_serial#,
blocking_s.username AS blocking_user,
blocking_s.program AS blocking_program,
blocking_s.status AS blocking_status,
blocking_s.sql_id AS blocking_sql_id,
blocking_l.lmode AS held_lock_mode,
waiting_l.id1 AS object_id -- 待機対象のオブジェクトID
FROM
v$session waiting_s
JOIN
v$lock waiting_l ON waiting_s.sid = waiting_l.sid AND waiting_l.type = 'TM' AND waiting_l.request > 0
LEFT JOIN
v$session blocking_s ON waiting_s.blocking_session = blocking_s.sid -- ブロックセッションとのJOIN
LEFT JOIN
v$lock blocking_l ON blocking_s.sid = blocking_l.sid AND blocking_l.type = 'TM' AND blocking_l.block = 1 AND blocking_l.id1 = waiting_l.id1 -- ブロックしているロックとのJOIN
WHERE
waiting_s.wait_event LIKE 'enq: TM - contention%'; -- TMロック待機イベントで絞り込み
このクエリは、待機セッションの情報 (waiting_s.*
, waiting_l.request
) と、そのセッションをブロックしているセッションの情報 (blocking_s.*
, blocking_l.lmode
) を同時に表示します。object_id
を見れば、どのオブジェクトに対するロックで競合が発生しているかがわかります。
第6章: 対象オブジェクトと問題SQLの特定
前述のクエリで取得した ID1
は、ロック対象のオブジェクト(通常はテーブル)のIDです。このIDを使って DBA_OBJECTS
ビューを結合することで、オブジェクトの名前、タイプ、所有者を特定できます。
sql
-- ロックされている/待機されているテーブルとそのセッション情報を表示
SELECT
s.sid,
s.serial#,
s.username,
s.program,
s.machine,
s.osuser,
DECODE(l.lmode, 0, 'None', 1, 'SS', 2, 'SX', 3, 'S', 4, 'SRX', 5, 'X', TO_CHAR(l.lmode)) AS held_mode,
DECODE(l.request, 0, 'None', 1, 'SS', 2, 'SX', 3, 'S', 4, 'SRX', 5, 'X', TO_CHAR(l.request)) AS requested_mode,
l.ctime AS lock_time_sec,
l.block, -- 1: Blocking, 0: Not Blocking, 2: Deadlock Victim (TMでは通常0)
o.owner,
o.object_name,
o.object_type,
s.sql_id -- 実行中のSQL ID
FROM
v$session s
JOIN
v$lock l ON s.sid = l.sid
JOIN
dba_objects o ON l.id1 = o.object_id
WHERE
l.type = 'TM'
ORDER BY
l.ctime DESC;
このクエリでは、DECODE
を使用してロックモード番号を分かりやすい名称に変換しています。l.block=1
となっている行がブロッカー、l.request > 0
となっている行がウェイターです。o.owner
, o.object_name
で対象テーブルが特定できます。
さらに、ロックに関与しているセッションが現在実行している(または直前に実行していた)SQLステートメントの内容を知ることは、ロックの原因を特定する上で非常に重要です。V$SESSION.SQL_ID
または V$SESSION.PREV_SQL_ID
を使用して、V$SQLAREA
または V$SQL
ビューからSQLテキストを取得できます。
sql
-- ロック競合に関わるセッション、オブジェクト、およびSQLテキストの詳細
SELECT
waiting_s.sid AS waiting_sid,
waiting_s.serial# AS waiting_serial#,
waiting_s.username AS waiting_user,
waiting_s.program AS waiting_program,
waiting_s.wait_event AS waiting_event,
waiting_l.request AS requested_lock_mode,
blocking_s.sid AS blocking_sid,
blocking_s.serial# AS blocking_serial#,
blocking_s.username AS blocking_user,
blocking_s.program AS blocking_program,
blocking_s.status AS blocking_status,
blocking_l.lmode AS held_lock_mode,
o.owner,
o.object_name,
o.object_type,
dbms_lob.substr(blocking_sql.sql_fulltext, 4000, 1) AS blocking_sql_text -- ブロッカーのSQL
FROM
v$session waiting_s
JOIN
v$lock waiting_l ON waiting_s.sid = waiting_l.sid AND waiting_l.type = 'TM' AND waiting_l.request > 0
LEFT JOIN
v$session blocking_s ON waiting_s.blocking_session = blocking_s.sid
LEFT JOIN
v$lock blocking_l ON blocking_s.sid = blocking_l.sid AND blocking_l.type = 'TM' AND blocking_l.block = 1 AND blocking_l.id1 = waiting_l.id1
LEFT JOIN
v$sql blocking_sql ON blocking_s.sql_id = blocking_sql.sql_id AND blocking_s.sql_child_number = blocking_sql.child_number -- SQLテキスト取得 (SQL_FULLTEXTはCLOBのためsubstr)
JOIN
dba_objects o ON waiting_l.id1 = o.object_id -- 待機対象オブジェクト
WHERE
waiting_s.wait_event LIKE 'enq: TM - contention%';
このクエリは、テーブルロックで待機しているセッションと、それをブロックしているセッション、競合しているテーブル、そしてブロックしているセッションが実行しているSQLテキストを一覧で表示します。ブロックしているSQLの内容を確認することで、なぜそのロックが保持されているのか、そしてそのトランザクションがどれくらいで完了しそうなのかを推測する手がかりが得られます。
第7章: SQL Developer/OEMによる監視
SQLコマンドラインだけでなく、Oracle SQL Developer や Oracle Enterprise Manager (OEM) / Cloud Control のようなGUIツールを使用すると、視覚的にロック情報を確認できます。
- Oracle SQL Developer:
- 接続後、左側のナビゲータで「レポート」タブを選択します。
- 「リアルタイム SQL モニタリング」や「トップNセッション」などのレポートの中に、アクティブセッションや待機セッション、実行中のSQLに関する情報が表示されます。
- 「DBA」ツリーを展開し、「セッション」や「トップセッション」などを選択すると、セッションの一覧や詳細、待機イベントなどが確認できます。ロック待機中のセッションは、待機イベントが
enq: TM - contention
などと表示されます。 - また、「ロック」や「待機」といったキーワードで検索すると、関連する標準レポートやカスタムレポートを見つけることができます。
- Oracle Enterprise Manager (OEM) / Cloud Control:
- ターゲットデータベースのホーム画面で、通常「パフォーマンス」セクションにアクティブセッションや待機イベントのグラフが表示されます。
- 「待機イベント」のセクションで
enq: TM - contention
が上位に表示されている場合、テーブルロック競合が多発していることを示します。 - 「アクティブセッション」画面では、各セッションの詳細情報(状態、待機イベント、実行SQL、ブロッカーなど)が確認できます。待機ツリーを表示する機能もあり、ロックチェーンを視覚的に把握できます。
- より詳細な診断のために、ASH分析やAWRレポートを生成することも可能です。
GUIツールは、複数のセッション間の関係性(ロックチェーン)を直感的に把握するのに非常に役立ちます。特にOEM/Cloud Controlの待機ツリー機能は、複雑なロック競合の根本原因を特定するのに強力です。
第8章: ロック競合の診断手順
テーブルロック競合によるパフォーマンス問題に直面した場合、以下の診断手順を実行することが推奨されます。
- 問題の発生を確認: アプリケーションエラー(タイムアウト)、処理の遅延、ユーザーからの報告など。
- データベース全体の状態を確認:
V$SESSION
でACTIVEなセッション数や待機中のセッション数を確認。CPU使用率、I/O待ちなどの全体的なボトルネックを確認。(OEM/Cloud Controlのホーム画面などが便利) - 待機イベントを確認:
V$SESSION
のWAIT_EVENT
やV$SESSION_WAIT
(旧バージョン) を確認し、enq: TM - contention
や関連する待機イベントが発生しているか確認。 - 待機セッションを特定:
V$LOCK
またはV$SESSION
を使用して、TYPE='TM'
かつREQUEST > 0
のセッション(ウェイター)を特定する。上記のSQLクエリが役立ちます。 - 待機対象オブジェクトを特定: ウェイターが待機している
TM
ロックのID1
から、DBA_OBJECTS
を使って対象テーブルを特定する。 - ブロックセッションを特定: ウェイターをブロックしているセッション(ブロッカー)を特定する。
V$LOCK.BLOCK=1
の行、またはV$SESSION.BLOCKING_SESSION
を使用する。 - ブロッカーが保持しているロックモードを確認: ブロッカーが対象テーブルに対してどのようなモードのロック (
V$LOCK.LMODE
) を保持しているか確認。 - ブロッカーが実行中のSQLを確認: ブロッカーセッションの
SQL_ID
からV$SQLAREA
/V$SQL
を使ってSQLテキストを取得する。またはV$SESSION.PREV_SQL_ID
を確認する。 - ブロッカーセッションの状態を確認: ブロッカーセッションがACTIVEで処理を進めているのか、それとも別の原因で待機しているのか(例えば、そのブロッカーセッション自身が別のロックやリソースを待っている可能性もある)。
V$SESSION
のSTATUS
,STATE
,WAIT_EVENT
を確認する。V$WAIT_CHAINS
ビュー(後述)が非常に役立ちます。 - 原因の分析: 特定したブロッカーセッションのSQL、保持しているロックモード、そのセッションの状態から、なぜロックが保持され続け、競合が発生しているのか原因を分析する。長時間実行されるトランザクション、非効率なSQL、不適切なロックの使用などが考えられる。
この手順を踏むことで、問題の根源にあるセッション、オブジェクト、そして処理内容を特定し、適切な次のステップに進むことができます。
第3部: ロック問題の解決と予防
ロック競合の原因が特定できたら、その解決策を検討し、将来的な問題を予防するための対策を講じます。
第9章: ロック競合の解決策 – 強制切断を含む
ロック競合が発生し、システム全体のパフォーマンスに影響を与えている場合、いくつかの解決策があります。
- ブロックしているセッションの完了を待つ:
- 最も穏便な方法です。ブロッカーセッションがすぐに処理を完了すると予想される場合、待つことが最善です。
- ブロッカーが実行中のSQLの内容や、そのセッションがACTIVEかどうかを確認し、完了までの時間を推測します。
- ブロックしているユーザー/プログラムに連絡する:
- ブロッカーの
USERNAME
,PROGRAM
,MACHINE
,OSUSER
を確認し、そのセッションの担当者に連絡を取り、トランザクションをコミットまたはロールバックしてもらうよう依頼します。 - これが可能な場合は、最もリスクの少ない解決策です。
- ブロッカーの
- ブロックしているセッションを強制切断する (KILL SESSION):
- 上記の手段が取れない場合や、問題が非常に深刻で待っていられない場合に、最終手段としてブロッカーセッションを強制的に終了させることができます。
- 構文:
ALTER SYSTEM KILL SESSION 'sid,serial#' IMMEDIATE;
- 例:
ALTER SYSTEM KILL SESSION '123,456' IMMEDIATE;
(ここで 123 はSID, 456 はSERIAL#) - 注意点:
IMMEDIATE
オプションを指定すると、現在実行中のSQLは即座に中断され、そのセッションが実行していた未コミットのトランザクションはロールバックされます。- ロールバック処理は、トランザクションが変更したデータの量によっては非常に時間がかかる場合があります。ロールバック中は、対象のリソース(テーブルや行)に対するロックは保持されたままになるため、待機していたセッションはロールバックが完了するまで待機し続けることになります。つまり、
KILL SESSION
を実行しても、即座にロックが解放されるわけではありません。 - 不用意なセッション切断は、アプリケーション側で予期しないエラーやデータの不整合(コミットされていない変更が失われるため、アプリケーションのロジックによっては問題となる)を引き起こす可能性があります。影響範囲を十分に考慮してから実行する必要があります。
V$SESSION
でセッションの状態がKILLED
になった後も、実際にはバックグラウンドでロールバック処理が続行している状態です。セッションが完全に終了し、ロックが解放されるのはロールバックが完了してからです。
- アプリケーションコードの修正:
- ロック競合が頻繁に発生する場合は、根本的な原因がアプリケーションの設計やSQLにある可能性が高いです。
- トランザクションを短くする、必要なロックだけを取得する、非効率なSQLをチューニングする(インデックスの追加、実行計画の見直しなど)、並行性の高い設計に見直すといった対策が必要です。
第10章: デッドロックとその対応
デッドロックは、複数のトランザクションがお互いが必要とするリソースに対するロックを取得しており、どのトランザクションも先に進めない状態です。例えば、トランザクションAがリソースXのロックを保持し、リソースYのロックを待っている。同時にトランザクションBがリソースYのロックを保持し、リソースXのロックを待っている、といった状況です。
Oracleデータベースは、デッドロックを自動的に検出するメカニズムを備えています。デッドロックが検出されると、Oracleはデッドロックに関与しているいずれか一方のトランザクションを強制的にロールバックさせ、デッドロックを解消します。ロールバックされるセッションは、通常 ORA-00060
エラーを受け取ります。
デッドロックが発生した場合、ORA-00060
エラーが発生したセッションのトレースファイルが診断情報として生成される場合があります。このトレースファイルには、デッドロックに関与していたセッション、待機していたオブジェクト、実行中のSQLなどの詳細情報が含まれています。トレースファイルを確認することで、デッドロックが発生した状況を把握できます。
まれに V$LOCK
の BLOCK
列が 2
と表示されることがありますが、これはそのセッションがデッドロックの犠牲者として選択されたことを示します。ただし、V$LOCK
ビューだけでデッドロック全体を把握することは難しく、トレースファイルや V$WAIT_CHAINS
ビュー(後述)がより有用です。
デッドロックの解決策は、Oracleによる自動検出とロールバックに依存しますが、根本的な予防策としては、アプリケーション側でのロック取得順序の標準化や、トランザクションの設計見直しが重要です。
第11章: ロック問題を防ぐためのベストプラクティス
テーブルロック競合を未然に防ぐためには、以下のベストプラクティスを実践することが推奨されます。
- トランザクションを可能な限り短くする: トランザクションが長いほど、ロックを保持する時間も長くなり、他のセッションとの競合が発生しやすくなります。不要な処理を含めず、必要なデータ変更を最小限の範囲で行い、迅速にコミットまたはロールバックします。
- 必要なロックだけを取得する:
SELECT ... FOR UPDATE
は必要な行だけをロックし、可能な限りWHERE句で絞り込みます。明示的なLOCK TABLE
文は、本当にテーブル全体をロックする必要がある場合に限り使用します。多くのDML操作では、デフォルトのSXモードのテーブルロックで十分な並行性が確保されます。 - 効率的なSQLを作成する: 高速に実行されるSQLは、ロックを保持する時間を短縮します。適切なインデックスの設計、SQLチューニングによる実行計画の最適化は、ロック問題の予防に大きく貢献します。例えば、UPDATE文でWHERE句の条件にインデックスが効かない場合、フルスキャンが発生し、不要な行にも一時的なロックがかかる可能性があります。
- ロック取得順序を標準化する: 複数のテーブルに対する更新を含むトランザクションでは、常に同じ順序でテーブルをロックするようにアプリケーションを設計することで、デッドロックの発生リスクを低減できます。
- ロックタイムアウトを活用する: 特定の操作でロック待機が長時間続くことを避けたい場合、
SET LOCK_TIMEOUT
パラメータを設定することで、指定した時間(秒)を超えてロックを取得できなかった場合にエラーを発生させることができます。これにより、システム全体がハングするのを防ぎ、アプリケーション側でエラーハンドリングを行うことが可能になります。 - アプリケーション設計における並行性の考慮: 同一データに対する同時更新が頻繁に発生するような処理は、設計段階で並行性を考慮し、楽観的ロック(バージョン番号などを使った更新チェック)や、キューイング、非同期処理などの手法を検討することも有効です。
- 定期的な監視と分析:
V$LOCK
,V$SESSION
,V$WAIT_CHAINS
(後述) などのビューを定期的に監視し、ロック待機が発生していないかチェックします。AWRやASHレポートを活用して、過去のロック競合の傾向や原因を分析し、改善策を講じます。
第4部: より高度な監視
基本的なV$ビューによる監視に加えて、より高度な監視ツールやビューを活用することで、複雑なロック問題や履歴情報の分析が可能になります。
第12章: V$WAIT_CHAINSによる待機チェーン分析
Oracle 11g R2以降で導入された V$WAIT_CHAINS
ビューは、セッション間の待機関係(ロックチェーン)をツリー構造として表示するのに非常に強力です。ロック競合によって複数のセッションが連鎖的に待機しているような複雑な状況を、視覚的に分かりやすく把握できます。
V$WAIT_CHAINS
の主要な列:
* WAITER_SID
, WAITER_SERIAL#
: 待機しているセッションのSIDとSERIAL#。
* HOLDER_SID
, HOLDER_SERIAL#
: この待機セッションを直接ブロックしているセッション(リソースホルダー)のSIDとSERIAL#。
* EVENT
: 待機イベント(例: enq: TM - contention
)。
* P1TEXT
, P1RAW
, P1
, P2TEXT
, P2RAW
, P2
, P3TEXT
, P3RAW
, P3
: 待機イベントに関する詳細パラメータ。エンキュー待機(enq: ...
)の場合、P1
はエンキュータイプとモード、P2
はID1
、P3
はID2
を表すことが多いです。
* BLOCKING_SESSION
, BLOCKING_INST_ID
: 待機セッションをブロックしているセッションのSIDとインスタンスID (自身をブロックしているわけではない場合に設定される)。
* CHAIN_ID
: 待機チェーンを一意に識別するID。
* DEPTH
: 待機チェーンにおけるこのセッションの深さ(ブロッカーが0、ブロッカーに待たされているセッションが1、さらにそのセッションに待たされているセッションが2、…)。
* SQL_ID
: このセッションが実行しているSQLのID。
V$WAIT_CHAINS
を使用すると、例えば以下のようなクエリで待機チェーンを確認できます。
sql
-- 現在の待機チェーンを表示
SELECT
w.waiter_sid,
w.waiter_serial#,
w.holder_sid,
w.holder_serial#,
w.event,
w.p1text,
w.p2text,
w.p3text,
w.chain_id,
w.depth,
s_wait.program AS waiter_program,
s_wait.username AS waiter_user,
s_hold.program AS holder_program,
s_hold.username AS holder_user,
o.owner,
o.object_name,
o.object_type
FROM
v$wait_chains w
LEFT JOIN
v$session s_wait ON w.waiter_sid = s_wait.sid
LEFT JOIN
v$session s_hold ON w.holder_sid = s_hold.sid
LEFT JOIN
dba_objects o ON w.p2 = o.object_id AND w.event LIKE 'enq: TM%'; -- TMロック待機の場合、P2がオブジェクトID
WHERE
w.event LIKE 'enq: TM - contention%' -- テーブルロック待機に絞り込み
ORDER BY
w.chain_id, w.depth;
このクエリは、テーブルロック待機を起点とした待機チェーンを表示します。depth
が0のセッションがチェーンの根元(通常は最初のブロッカー)、depth
が増えるにつれて、そのセッションに待たされているセッションが表示されます。holder_sid
が waiter_sid
と同じ行は、自身がブロックしているのではなく、別のセッションにブロックされていることを示唆します。P2
(または P2RAW
) の値がTMロックのオブジェクトID (DBA_OBJECTS.OBJECT_ID
) に対応するため、それを DBA_OBJECTS
と結合することで対象オブジェクト名を特定できます。
V$WAIT_CHAINS
は、ロック競合だけでなく、他のリソース待機による複雑な待機チェーンの分析にも非常に強力なツールです。
第13章: ASH/AWRによる履歴分析
Active Session History (ASH) および Automatic Workload Repository (AWR) は、過去のパフォーマンスデータを収集・保存しており、過去に発生したロック競合の傾向や原因を分析するのに役立ちます。
- ASH: メモリー上のバッファにアクティブなセッションの情報をサンプリングして記録します。
V$ACTIVE_SESSION_HISTORY
ビューからアクセスできます。このビューには、セッションの状態、待機イベント、実行中のSQL ID、ブロックしているセッションの情報などが含まれます。過去の特定の時間帯にロック待機が多発していたかどうか、どのSQLがロックを保持していたかなどを確認できます。 - AWR: データベースのワークロードに関する統計情報(待機イベントの合計時間、トップSQLなど)をスナップショットとしてリポジトリに永続的に保存します。AWRレポートには、特定の期間内に発生した主要な待機イベントのサマリーが含まれており、
enq: TM - contention
が上位にランクインしているかどうかを確認できます。レポートの「Top Blocking Sessions」や「SQL ordered by % CPU Time」などのセクションも、ロック問題に関連する情報を得るのに役立ちます。
ASHやAWRを活用することで、リアルタイム監視では捉えきれない、断続的に発生するロック問題や、長期的な傾向分析が可能になります。
まとめ
Oracleデータベースにおけるテーブルロック(TMロック)は、データの一貫性と並行性を保証するための重要なメカニズムですが、適切に管理されないとパフォーマンス問題の主要な原因となり得ます。
本記事では、テーブルロックの基本的な仕組み、モード、互換性から始まり、以下の主要なポイントを詳細に解説しました。
- ロックの必要性: データの一貫性と並行性の両立のための基盤。
- ロックの種類: TXロック、TMロックなど、Oracleにおける様々なロック。
- TMロックの詳細: 6つのモード(N, SS, SX, S, SRX, X)と互換性マトリクス。
- ロック監視の基本:
V$LOCK
,V$SESSION
ビューを使用した現在のロック状況の確認。 - 診断: 待機セッション、ブロックセッション、対象オブジェクト、実行中SQLを特定する具体的なSQLクエリ例と診断手順。
- 解決: セッション完了待ち、連絡、
ALTER SYSTEM KILL SESSION
による強制切断(注意点付き)。 - デッドロック: Oracleによる自動検出と対応、トレースファイルによる診断。
- 予防: 短いトランザクション、効率的なSQL、適切なロック使用などのベストプラクティス。
- 高度な監視:
V$WAIT_CHAINS
による待機チェーン分析、ASH/AWRによる履歴分析。
テーブルロック競合は、複雑なアプリケーションや多数のユーザーが同時にアクセスするシステムでは避けられない可能性があります。重要なのは、ロック問題が発生した際に迅速かつ正確に状況を診断し、適切な解決策を実行できる能力を持つことです。また、日頃からロック待機状況を監視し、問題の兆候を早期に発見すること、そしてアプリケーション設計やSQLチューニングによってロック競合を予防するための継続的な取り組みが、安定したデータベース運用には不可欠です。
本記事が、Oracleデータベースにおけるテーブルロックの理解と、実際の運用におけるロック問題の解決・予防に役立つことを願っています。