日本語PDF

9 データの同時実行性と整合性

この章では、マルチユーザー・データベース環境でのOracle Databaseによるデータ整合性の維持について説明します。

この章の構成は、次のとおりです。

データの同時実行性と整合性の概要

シングル・ユーザーのデータベースでは、他のユーザーが同時に同じデータを変更することがないため、ユーザーは何も心配せずにデータを変更できます。ただし、マルチユーザー・データベースでは、複数のトランザクション内の文によって、同じデータが同時に更新される可能性があります。同時に実行される複数のトランザクションでは、意味のある一貫した結果を出すことが必要です。

マルチユーザー・データベースには次の機能が備わっている必要があります。

  • 複数のユーザーが同時にデータにアクセスできることの保証(データ同時実行性)

  • ユーザー自身のトランザクションや他のユーザーのトランザクションによる参照可能な変更を含め、各ユーザーにデータの一貫したビューが表示されることの保証(データ整合性)

トランザクションが同時に実行されるときの一貫したトランザクション動作を表すために、データベース調査者は、シリアライズ可能性と呼ばれるトランザクション分離モデルを定義しています。シリアライズ可能トランザクションは、他のユーザーがデータベースのデータを変更していないように認識される環境内で動作します。

一般に、この程度までのトランザクションの分離が望ましいとされますが、シリアライズ可能モードで数多くのアプリケーションを実行すると、アプリケーションのスループットがかなり低下する可能性があります。同時に実行される各トランザクションの完全な分離とは、あるトランザクションが問合せを実行している表に対して、他のトランザクションが挿入を実行できない状態を指します。つまり、現実には、トランザクションの完全な分離とパフォーマンスとの間の妥協点を考慮する必要があります。

Oracle Databaseでは、マルチバージョン一貫性モデルや様々なタイプのロックおよびトランザクションを使用することによって、データ整合性を維持します。これにより、データベースが複数の同時ユーザーに対してデータのビューを提示する場合、それぞれ特定の時点において一貫性があるビューになります。異なるバージョンのデータ・ブロックが同時に存在する場合があるため、トランザクションでは問合せに要求された時点でコミットされているデータのバージョンが読み取られ、ある特定の時点において一貫性のある結果が戻されます。

マルチバージョン読取り一貫性

Oracle Databaseでは、マルチバージョン機能によって、複数のバージョンのデータが同時にマテリアライズされます。Oracle Databaseでは、マルチバージョン読取り一貫性を維持します。

Oracleデータベースの問合せには、次の特性があります。

  • 読取り一貫性問合せ

    1つの問合せによって戻されるデータは、コミット済であり、ある特定の時点での一貫性を保っています。

    ノート:

    Oracle Databaseでは、他のトランザクションにおいて未コミットのデータをトランザクションが読み取る場合に発生する、内容を保証しない読取りは許可されていません

    内容を保証しない読取りの問題を具体的に説明するため、あるトランザクションが列値を更新し、その値がまだコミットされていない場合を考えます。2つ目のトランザクションによって、内容が保証されない更新された値(未コミットの値)が読み取られます。その後、1つ目のセッションによってトランザクションがロールバックされ、列の値が元の値に戻された場合でも、2つ目のトランザクションでは更新された値が引き続き使用されるため、データベースが破損します。内容を保証しない読取りでは、データ整合性が損われ、外部キーに違反し、一意制約が無視されます。

  • 非ブロック化問合せ

    データの読取り側と書込み側は、互いにブロックすることはありません。

文レベルの読取り一貫性

Oracle Databaseでは、文レベルの読取り一貫性を常に適用することにより、1つの問合せによって戻されたデータがコミット済で、ある特定の時点で一貫していることを保証します。

単一のSQL文の一貫性が保たれる基準となる時点は、トランザクション分離レベルおよび問合せの内容に応じて異なります。

  • コミット読取り分離レベルでは、この時点はが開始されたときになります。たとえば、SCN 1000でSELECT文が開始された場合、この文ではSCN 1000の時点での一貫性が保たれます。

  • シリアライズ可能トランザクションまたは読取り専用トランザクションでは、この時点はトランザクションが開始されたときになります。たとえば、複数のSELECT文が実行されるトランザクションがSCN 1000で開始された場合、各文ではSCN 1000の時点での一貫性が保たれます。

  • フラッシュバック問合せ操作(SELECT ... AS OF)では、SELECT文で特定の時点が明示的に指定されます。たとえば、先週の木曜日の2:00 p.m時点の表を問い合せることができます。

関連項目:

フラッシュバック問合せについて学習するには、Oracle Database開発ガイドを参照してください

トランザクション・レベルの読取り一貫性

Oracle Databaseは、トランザクションのすべての問合せに対して、トランザクション・レベルの読取り一貫性と呼ばれる読取り一貫性を実現することもできます。

この場合、トランザクション内の各文には同じ時点のデータが表示されます。これは、トランザクション開始時点です。

シリアライズ可能トランザクションによる問合せでは、そのトランザクション自体が変更したデータも参照できます。たとえば、employeesを更新し、employeesを問い合せたトランザクションには、その更新内容が反映されます。トランザクション・レベルの読取り一貫性によってリピータブル・リードが実現され、問合せで仮読取りが検索されなくなります。

読取り一貫性とUNDOセグメント

マルチバージョン読取り一貫性モデルを管理するには、表が同時に問合せおよび更新された場合に、読取り一貫性のある一連のデータがデータベースで作成される必要があります。

Oracle Databaseは、UNDOデータを使用して読取り一貫性を実現しています。

ユーザーがデータを変更すると、常にOracle DatabaseによってUNDOエントリが作成されて、UNDOセグメントに書き込まれます。UNDOセグメントには、未コミットのトランザクションや、最近コミットされたトランザクションによって変更されたデータの古い値が含まれています。このため、同じデータの異なる時点の複数のバージョンがデータベース内に存在することになります。データの読取り一貫性ビューを提供し、非ブロック化問合せを可能にするために、データベースでは異なる時点のデータのスナップショットを使用できます。

読取り一貫性は、単一のインスタンス内、およびOracle Real Application Clusters(Oracle RAC)環境内で保証されています。Oracle RACでは、データ・ブロックの読取り一貫性イメージをデータベース・インスタンス間で転送するために、キャッシュ・フュージョンと呼ばれるキャッシュ間ブロック転送メカニズムを使用します。

関連項目:

  • UNDOセグメント

    UNDO記憶域について学習するには
  • LOBの読取り一貫性メカニズムについて学習するには、「内部LOB」を参照してください

読取り一貫性: 例

この例は、コミット読取り分離レベルで、UNDOデータを使用して文レベルの読取り一貫性を提供する問合せの例を示しています。

図9-1 コミット読取り分離レベルでの読取り一貫性

図9-1の説明が続きます
「図9-1 コミット読取り分離レベルでの読取り一貫性」の説明

問合せによってデータ・ブロックを取得する場合、データベースによって、各ブロックのデータが問合せ開始時のブロックの内容を反映したものになることが保証されます。必要に応じて、ブロックに対する変更がロールバックされ、ブロックが問合せ処理の開始時点の内容に再構成されます。

データベースでは、SCNと呼ばれる内部順序付けメカニズムを使用して、トランザクションの順序が保証されます。SELECT文が実行フェーズに移行すると、データベースによって、問合せの実行開始時点で記録されたSCNが特定されます。図9-1では、このSCNは10023です。問合せでは、SCN 10023に対応したコミット済データが戻されます。

図9-1では、10023よりものSCNを持つブロック(SCN 10024の2つのブロック)は変更済データであることが示されています。この場合、SELECT文では、コミット済の変更と一貫性があるバージョンのブロックが必要となります。データベースでは、現在のデータ・ブロックが新しいバッファにコピーされ、前のバージョンのブロックを再構築するためにUNDOデータが適用されます。このように再構築されたデータ・ブロックは、読取り一貫性(CR)クローンと呼ばれます。

図9-1では、データベースによって2つのCRクローンが作成されており、1つのブロックではSCN 10006との一貫性が保たれ、もう1つのブロックではSCN 10021との一貫性が保たれています。問合せに対しては、この再構築されたデータが戻されます。Oracle Databaseでは、このようにして内容を保証しない読取りが回避されています。

読取り一貫性とInterested Transaction List

各セグメント・ブロックのブロック・ヘッダーにはInterested Transaction List (ITL)が含まれています。

データベースではITLを使用して、ブロックの変更が開始された時点でトランザクションがコミットされているかどうかが判断されます。

ITLのエントリは、ロックされた行のあるトランザクション、およびブロック内のコミット済の変更や未コミットの変更を含む行を表しています。ITLは、データベースに対する変更が行われたタイミングの情報を提供するUNDOセグメント内のトランザクション表を指しています。

ある意味で、ブロック・ヘッダーには、ブロック内の各行に影響を与えたトランザクションの最新の履歴が含まれていると考えられます。CREATE TABLE文およびALTER TABLE文のINITRANSパラメータは、保持するトランザクション履歴の量を制御します。

関連項目:

INITRANSパラメータについて学習するには、Oracle Database SQL言語リファレンスを参照してください。

読取り一貫性および遅延挿入

遅延挿入と呼ばれる特殊なタイプの挿入では、標準の読取り一貫性メカニズムは使用されません。

遅延挿入では、MEMOPTIMIZE_WRITEヒントを使用して、MEMOPTIMIZE FOR WRITEとして指定された表に挿入します。データベースではこれらの挿入を、バッファ・キャッシュではなくラージ・プールでバッファします。データベースでは、REDOおよびUNDOを使用して変更を追跡することはありません。かわりに、領域管理コーディネータ(SMCO)がバッファをディスクに書き込むと、データベースにより変更が自動的にコミットされます。この変更は、ロールバックできません。

遅延挿入は、従来の挿入と次の点で大きく異なります。

  • アプリケーションではコミット済となっている、ラージ・プールに存在するデータが失われる可能性があります。たとえば、変更が保存されたことがアプリケーションにより報告された場合でも、変更がディスクに保存される前にデータベース・インスタンスが失敗する場合があります。

  • データをメモリーから直接読み取ることはできません。ライターは、バックグラウンド・プロセスが変更をディスクに書き込むまで、自身の変更を読み取ることができません。読取り側は、コミットされた変更がディスクに書き込まれるまでその変更を参照できません。

データ消失を回避する必要があるクライアント・アプリケーションでは、ラージ・プールへの書込み後にデータのローカル・コピーを保持する必要があります。クライアントはDBMS_MEMOPTIMIZEパッケージを使用してメモリーへの書込みの永続性を追跡し、DBMS_MEMOPTIMIZE_ADMINパッケージを使用してデータベースを強制的にディスクに書き込むことができます。

関連項目:

ロックのメカニズム

一般に、マルチユーザー・データベースでは、なんらかのデータ・ロックを使用してデータの同時実行性、一貫性、および整合性の問題を解決しています。

ロックは、同じリソースにアクセスする複数のトランザクション間で、破壊的な相互作用が起きないようにするメカニズムです。

ANSI/ISOトランザクション分離レベル

ANSIとISO/IECの両方で採用されているSQL規格では、トランザクション分離レベルが4つ定義されています。これらのレベルは、トランザクション処理のスループットに与える影響の度合いが異なります。

これらの分離レベルは、複数のトランザクションを同時に実行する場合に防ぐ必要がある現象という観点から定義されています。回避可能な現象とは、次のものです。

  • 内容を保証しない読取り

    未コミットのトランザクションが書き込んだデータを、別のトランザクションが読み取る現象。

  • 非リピータブル・リード(ファジー読取り)

    あるトランザクションが以前に読み取ったデータをもう一度読み取ったときに、コミットされた別のトランザクションによってそのデータが変更または削除されたことが明らかになる現象。たとえば、ユーザーがデータに変更があるかどうかを知るためにのみ、ある行を問い合せ、後で再度同じ行を問い合せるという現象などのことです。

  • 仮読取り

    あるトランザクションが検索条件を満たす一連の行を戻す問合せを2回実行する間に、コミットされた別のトランザクションによってその条件を満たす新しい行が挿入されたことが明らかになる現象。

    たとえば、トランザクションから従業員数を問い合せるとします。5分後に同じ問合せを実行したところ、他のユーザーが新規従業員のレコードを挿入したため、戻される従業員数が1人増えています。問合せ基準を満たすデータは増えていますが、ファジー読取りとは異なり、すでに読み取ったデータは変更されていません。

SQL規格では、それぞれの分離レベルで実行されるトランザクションで起こり得る現象という観点で4つの分離レベルを定義しています。表9-1に、これらのレベルを示します。

表9-1 分離レベル別による回避可能な読取り現象

分離レベル 内容を保証しない読取り 非リピータブル・リード 仮読取り

非コミット読取り

可能性あり

可能性あり

可能性あり

コミット読取り

可能性なし

可能性あり

可能性あり

リピータブル・リード

可能性なし

可能性なし

可能性あり

シリアライズ可能

可能性なし

可能性なし

可能性なし

Oracle Databaseには、コミット読取り(デフォルト)およびシリアライズ可能の分離レベルが用意されています。また、読取り専用モードも提供されています。

関連項目:

Oracle Databaseのトランザクション分離レベルの概要

トランザクション分離レベルのANSI規格は、各分離レベルで許可または防止される現象という観点から定義されています。

Oracle Databaseでは、次のトランザクション分離レベルが提供されています。

関連項目:

コミット読取り分離レベル

コミット読取り分離レベルで、トランザクションによって実行される問合せにおいて、トランザクション開始前ではなく、問合せ開始前にコミット済のデータのみが表示されます。

この分離レベルがデフォルトになります。これはトランザクションの競合がほとんど発生しないデータベース環境に適しています。

コミット読取りトランザクション内の問合せでは、問合せ処理中にコミットされるデータは読み取られません。たとえば、100万行を持つ表の問合せ中に、別のトランザクションによって行950,000への更新がコミットされたとすると、この問合せで行950,000を読み取っても、その変更は反映されていません。ただし、問合せによって読み取られたデータを他のトランザクションが変更することは可能であるため、複数の問合せ実行のにデータが変更される可能性があります。このため、同じ問合せを2回実行するトランザクションでは、ファジー読取りと仮読取りが発生する可能性があります。

コミット読取り分離レベルでの読取り一貫性

データベースでは、ユーザーが何もしなくても、あらゆる問合せに対して一貫した結果セットが保証され、データ整合性が保証されます。

UPDATE文のWHERE句などで使用される暗黙的問合せでは、一貫した結果セットが保証されます。ただし、暗黙的問合せ内の各文では、そのDML文自体によって行われた変更は参照されず、変更前に存在したデータが参照されます。

SELECT文のリストにPL/SQLファンクションが含まれる場合、親SQLレベルではなくPL/SQLファンクション・コード内で実行されるSQL文のレベルにおいて、文レベルの読取り一貫性が適用されます。たとえば、ファンクションは、別のユーザーがデータを変更およびコミットした表にアクセスする場合があります。ファンクション内のSELECTの実行ごとに、読取り一貫性を備えた新しいスナップショットが設定されます。

関連項目:

副問合せ

コミット読取りトランザクション内での書込みの競合

コミット読取りトランザクションでは、未コミットの同時実行トランザクションによって更新された行の変更を試行すると、書込みの競合が発生します。

行の変更を防止するトランザクションは、ブロックしているトランザクションと呼ばれる場合があります。コミット読取りトランザクションは、ブロックしているトランザクションが終了して行ロックが解除されるまで待機します。

次の2通りの場合があります。

  • ブロックしているトランザクションがロールバックされた場合、待機しているトランザクションでは、ブロックしているトランザクションが存在していなかったかのように、ロックされていた行が変更されます。

  • ブロックしているトランザクションがコミットされてロックが解除された場合、待機しているトランザクションでは、新たに変更された行に対して更新が実行されます。

次の表に、トランザクション1 (シリアライズ可能トランザクションまたはコミット読取りトランザクション)とコミット読取りトランザクション2との間の相互作用を示します。これは、更新内容の消失と呼ばれる典型的な状況を示しています。トランザクション1の更新内容は、コミットされましたが表に反映されていません。このような更新内容の消失の問題をいかに回避するかは、アプリケーション開発の重要な点です。

表9-2 READ COMMITTEDトランザクションにおける書込みの競合と更新内容の消失

セッション1 セッション2 説明
SQL> SELECT last_name, 
salary FROM employees 
WHERE last_name IN 
('Banda', 'Greene',
'Hintz');
 
LAST_NAME         SALARY
------------- ----------
Banda               6200
Greene              9500
アクションなし。

セッション1で、Banda、GreeneおよびHintzの給与を問い合せます。Hintzという名前の従業員が見つかりません。

SQL> UPDATE employees 
SET salary = 7000 WHERE 
last_name = 'Banda';
アクションなし。

セッション1で、Bandaの給与を更新することによってトランザクションを開始します。トランザクション1のデフォルトの分離レベルは、READ COMMITTEDです。

アクションなし。
SQL> SET TRANSACTION 
ISOLATION LEVEL 
READ COMMITTED;

セッション2でトランザクション2を開始して、分離レベルを明示的にREAD COMMITTEDに設定します。

アクションなし。
SQL> SELECT last_name, 
salary FROM employees 
WHERE last_name IN
('Banda', 'Greene',
'Hintz');
 
LAST_NAME         SALARY
------------- ----------
Banda               6200
Greene              9500

トランザクション2で、Banda、GreeneおよびHintzの給与を問い合せます。Oracle Databaseでは読取り一貫性が使用され、トランザクション1によって行われた未コミットの更新前のBandaの給与を表示します。

アクションなし。
SQL> UPDATE employees 
SET salary = 9900 
WHERE last_name='Greene';

トランザクション1ではBandaの行のみがロックされているため、トランザクション2でGreeneの給与が正常に更新されます。

SQL> INSERT INTO 
employees (employee_id, 
last_name, email, 
hire_date, job_id) 
VALUES (210, 'Hintz', 
'JHINTZ', SYSDATE, 
'SH_CLERK');
アクションなし。

トランザクション1で、従業員Hintzの行を挿入しますが、コミットはしません。

アクションなし。
SQL> SELECT last_name, 
salary FROM employees 
WHERE last_name IN 
('Banda', 'Greene',
'Hintz');
 
LAST_NAME         SALARY
------------- ----------
Banda               6200
Greene              9900

トランザクション2で、従業員Banda、GreeneおよびHintzの給与を問い合せます。

トランザクション2では、このトランザクション自体で更新したGreeneの給与を参照できます。トランザクション2では、トランザクション1によって行われた未コミットのBandaの給与の更新やHintzの挿入は参照できません。

アクションなし。
SQL> UPDATE employees SET
salary = 6300 WHERE 
last_name = 'Banda';

-- prompt does not return 

トランザクション2で、現在トランザクション1によってロックされているBandaの行の更新を試行したため、書込みの競合が発生します。トランザクション2は、トランザクション1の終了を待機します。

SQL> COMMIT;
アクションなし。

トランザクション1では作業をコミットし、トランザクションを終了します。

アクションなし。
1 row updated.
 
SQL>

Bandaの行に対するロックが解除されたため、トランザクション2ではBandaの給与が更新されます。

アクションなし。
SQL> SELECT last_name, 
salary FROM employees 
WHERE last_name IN
('Banda', 'Greene', 
'Hintz');
 
LAST_NAME         SALARY
------------- ----------
Banda               6300
Greene              9900
Hintz

トランザクション2で、従業員Banda、GreeneおよびHintzの給与を問い合せます。トランザクション1によってコミットされたHintzの行の挿入がトランザクション2でも参照可能になりました。トランザクション2では、このトランザクション自体で行ったBandaの給与への更新が表示されます。

アクションなし。
COMMIT;

トランザクション2では作業をコミットし、トランザクションを終了します。

SQL> SELECT last_name, 
salary FROM employees 
WHERE last_name IN 
('Banda', 'Greene',
'Hintz');
 
LAST_NAME         SALARY
------------- ----------
Banda               6300
Greene              9900
Hintz
アクションなし。

セッション1で、Banda、GreeneおよびHintzの行を問い合せます。Bandaの給与は6300ですが、これはトランザクション2で更新された値です。トランザクション1でBandaの給与を7000に更新しましたが、この更新内容は消失しました。

関連項目:

  • 更新内容の消失について学習するには、ロックの使用を参照

  • データベースが行ロックを取得するタイミングと理由を学習するには、行ロック(TX)

シリアライズ可能分離レベル

シリアライズ可能分離レベルでは、問合せ開始時ではなく、トランザクション開始時にコミット済の変更およびこのトランザクション自体が行った変更のみを参照できます。

シリアライズ可能トランザクションは、他のユーザーがデータベースのデータを変更していないように認識される環境内で動作します。シリアライズ可能な分離は、次のような環境に適しています。

  • 大規模データベースを使用し、短いトランザクションでごく少数の行しか更新しない環境。

  • 2つの同時実行トランザクションが同一の行を更新するようなことが比較的少ない環境。

  • 比較的長時間にわたって実行されるトランザクションが主に読取り専用である環境。

シリアライズ可能分離では、通常は文レベルで確保される読取り一貫性が、トランザクション全体に拡張されます。トランザクションによって読み取られるすべての行は、再度読み取っても同じであることが保証されます。トランザクション実行中は、問合せによって戻される結果が同じであることが保証されるため、トランザクションの実行時間に関係なく、他のトランザクションによる変更は問合せで参照できません。シリアライズ可能トランザクションでは、内容を保証しない読取り、ファジー読取りまたは仮読取りの現象は発生しません。

Oracle Databaseの場合、シリアライズ可能トランザクションでは、他のトランザクションで変更され、シリアライズ可能トランザクションが開始されたときにすでにコミットされている行のみ変更できます。シリアライズ可能トランザクションが、その開始にコミットされた別のトランザクションによって変更されたデータの更新または削除を試行すると、データベースでは次のようなエラーが生成されます。

ORA-08177: Cannot serialize access for this transaction

シリアライズ可能トランザクションでORA-08177エラーが発生した場合、アプリケーションでは次のいずれかのアクションを実行できます。

  • 実行した作業をそのポイントまでコミットします。

  • 追加の(異なる)文を実行します(トランザクション内で以前に設定したセーブポイントまでロールバックした後など)。

  • トランザクション全体をロールバックします。

次の表に、シリアライズ可能トランザクションと他のトランザクションとの間の相互作用を示します。シリアライズ可能トランザクションにおいては、シリアライズ可能トランザクションの開始後に他のトランザクションでコミットされた行の変更を試行しなければ、シリアライズ・アクセスの問題を回避できます。

表9-3 シリアライズ可能トランザクション

セッション1 セッション2 説明
SQL> SELECT last_name, salary FROM employees WHERE 
last_name  IN ('Banda', 
'Greene', 'Hintz');
 
LAST_NAME         SALARY
------------- ----------
Banda               6200
Greene              9500

アクションなし。

セッション1で、Banda、GreeneおよびHintzの給与を問い合せます。Hintzという名前の従業員が見つかりません。

SQL> UPDATE employees 
SET salary = 7000 
WHERE last_name='Banda';

アクションなし。

セッション1で、Bandaの給与を更新することによってトランザクション1を開始します。デフォルトの分離レベルはREAD COMMITTEDです。

アクションなし。

SQL> SET TRANSACTION 
ISOLATION LEVEL SERIALIZABLE;

セッション2でトランザクション2を開始して、SERIALIZABLE分離レベルに設定します。

アクションなし。

SQL> SELECT last_name, salary FROM employees 
WHERE last_name IN 
('Banda', 'Greene', 'Hintz');
 
LAST_NAME        SALARY
------------ ----------
Banda              6200
Greene             9500

トランザクション2で、Banda、GreeneおよびHintzの給与を問い合せます。Oracle Databaseでは読取り一貫性が使用され、トランザクション1によって行われた未コミットの更新のBandaの給与を表示します。

アクションなし。

SQL> UPDATE employees 
SET salary = 9900 
WHERE last_name = 'Greene';

Bandaの行のみがロックされているため、トランザクション2でGreeneの給与は正常に更新されます。

SQL> INSERT INTO employees 
(employee_id, last_name, 
email, hire_date, job_id) 
VALUES (210, 'Hintz', 
'JHINTZ', SYSDATE,'SH_CLERK');

アクションなし。

トランザクション1で、従業員Hintzの行を挿入します。

SQL> COMMIT;

アクションなし。

トランザクション1では作業をコミットし、トランザクションを終了します。

SQL> SELECT last_name, salary 
FROM employees 
WHERE last_name IN 
('Banda', 'Greene', 'Hintz');
 
LAST_NAME         SALARY
------------- ----------
Banda               7000
Greene              9500
Hintz
SQL> SELECT last_name, salary FROM employees 
WHERE last_name IN
('Banda', 'Greene', 'Hintz');
 
LAST_NAME        SALARY
------------- ---------
Banda              6200
Greene             9900

セッション1で従業員Banda、GreeneおよびHintzの給与を問い合せると、トランザクション1によってコミットされた変更が表示されます。セッション1では、トランザクション2での未コミットのGreeneの更新は参照できません。

トランザクション2で、従業員Banda、GreeneおよびHintzの給与を問い合せます。Oracle Databaseの読取り一貫性により、トランザクション1でコミットされたHintzの行の挿入およびBandaの更新は、トランザクション2では表示されません。トランザクション2では、このトランザクション自体で行ったGreeneの給与への更新が表示されます。

アクションなし。

COMMIT;

トランザクション2では作業をコミットし、トランザクションを終了します。

SQL> SELECT last_name, salary 
FROM employees 
WHERE last_name IN 
('Banda', 'Greene', 'Hintz');
 
LAST_NAME         SALARY
------------- ----------
Banda               7000
Greene              9900
Hintz
SQL> SELECT last_name, salary
FROM employees 
WHERE last_name IN 
('Banda', 'Greene', 'Hintz');
 
LAST_NAME        SALARY
------------- ---------
Banda              7000
Greene             9900
Hintz

両方のセッションで、Banda、GreeneおよびHintzの給与を問い合せます。両方のセッションで、トランザクション1およびトランザクション2で行われたコミット済のすべての変更を参照できます。

SQL> UPDATE employees 
SET salary = 7100 
WHERE last_name = 'Hintz';

アクションなし。

セッション1で、Hintzの給与を更新することによってトランザクション3を開始します。トランザクション3のデフォルトの分離レベルは、READ COMMITTEDです。

アクションなし。

SQL> SET TRANSACTION 
ISOLATION LEVEL SERIALIZABLE;

セッション2でトランザクション4を開始して、SERIALIZABLE分離レベルに設定します。

アクションなし。

SQL> UPDATE employees 
SET salary = 7200 
WHERE last_name = 'Hintz';

-- prompt does not return

トランザクション4で、Hintzの給与の更新を試みますが、トランザクション3でHintzの行がロックされているためにブロックされます。トランザクション4は、トランザクション3の後のキューに入ります。

SQL> COMMIT;

アクションなし。

トランザクション3でHintzの給与の更新をコミットして、トランザクションを終了します。

アクションなし。

UPDATE employees 
SET salary = 7200 
WHERE last_name = 'Hintz'
*
ERROR at line 1: 
ORA-08177: can't 
serialize access for 
this transaction

コミットしてトランザクション3を終了することによって、トランザクション4におけるHintzの更新はORA-08177エラーが発生して失敗します。この問題は、トランザクション4が開始されたにトランザクション3でHintzの更新をコミットしたことによって発生します。

アクションなし。

SQL> ROLLBACK;

セッション2で、トランザクション4をロールバックして終了します。

アクションなし。

SQL> SET TRANSACTION 
ISOLATION LEVEL SERIALIZABLE;

セッション2でトランザクション5を開始して、SERIALIZABLE分離レベルに設定します。

アクションなし。

SQL> SELECT last_name, 
salary FROM employees 
WHERE last_name IN 
('Banda', 'Greene', 'Hintz');
 
LAST_NAME         SALARY
------------- ----------
Banda               7000
Greene              9900
Hintz               7100

トランザクション5で、Banda、GreeneおよびHintzの給与を問い合せます。トランザクション3によってコミットされたHintzの給与の更新を参照できます。

アクションなし。

SQL> UPDATE employees 
SET salary = 7200 
WHERE last_name='Hintz';

1 row updated.

トランザクション5で、Hintzの給与を異なる値に更新します。トランザクション5が開始されるにトランザクション3でのHintzの更新をコミットしているため、シリアライズ・アクセスの問題は回避されます。

ノート: トランザクション5が開始された後に、別のトランザクションでHintzの行を更新してコミットした場合は、再度シリアライズ・アクセスの問題が発生します。

アクションなし。

SQL> COMMIT;

セッション2で、問題なく更新をコミットして、トランザクションを終了します。

読取り専用分離レベル

読取り専用分離レベルはシリアライズ可能分離レベルと類似していますが、読取り専用トランザクションでは、SYSユーザー以外はトランザクションでデータを変更できません。

読取り専用トランザクションでは、ORA-08177エラーは発生しません。読取り専用トランザクションは、トランザクション開始時点での一貫性を保つ必要があるレポートの生成に役立ちます。

Oracle Databaseでは、必要に応じてUNDOセグメントからデータを再構築することによって、読取り一貫性を実現しています。UNDOセグメントは循環方式で使用されるため、データベースではUNDOデータを上書きできます。長時間実行されるレポートでは、読取り一貫性を保つために必要なUNDOデータが他のトランザクションによって再利用され、snapshot too oldというエラーが発生することがあります。UNDO保存期間とは、古いUNDOデータを上書きしないで保存する最低期間のことで、この期間を設定することにより、この問題を適切に回避できます。

関連項目:

Oracle Databaseのロック・メカニズムの概要

ロックは破壊的な相互作用が起きないようにするメカニズムです。

共有データにアクセスする複数のトランザクション間で不正確なデータの更新または基礎となるデータ構造の不正な変更を引き起こす場合に、相互作用は破壊的になります。ロックは、データベースの同時実行性および一貫性を維持するために非常に重要です。

ロック動作の概要

データベースでは、ロックを取得する操作に応じて、複数のタイプのロックが使用されます。

一般に、データベースでは、排他ロックと共有ロックの2種類のロックが使用されます。排他ロックは、1つのリソース(行や表など)に対して1つのみ取得でき、共有ロックは、1つのリソースに対して複数取得できます。

ロックは、読取り側と書込み側の相互作用に影響を与えます。読取り側とはリソースの問合せを指し、書込み側とはリソースを変更する文を指します。次に、Oracle Databaseにおける、読取り側と書込み側に対するロック動作の概要を示します。

  • 行は、書込み側によって変更される場合にのみロックされます。

    文がある行を更新する場合、そのトランザクションはその行に対してのみロックを取得します。行レベルで表データをロックすることによって、同一のデータに対する競合が最小限に抑えられます。通常(脚注1)、行ロックがブロック・レベルや表レベルに拡大されることはありません。

  • 行の書込み側は、同じ行に対する同時書込みをブロックします。

    あるトランザクションで行を変更している場合は、行ロックによって、他のトランザクションからの同じ行に対する同時変更が防止されます。

  • 読取り側が書込み側をブロックすることはありません。

    行の読取り側は行をロックしないため、書込み側はこの行を変更できます。唯一の例外はSELECT ... FOR UPDATE文で、この文はSELECT文の特殊なタイプであり、読取り中の行をロックします

  • 書込み側が読取り側をブロックすることはありません。

    行が書込み側によって変更されている場合、データベースでは、UNDOデータを使用して読込み側に一貫性のある行のビューが表示されます。

    ノート:

    保留中の分散トランザクションにおける非常に特殊な状況では、データを読み取るために、同じデータ・ブロックへの書込みの待機が必要になることもあります。

関連項目:

ロックの使用

シングル・ユーザーのデータベースでは、情報を変更するのは1人のユーザーのみであるため、ロックは必要ありません。ただし、複数のユーザーがデータにアクセスして変更する場合、データベースには、同じデータに対する同時変更を防止する手段が必要です。

ロックによって、次のようなデータベースの重要な要件を満たすことが可能になります。

  • 一貫性

    あるセッションが表示または変更しているデータは、そのユーザーがデータの使用を終了するまで他のセッションでは変更できません。

  • 整合性

    データおよび構造には、すべての変更内容が適切な順序で反映される必要があります。

Oracle Databaseでは、ロック・メカニズムを使用して、トランザクション間のデータの同時実行性、一貫性および整合性を実現します。ロックは自動的に実行されるため、ユーザーの操作は必要ありません。

ロックの必要性については、単一の行に対する同時更新で説明できます。次の例では、単純なWebベースのアプリケーションによって、エンド・ユーザーに対して従業員の電子メールおよび電話番号が表示されます。アプリケーションでは、次のようなUPDATE文を使用してデータが変更されます。

UPDATE employees
SET    email = ?, phone_number = ?
WHERE  employee_id = ?
AND    email = ?
AND    phone_number = ?

この例のUPDATE文では、WHERE句中の電子メールと電話番号は、特定の従業員についての変更前の元の値です。この更新では、アプリケーションで変更される行は、最後に読み取られてユーザーに表示された後、変更されていないことが保証されます。このようにして、アプリケーションでは、あるユーザーによって行われた変更を他のユーザーが上書きして更新内容が消失する、更新内容の消失の問題が回避されます(更新内容の消失の例は、表9-2を参照)。

表9-4に、2つのセッションがほぼ同時にemployees表の同じ行の変更を試行した場合の一連のイベントを示します。

表9-4 行ロックの例

T セッション1 セッション2 説明

t0

SELECT employee_id as ID, 
       email, phone_number 
FROM   hr.employees 
WHERE  last_name='Himuro';
ID  EMAIL   PHONE_NUMBER
--- ------- ------------
118 GHIMURO 515.127.4565
 

セッション1で、hr1ユーザーがhr.employeesのHimuroのレコードを問い合せて、employee_id(118)、email(GHIMURO)およびphone_number(515.127.4565)属性を表示します。

t1

 
SELECT employee_id as ID,
       email, phone_number 
FROM   hr.employees 
WHERE  last_name='Himuro';
ID  EMAIL   PHONE_NUMBER
--- ------- ------------
118 GHIMURO 515.127.4565

セッション2で、hr2ユーザーがhr.employeesのHimuroのレコードを問い合せて、employee_id(118)、email(GHIMURO)およびphone_number(515.127.4565)属性を表示します。

t2

UPDATE hr.employees SET 
phone_number='515.555.1234'
WHERE employee_id=118
AND email='GHIMURO'
AND phone_number = 
'515.127.4565';

1 row updated.
 

セッション1で、hr1ユーザーがこの行の電話番号を515.555.1234に更新します。この場合、GHIMUROの行に対してロックを取得します。

t3

 
UPDATE hr.employees SET 
phone_number='515.555.1235'
WHERE employee_id=118
AND email='GHIMURO'
AND phone_number = 
'515.127.4565';

-- SQL*Plus does not show
-- a row updated message or
-- return the prompt.

セッション2で、hr2ユーザーが同じ行を更新しようとしますが、現在hr1がその行を処理中であるためブロックされます。

hr2による更新は、hr1による更新とほぼ同時に試行されました。

t4

COMMIT;

Commit complete.
 

セッション1で、hr1ユーザーがトランザクションをコミットします。

このコミットによってHimuroに対する変更が確定され、待機しているセッション2のブロックが解除されます。

t5

 
0 rows updated.

セッション2で、hr2ユーザーは、GHIMUROの行が変更されたため、条件に一致しなくなったことに気付きます。

条件が一致しないため、セッション2ではレコードが更新されません。

t6

UPDATE hr.employees SET 
phone_number='515.555.1235'
WHERE employee_id=118
AND email='GHIMURO' AND 
phone_number='515.555.1234';

1 row updated.
 

セッション1で、hr1ユーザーは、GHIMUROの行を誤った電話番号で更新したことに気付きます。そのためユーザーは新たにトランザクションを開始して、この行の電話番号を515.555.1235に更新し、これによりGHIMUROの行がロックされます。

t7

 
SELECT employee_id as ID, 
       email, phone_number 
FROM   hr.employees 
WHERE  last_name='Himuro';
ID  EMAIL   PHONE_NUMBER
--- ------- ------------
118 GHIMURO 515.555.1234

セッション2で、hr2ユーザーは、hr.employeesのHimuroのレコードを問い合せます。レコードには、t4でセッション1によってコミットされた電話番号の更新内容が表示されます。Oracle Databaseの読取り一貫性によって、セッション2ではt6の未コミットの変更は表示されません。

t8

 
UPDATE hr.employees SET 
phone_number='515.555.1235 
WHERE employee_id=118
AND email='GHIMURO'
AND phone_number =
'515.555.1234';

-- SQL*Plus does not show
-- a row updated message or
-- return the prompt.

セッション2で、hr2ユーザーが同じ行を更新しようとしますが、現在hr1がその行を処理中であるためブロックされます。

t9

ROLLBACK;

Rollback complete.
 

セッション1で、hr1ユーザーがトランザクションをロールバックして終了します。

t10

 
1 row updated.

セッション1による更新がロールバックされたため、セッション2による電話番号の更新は成功します。GHIMUROの行が条件に一致するため、更新は成功します。

t11

 
COMMIT;

Commit complete.

セッション2で、更新をコミットして、トランザクションを終了します。

Oracle Databaseでは、SQL文を実行するときに、必要なロックを自動的に取得します。たとえば、セッションによるデータの変更が許可される前に、最初にデータをロックする必要があります。ロックによってセッションは更新対象データに対する排他制御を取得するため、ロックが解除されるまでは他のトランザクションはロックされたデータを変更できません。

Oracle Databaseのロック・メカニズムはトランザクション制御と密接に結び付けられているため、アプリケーション設計者がトランザクションを適切に定義するのみで、ロックはOracle Databaseによって自動的に管理されます。ユーザーはリソースを明示的にロックする必要はありませんが、Oracle Databaseには、ユーザーが手動でデータをロックするための方法も用意されています。

次の項では、Oracle Databaseでデータの同時実行性が実現される方法を理解するために重要な概念について説明します。

関連項目:

更新内容の消失の防止に役立つサブプログラムを含むOWA_OPT_LOCKパッケージについて学習するには、Oracle Database PL/SQLパッケージ・プロシージャおよびタイプ・リファレンスを参照してください

ロック・モード

Oracle Databaseでは、最高水準のデータ同時実行性を実現しながらフェイルセーフなデータの整合性を確保するため、可能なかぎり低い制限レベルが自動的に使用されます。

制限レベルが低いほど、他のユーザーによるデータへのアクセスがより容易になります。逆に、制限レベルが高いほど、他のトランザクションで取得できるロックのタイプがより制限されます。

Oracle Databaseでは、マルチユーザー・データベースで2つのロック・モードを使用します。

  • 排他ロック・モード

    このモードは、関連リソースの共有を防止します。トランザクションでは、データを変更するときに排他ロックを取得します。リソースを排他的にロックした最初のトランザクションは、その排他ロックが解除されるまで、そのリソースを変更できる唯一のトランザクションになります。

  • 共有ロック・モード

    このモードでは、操作の種類に応じて、関連するリソースの共有が可能です。データの読取りを実行する複数のユーザーはそのデータを共有でき、またそれぞれが共有ロックを保持することによって、排他ロックを必要とする書込みユーザーの同時アクセスを防ぎます。複数のトランザクションが同じリソースについて共有ロックを取得できます。

あるトランザクションで、SELECT ... FOR UPDATE文を使用して単一の表の行を選択するとします。このトランザクションでは、排他行ロックおよび行共有表ロックを取得します。行ロックでは、他のセッションは、ロックされた行以外の任意の行を変更できますが、表ロックでは、他のセッションによる表の構造の変更が禁止されます。したがって、データベースでは、任意の数の文を実行できます。

ロック変換とロックの段階的拡大

Oracle Databaseでは、必要に応じてロック変換が実行されます。

ロック変換は、低い制限の表ロックを、より高い制限の表ロックに自動的に変換します。たとえば、あるトランザクションで特定の従業員に対するSELECT ... FOR UPDATEを発行し、その後ロックされた行を更新するとします。この場合、データベースでは、行共有表ロックが行排他表ロックに自動的に変換されます。トランザクションは、トランザクション内で挿入、更新または削除の対象になる行すべてに対して排他ロックを保持します。行ロックは、制限の最も高い程度で取得されるため、ロック変換はまったく必要なく、実行されません。

ロック変換は、あるレベル(行レベルなど)で多数のロックが保持されている場合に、各ロックをデータベースが上位レベルの粒度(表レベルなど)の別のロックに拡大するロックの段階的拡大とは異なります。あるセッションが表の中の多数の行をロックした場合、データベースによっては、行ロックが単一の表ロックに自動的に拡大される場合があります。ロックの数は少なくなりますが、ロックされている対象の制限は大きくなります。

Oracle Databaseでは、ロックの段階的拡大が発生することはありません。ロックの段階的拡大はデッドロックの可能性を大幅に増します。たとえば、システムがトランザクション1のためにロックの拡大を試行しますが、トランザクション2によって保持されているロックのために拡大できないとします。トランザクション2の処理を続行するために同じデータのロックの段階的拡大が必要な場合、デッドロックが発生します。

ロックの期間

Oracle Databaseでは、なんらかのイベントが発生し、トランザクションがそのリソースを必要としなくなると、ロックは自動的に解除されます。

通常、トランザクション内の文で取得したロックは、トランザクションの存続期間中保持されます。これらのロックによって、同時トランザクションによる内容を保証しない読取り、更新内容の消失、有害なDDLなどの破損を招く干渉を防止できます。

ノート:

外部キーに索引が作成されていないために子表に対して設定される表ロックは、トランザクション存続期間中ではなく、文の存続期間中のみ保持されます。また、DBMS_LOCKパッケージを使用すると、ユーザー定義のロックを自由に割当ておよび解除でき、トランザクションの境界を越えて保持することもできます。

Oracle Databaseでは、トランザクションをコミットまたはロールバックすると、そのトランザクション内の文で取得したすべてのロックが解除されます。また、Oracle Databaseでは、セーブポイントまでロールバックすると、そのセーブポイントより後で取得されたロックが解除されます。ただし、ロックを解除されて使用可能となったリソースに対してロックを取得できるのは、ロック中だったリソースを待機していなかったトランザクションのみです。ロック中のリソースを待機していたトランザクションは、元のトランザクションが完全にコミットまたはロールバックされるまで待機し続けます。

関連項目:

ロックとデッドロック

デッドロックとは、2人以上のユーザーが、相手がロックしているデータをお互いに待機している状況を指します。デッドロックが発生すると、トランザクションの処理を継続できなくなる場合があります。

Oracle Databaseでは、デッドロックを自動的に検出し、そのデッドロックに関係する文の一方をロールバックして、競合する一連の行ロックを解放することにより、デッドロックを解決します。また、データベースでは、文レベルのロールバックが実行されたトランザクションに対して対応するメッセージが戻されます。ロールバックされる文は、デッドロックが検出されたトランザクションに属しています。通常、通知されたトランザクションは明示的にロールバックする必要がありますが、待機した後でロールバック文を再試行できます。

表9-5に、デッドロック状態の2つのトランザクションを示します。

表9-5 デッドロック・トランザクション

T セッション1 セッション2 説明

t0

SQL> UPDATE employees 
  SET salary = salary*1.1
  WHERE employee_id = 100;

1 row updated.
SQL> UPDATE employees
  SET  salary = salary*1.1
  WHERE employee_id = 200;

1 row updated.

セッション1でトランザクション1を開始して、従業員100の給与を更新します。セッション2でトランザクション2を開始して、従業員200の給与を更新します。各トランザクションでは、そのトランザクションで更新する行のみがロックされるため、問題は発生しません。

t1

SQL> UPDATE employees 
  SET salary = salary*1.1
  WHERE employee_id = 200;

-- prompt does not return
SQL> UPDATE employees
  SET salary = salary*1.1
  WHERE employee_id = 100;

-- prompt does not return

トランザクション1で、現在トランザクション2でロックされている従業員200の行の更新を試行します。トランザクション2で、現在トランザクション1でロックされている従業員100の行の更新を試行します。

どちらのトランザクションでも、処理を続行または終了するために必要なリソースを取得できないため、デッドロックが発生します。各トランザクションがいくら待機しても、競合するロックは保持されたままです。

t2

UPDATE employees
       *
ERROR at line 1:
ORA-00060: deadlock detected
while waiting for resource

SQL>
 

トランザクション1でデッドロックが検出されて、t1で発行されたUPDATE文がロールバックされます。ただし、t0で行われた更新はロールバックされません。セッション1にプロンプトが戻されます。

ノート: デッドロック状態になっているセッションのうちの一方のみが実際にデッドロック・エラーを受信しますが、どちらのセッションもエラーになる可能性があります。

t3

SQL> COMMIT;

Commit complete.
 

セッション1で、t0で行った更新をコミットして、トランザクション1を終了します。t1で正常に行われなかった更新はコミットされません。

t4

 
1 row updated.

SQL>

トランザクション1によってブロックされていた、t1でのトランザクション2による更新が実行されます。プロンプトが戻されます。

t5

 
SQL> COMMIT;

Commit complete.

セッション2で、t0およびt1において行った更新をコミットして、トランザクション2を終了します。

デッドロックが最も頻繁に発生するのは、トランザクションがOracle Databaseのデフォルト・ロックを明示的にオーバーライドする場合です。Oracle Databaseではロックの段階的拡大は実行されず、問合せで読取りロックは使用されませんが、行レベルのロック(ページ・レベルのロックではありません)のみは使用されるため、デッドロックはほとんど起こりません。

関連項目:

自動ロックの概要

Oracle Databaseのトランザクションでは、同じリソースに対する排他的アクセスを必要とする処理が他のトランザクションで行われないように、自動的にリソースがロックされます。

データベースは、リソースおよび実行される操作に応じて、様々な制限レベルの様々なタイプのロックを自動的に取得します。

ノート:

単純な読取り時に行がロックされることはありません。

Oracle Databaseのロックは、次の表に示すカテゴリに分類されます。

表9-6 ロックのカテゴリ

ロック 説明 さらに学習するには

DMLロック

データを保護します。たとえば、表ロックは表全体をロックし、行ロックは選択された行をロックします。

DMLロック

DDLロック

スキーマ・オブジェクトの構造、たとえば表とビューのディレクトリ定義を保護します。

DDLロック

システム・ロック

データファイルなどの内部データベース構造を保護します。ラッチ、mutexおよび内部ロックは完全に自動的です。

システム・ロック

DMLロック

DMLロックは、データ・ロックとも呼ばれ、複数のユーザーが同時にアクセスするデータの整合性を保証します。

たとえば、DMLロックでは、オンライン書店で最後の1冊を2人の顧客が購入可能になることを防止できます。DMLロックは、同時に実行される矛盾する複数のDML操作またはDDL操作の破損を招く干渉を防ぎます。

DML文では、次のタイプのロックを自動的に取得します。

後続の項において、それぞれのタイプのロックまたはロック・モードの後のカッコ内にある頭字語は、Oracle Enterprise Manager(Enterprise Manager)のロック・モニターで使用される略称です。Enterprise Managerでは、表ロックのモード(RSやSRXなど)が示されるかわりに、すべての表ロックにTMと表示される可能性があります。

関連項目:

Oracle Enterprise Manager

行ロック(TX)

行ロックは、TXロックとも呼ばれ、表の単一の行に対するロックです。トランザクションでは、INSERTUPDATEDELETEMERGEまたはSELECT ... FOR UPDATE文によって変更される各行で行ロックを取得します。行ロックは、トランザクションがコミットされるかロールバックされるまで保持されます。

行ロックは、主に2つのトランザクションによる同じ行の変更を防止するためのキューイング・メカニズムとして機能します。変更される行は常に排他モードでロックされるため、ロックを保持しているトランザクションがコミットまたはロールバックされるまで、他のトランザクションはその行を変更できません。行ロックでは、最もきめの細かいロックが実現されるため、最高の同時実行性とスループットが得られます。

ノート:

データベースのインスタンス障害が原因でトランザクションが終了した場合は、トランザクション全体がリカバリされる前に、ブロックレベルのリカバリによって行が使用可能になります。

ある行のロックを取得したトランザクションは、その行を含む表のロックも取得します。表ロックがあると、現行のトランザクションでのデータ変更をオーバーライドするDDL操作の競合が回避されます。図9-2に、表の行3を更新する例を示します。Oracle Databaseでは、更新される行には排他ロックが、表には副排他ロックが自動的に設定されます。

図9-2 行ロックと表ロック

図9-2の説明が続きます
「図9-2 行ロックと表ロック」の説明
行ロックと同時実行性

このシナリオは、Oracle Databaseが同時実行性のために行ロックを使用する方法を示しています。

3つのセッションから同時に同じ行を問い合せます。セッション1およびセッション2では異なる行に対してコミットせずに更新しますが、セッション3では更新は行われません。各セッションでは、そのセッション自体で行った未コミットの更新を参照できますが、他のセッションによる未コミットの更新は参照できません。

表9-7 データ同時実行性の例

T セッション1 セッション2 セッション3 説明

t0

SELECT employee_id,
       salary
FROM   employees
WHERE  employee_id
IN ( 100, 101 );
EMPLOYEE_ID  SALARY
-----------  ------
100          512
101          600
SELECT employee_id,
       salary 
FROM   employees
WHERE  employee_id
IN ( 100, 101 );
EMPLOYEE_ID  SALARY
-----------  ------
100          512
101          600
SELECT employee_id, 
       salary 
FROM   employees
WHERE  employee_id
IN ( 100, 101 );
EMPLOYEE_ID  SALARY
-----------  ------
100          512
101          600

3つの異なるセッションから、従業員100および101のIDと給与を同時に問い合せます。それぞれの問合せからは、同じ結果が戻されます。

t1

UPDATE hr.employees
SET salary = 
      salary+100
WHERE 
  employee_id=100;
   

セッション1で従業員100の給与を更新しますが、コミットはしません。更新時には、書込み側は更新する行に対してのみ行レベル・ロックを取得して、他の書込み側からこの行を変更されないようにします。

t2

SELECT employee_id,
       salary 
FROM   employees 
WHERE  employee_id
IN ( 100, 101 );
EMPLOYEE_ID  SALARY
-----------  ------
100          612
101          600
SELECT employee_id,
       salary
FROM   employees
WHERE  employee_id
IN ( 100, 101 );
EMPLOYEE_ID  SALARY
-----------  ------
100          512
101          600
SELECT employee_id, 
       salary 
FROM   employees
WHERE  employee_id
IN ( 100, 101 );
EMPLOYEE_ID  SALARY
-----------  ------
100          512
101          600

各セッションが、同時に元の問合せを発行します。セッション1では、t1での更新結果が反映された給与612が表示されます。セッション2およびセッション3における読取りでは、セッション1のトランザクション終了を待機せず、即座に行が戻されます。データベースでは、セッション1での更新前の状態の給与を表示するためにマルチバージョン読取り一貫性が使用されます。

t3

 
UPDATE hr.employee
  SET  salary =
         salary+100
  WHERE 
    employee_id=101;
 

セッション2で従業員101の給与を更新しますが、トランザクションをコミットしません。更新時には、書込み側は更新する行に対してのみ行レベル・ロックを取得して、他のセッションからこの行が変更されないようにします。

t4

SELECT employee_id,
       salary 
FROM   employees 
WHERE  employee_id
IN ( 100, 101 );
EMPLOYEE_ID  SALARY
-----------  ------
100          612
101          600
SELECT employee_id,
       salary
FROM   employees 
WHERE  employee_id
IN ( 100, 101 );
EMPLOYEE_ID  SALARY
-----------  ------
100          512
101          700
SELECT employee_id, 
       salary 
FROM   employees
WHERE  employee_id
IN ( 100, 101 );
EMPLOYEE_ID  SALARY
-----------  ------
100          512
101          600

各セッションが、同時に元の問合せを発行します。セッション1では、t1で更新された給与612が表示されますが、セッション2で更新された従業員101の給与は表示されません。セッション2の読取り側では、セッション2によって更新された給与が表示されますが、セッション1で更新された給与は表示されません。セッション3での読取りでは、読取り一貫性を使用して、セッション1およびセッション2で変更される前の給与が表示されます。

関連項目:

行ロックの記憶域

ロック・マネージャを使用してロックのリストをメモリー上に保持する一部のデータベースとは異なり、Oracle Databaseでは、ロックされた行を含むデータ・ブロックにロック情報を格納します。

Oracle Databaseでは、キューイング・メカニズムを使用して、行ロックを取得します。トランザクションで未ロックの行に対するロックが必要になった場合は、データ・ブロックにロックが配置されます。このトランザクションで変更されるそれぞれの行は、ブロック・ヘッダーに格納されたトランザクションIDのコピーをポイントします。

トランザクションが終了しても、トランザクションIDはそのままブロック・ヘッダーに残ります。別のトランザクションで行が変更される場合は、そのトランザクションIDを使用してロックがアクティブであるかどうかが判断されます。ロックがアクティブである場合は、ロックが解除されたときにセッションに通知されるように設定されます。ロックがアクティブでない場合は、そのトランザクションがロックを取得します。

関連項目:

表ロック(TM)

表ロックTMロックとも呼ばれ、INSERTUPDATEDELETEMERGEFOR UPDATE句付きのSELECTまたはLOCK TABLE文で表を変更する場合にトランザクションに取得されます。

DML操作では、トランザクションのために表へのDMLアクセスを確保して、このトランザクションと競合する可能性のあるDDL操作を防止するために表ロックが必要になります。

表ロックは、次のいずれかのモードで保持できます。

  • 行共有(RS)

    このロックは、副共有表ロック(SS)とも呼ばれ、表に対するロックを保持しているトランザクションがその表の行をロック済であり、それらの行を更新する予定であることを示しています。行共有ロックは、最も制限の緩やかなモードの表ロックであり、最高水準の同時実行性を表にもたらします。

  • 行排他表ロック(RX)

    このロックは、副排他表ロック(SX)とも呼ばれ、一般的にこのロックを保持しているトランザクションが表の行を更新したか、SELECT ... FOR UPDATEを発行したことを示しています。SXロックでは、他のトランザクションが同じ表の行に対して問合せ、挿入、更新、削除またはロックを実行できます。そのため、SXロックでは、複数のトランザクションが同じ表に対して同時にSXロックおよび副共有表ロックを取得できます。

  • 共有表ロック(S)

    トランザクションで共有表ロックを取得すると、他のトランザクションは表に対するSELECT ... FOR UPDATEを使用しない問合せを実行できますが、更新は単一のトランザクションが共有表ロックを保持している場合にのみ許可されます。複数のトランザクションが共有表ロックを同時に保持することがあるため、トランザクションで表を変更できるかどうかは、このロックを保持しているのみでは保証されません。

  • 共有行排他表ロック(SRX)

    このロックは、共有副排他表ロック(SSX)とも呼ばれ、共有表ロックよりも多くの制限を課します。特定の表に対して、同時に1つのトランザクションのみがSSXロックを取得できます。トランザクションでSSXロックを取得すると、他のトランザクションは表に対してSELECT ... FOR UPDATE以外の問合せを実行できますが、表は更新できません。

  • 排他表ロック(X)

    このロックは、最も制限が強くなります。他のトランザクションは、一切のDML文を実行できなくなり、表に一切のロックをかけられなくなります。

関連項目:

ロックと外部キー

Oracle Databaseでは、親キーとそれに依存する外部キーとの関連において、同時実行性制御が最適化されます。

ロックの動作は、外部キーの列が索引付けされているかどうかに依存します。外部キーが索引付けされていない場合、子表が頻繁にロックされ、デッドロックが発生し、同時実行性が低下する可能性があります。このため外部キーは、ほとんどの場合、索引付けが必要です。唯一の例外は、対応する一意キーまたは主キーの更新や削除が発生しないことが確実な場合です。

ロックと索引のない外部キー

子表の外部キー列に索引が存在せず、セッションによって(行の削除や主キーの属性の変更などで)親表の主キーが変更されるか、親表に行がマージされた場合、データベースは子表に対する完全表ロックを取得します。

次の両方の条件に当てはまる場合、データベースは子表に対する完全表ロックを取得します。

  • 子表の外部キー列に索引が存在しない場合。

  • セッションで、親表の主キーが変更されるか(行の削除や主キーの属性の変更など)、親表に行がマージされた場合。

ノート:

親表に挿入しても、子表に対するDMLを阻止する、ブロックしている表ロックは取得しません。挿入の場合、データベースは子表に対するロックを取得します。これにより、構造変更は阻止されますが、既存の行または新規に追加された行の変更は阻止されません。

hr.departments表が、索引付けされていない外部キーemployees.department_idを含むhr.employeesの親である場合など。次の図に、departments表の部門60の主キー属性を変更するセッションを示します。

図9-3 外部キーの索引がない場合のロック・メカニズム

図9-3の説明が続きます
「図9-3 外部キーの索引がない場合のロック・メカニズム」の説明

図9-3では、データベースは、部門60の主キーの変更時にemployeesに対する完全表ロックを取得しています。このロックによって、他のセッションはemployees表の問合せはできますが、更新できなくなります。たとえば、セッションでは従業員の電話番号を更新できません。employeesに対する表ロックはdepartments表への主キーの変更が完了すると、即時に解除されます。departmentsの複数の行で主キーの変更が実行される場合、employeesに対する表ロックは、departmentsで変更される行ごとに取得および解除されます。

ノート:

子表に対するDMLでは、親表の表ロックは取得されません。

ロックと索引付き外部キー

子表の外部キー列が索引付けされていて、セッションによって(行の削除や主キーの属性の変更などで)親表の主キーが変更されるか、親表に行がマージされた場合、データベースは子表に対する完全表ロックを取得しません

親表に対するロックにより、トランザクションは排他表ロックを取得できませんが、主キーの変更時に、親表または子表に対するDMLは阻止されません。この状況は、子表への更新が発生している間に、親表での主キーの変更が発生する場合に適しています。

図9-4に、department_id列が索引付けられた子表employeesを示します。トランザクションにより、部門280がdepartmentsから削除されます。この削除により、「ロックと索引のない外部キー」に示すようにデータベースがemployees表に対する完全表ロックを取得することはありません。

図9-4 外部キーが索引付けされている場合のロック・メカニズム

図9-4の説明が続きます
「図9-4 外部キーが索引付けされている場合のロック・メカニズム」の説明

子表でON DELETE CASCADEを指定すると、親表からの削除によって、子表からも削除することができます。たとえば、部門280を削除した場合、削除された部門の従業員に関するレコードをemployeesから削除できます。この場合、待機とロックに関するルールは、親表から行を削除した後に子表から行を削除する場合と同じです。

関連項目:

DDLロック

データ・ディクショナリ・ロック(DDLロック)によって、DDL操作によるスキーマ・オブジェクトの処理中または参照中に、そのオブジェクトの定義が保護されます。

DDL操作中には、修正や参照の対象となる個々のスキーマ・オブジェクトのみがロックされます。データ・ディクショナリ全体がロックされることはありません。

Oracle Databaseは、DDLロックを必要とするDDLトランザクションのために、DDLロックを自動的に取得します。ユーザーはDDLロックを明示的に要求できません。たとえば、ユーザーがストアド・プロシージャを作成する場合、Oracle Databaseはプロシージャ定義内で参照されるすべてのスキーマ・オブジェクトについてのDDLロックを自動的に取得します。DDLロックにより、プロシージャのコンパイル完了前にそれらのオブジェクトの変更または削除が防止されます。

排他DDLロック

排他DDLロックは、他のセッションによるDDLロックまたはDMLロックの取得を防止します。

ほとんどのDDL操作では、同じスキーマ・オブジェクトを変更または参照する可能性のある他のDDL操作によって破壊的な干渉が起きないように、リソースの排他DDLロックが必要です。たとえば、ALTER TABLEで表に列を追加している間は、DROP TABLE操作でその表を削除できず、その逆も同様です。

排他ロックは、DDL文実行の継続中と自動コミット中ずっと存続します。排他DDLロックの取得では、別の操作によってそのスキーマ・オブジェクトに対して別のDDLロックが保持されている場合、その取得は古いDDLロックが解除されるまで待機し、その後に処理されます。

関連項目:

共有DDLロックでは、破壊的な干渉を防止するうえで排他ロックが不要である状況について説明します

共有DDLロック

リソースに対する共有DDLロックでは、競合するDDL操作によって破壊的な干渉が防止されますが、同様のDDL操作でのデータ同時実行性は確保されます。

たとえば、CREATE PROCEDURE文の実行時には、その文を含むトランザクションは、参照されるすべての表について共有DDLロックを取得します。他のトランザクションは同じ表を参照するプロシージャを同時に作成できるため、同じ表について同時実行の共有DDLロックの取得はできますが、参照中の表について排他DDLロックを取得できるトランザクションはありません。

共有DDLロックは、DDL文実行の継続中と自動コミット中ずっと存続します。その結果、共有DDLロックを保持するトランザクションでは、参照中のスキーマ・オブジェクトの定義がそのトランザクションの継続時間にわたって変化しないことが保証されます。

ブレーク可能解析ロック

SQL文またはPL/SQLプログラム・ユニットは、参照する各スキーマ・オブジェクトについて解析ロックを保持します。

参照オブジェクトが変更または削除された場合に、対応する共有SQL領域を無効にできるようにするため、解析ロックが取得されます。解析ロックは、どのようなDDL操作も拒否せず、一時的に解除して競合するDDL操作を実行することも許可するために、ブレーク可能解析ロックと呼ばれます。

解析ロックは、SQL文実行の解析フェーズ中に共有プールに取得されます。解析ロックは、その文の共有SQL領域が共有プールに残っているかぎり保持されます。

関連項目:

共有プール

システム・ロック

Oracle Databaseでは、様々なタイプのシステム・ロックを使用して、内部データベースおよびメモリー構造を保護します。ユーザーは、システム・ロックの出現や継続期間を制御できないため、このメカニズムにアクセスできません。

ラッチ

ラッチは、共有データ構造、オブジェクトおよびファイルへのマルチユーザー・アクセスを調整する、単純な低レベルのシリアライズ化メカニズムです。

ラッチによって、複数のプロセスからアクセスされる共有メモリー・リソースが破損しないように保護されます。特に、ラッチは次の状況からデータ構造を保護します。

  • 複数のセッションによる同時変更

  • あるセッションで変更中の場合の他のセッションによる読取り

  • アクセス中のメモリーの割当て解除(エージ・アウト)

通常、1つのラッチによってSGA内の複数のオブジェクトが保護されます。たとえば、DBWやLGWRなどのバックグラウンド・プロセスでは、共有プールのメモリーを割り当ててデータ構造を作成します。このメモリーを割り当てるために、これらのプロセスでは、アクセスをシリアライズする共有プール・ラッチを使用して、2つのプロセスが共有プールを同時に検査または変更できないようにします。メモリーが割り当てられた後、解析に必要なライブラリ・キャッシュなどの共有プール領域に他のプロセスがアクセスする必要がある場合があります。この場合、共有プール全体ではなくライブラリ・キャッシュのみがラッチされます。

行ロックなどのエンキュー・ラッチとは異なり、ラッチではセッションのキューイングは許可されません。ラッチが使用可能になると、ラッチを要求する最初のセッションがラッチへの排他的アクセスを取得します。プロセスがループ内のラッチを繰り返し要求するとラッチ・スピンが発生し、プロセスがラッチ要求を更新する前にCPUを解放するとラッチ・スリープが発生します。

通常、Oracleプロセスは、データ構造の1つを操作または参照する、非常に短い間のみラッチを取得します。たとえば、1人の従業員の給与更新の処理中に、データベースは数千のラッチを取得および解除する場合があります。ラッチの実装(特に、プロセスがラッチを待機するかどうか、およびどのくらいの時間待機するか)は、オペレーティング・システムに依存します。

ラッチの数を増やすと、同時実行可能な操作の数が減少します。たとえば、ハード解析操作を過度に行うと、ライブラリ・キャッシュ・ラッチの競合が発生します。V$LATCHビューには、各ラッチが要求された回数や待機された回数を含む、各ラッチの詳細な使用統計が含まれています。

関連項目:

mutex

相互排他オブジェクト(mutex)は、メモリー内のオブジェクトが同時プロセスでアクセスされた場合にエージ・アウトまたは破損しないようにするための低レベルのメカニズムです。mutexはラッチに似ていますが、通常、ラッチではオブジェクトのグループが保護されるのに対して、mutexでは単一のオブジェクトが保護されます。

mutexには、複数の利点があります。

  • mutexによって、競合の可能性が減少します。

    ラッチでは複数のオブジェクトを保護するため、複数のプロセスで保護対象のオブジェクトのいずれかへの同時アクセスが試行された場合に、ボトルネックが生じる可能性があります。mutexでは、グループではなく個別のオブジェクトへのアクセスをシリアライズするため、可用性が向上します。

  • mutexでは、ラッチよりも少ないメモリーが使用されます。

  • 共有モードでmutexを使用すると、複数のセッションからの同時参照が可能になります。

内部ロック

内部ロックは、ラッチやmutexよりも高レベルで複雑なメカニズムであり、様々な目的に使用されます。

データベースでは、次のタイプの内部ロックを使用します。

  • ディクショナリ・キャッシュ・ロック

    これらのロックの継続時間は非常に短く、ディクショナリ・キャッシュ内のエントリが修正されたり使用されている間、そのエントリについて保持されます。これらのロックは、解析されている文が矛盾したオブジェクト定義を参照しないことを保証します。ディクショナリ・キャッシュ・ロックは、共有または排他で保持されます。共有ロックは解析が完了すると解除されますが、排他ロックはDDL操作が完了したときに解除されます。

  • ファイルとログの管理ロック

    これらのロックは様々なファイルを保護します。たとえば、ある内部ロックは制御ファイルを保護し、一度に1つのプロセスのみがそれを変更できるようにします。別のロックは、オンラインREDOログ・ファイルの使用とアーカイブを調整します。データベースを複数インスタンスが共有モードでマウントしたり、1つのインスタンスが排他モードでマウントすることを保証するために、データファイルがロックされます。ファイルとログのロックはファイルの状態を示すため、必然的に長い間保持されます。

  • 表領域とUNDOセグメントのロック

    これらのロックは、表領域とUNDOセグメントを保護します。たとえば、データベースにアクセスするすべてのインスタンスは、表領域のオンライン/オフラインの状態について一致している必要があります。UNDOセグメントは、1つのセグメントに1つのデータベース・インスタンスしか書き込めないようにロックされています。

手動データ・ロックの概要

Oracle Databaseでは、手動でデフォルトのロック・メカニズムを置き換えることもできます。

データの同時実行性、整合性および文レベルの読取り一貫性を確保するために、Oracle Databaseは自動的にロックを実行します。ただし、デフォルト・ロックの置換えは、次のような状況で有効です。

  • アプリケーションで、トランザクション・レベルの読取り一貫性またはリピータブル・リードが必要な場合。

    この場合、問合せによって作成されるデータが、他のトランザクションによる変更を反映せず、トランザクションの継続時間にわたって一貫性を維持する必要があります。トランザクション・レベルの読取り一貫性は、明示的ロック、読取り専用トランザクションまたはシリアライズ可能トランザクションを使用するか、デフォルトのロックをオーバーライドすると実現できます。

  • アプリケーションで、あるトランザクションが他のトランザクションの完了まで待機せずに済むように、そのトランザクションがリソースに排他的アクセスできるようにする必要がある場合。

Oracle Databaseの自動ロックは、セッション・レベルまたはトランザクション・レベルでオーバーライドできます。セッション・レベルでは、ALTER SESSION文を使用して必要なトランザクション分離レベルを設定できます。トランザクション・レベルでは、次のSQL文を含むトランザクションによってOracle Databaseのデフォルト・ロックがオーバーライドされます。

  • SET TRANSACTION ISOLATION LEVEL

  • LOCK TABLE文(表をロックする文、またはビューを使用するときに実表をロックする文)

  • SELECT ... FOR UPDATE

前述の文で取得したロックは、トランザクション終了後に解除されるか、セーブポイントへのロールバックによって解除されます。

Oracle Databaseのデフォルト・ロックを任意のレベルで置き換える場合、データベース管理者やアプリケーション開発者は、ロック置換の手順が確実に正しく実行されるようにしてください。ロックの手順は、データ整合性が保証される、データ同時実行性が許容範囲内である、デッドロックは発生する可能性がないかまたは適切に処理される、などの基準を満たすものにする必要があります。

関連項目:

ユーザー定義ロックの概要

Oracle Databaseのロック・マネージメント・サービスを使用すると、特定のアプリケーションで独自のロックを定義できます。

たとえば、ファイルシステム上のメッセージ・ログに対するアクセスをシリアライズするためのロックを作成できます。確保したユーザー・ロックは、Oracle Databaseロックと同一とみなされるため、デッドロックの検出などのOracle Databaseロックの機能をすべて備えています。ユーザー・ロックは接頭辞ULで識別されるため、Oracle Databaseロックと矛盾することはありません。

Oracle Databaseのロック・マネージメント・サービスは、DBMS_LOCKパッケージ内のプロシージャを介して利用できます。PL/SQLブロックには、次の処理を行う文を含めることができます。

  • 特定のタイプのロックを要求します。

  • そのロックに、同一のインスタンスまたは別のインスタンス内にある別のプロシージャからも識別できる、一意の名前を指定します。

  • ロック・タイプを変更します。

  • ロックを解除します

関連項目:



脚注の凡例

脚注1:

分散トランザクションの2フェーズ・コミットを処理する場合、特殊な状況では、データベースで読取りアクセスが一時的に禁止されることがあります。具体的には、問合せが準備フェーズとコミット・フェーズの間に開始され、コミット前にデータの読取りを試みる場合、データベースでは、読取りの整合性を保証するため、行レベルからブロック・レベルにロックが拡大されます。