29 ロックフリー予約の使用
この章では、データベース・アプリケーションにおけるロックフリー予約の使用方法について説明します。
トピック:
29.1 トランザクション処理での同時実行性について
通常、トランザクション処理には、古い値を新しい値に置換するデータ更新を伴うフラット・トランザクションが含まれます。ほとんどのアプリケーションでは、製品の手持数量、銀行口座残高、株式、使用可能な座席数などの数値および集計値が格納されます。このような数値集計データは、単一のエンティティとして参照されますが、1つ以上の同じデータまたは類似データに基づきます。このようなデータには、"データ←値"形式の代入ではなく、値の減算または加算が含まれます。たとえば、銀行口座残高は、口座で発生する借方と貸方トランザクションに基づきます。
在庫管理、サプライ・チェーン、金融または投資銀行、株式、旅行および娯楽を提供するアプリケーションは、数値集計データに基づいて動作します。数値集計データは一般に"ホット"リソースとして識別されます。これは、アプリケーションが継続的に、このようなデータを繰り返し読み取ったり更新するためです。このようなデータに多数のトランザクションが同時にアクセスする可能性は高いです。2フェーズ・ロック(2PL)などの従来のロック・プロトコルでは、同時トランザクションの実行は、トランザクションが行の更新を開始してロックした後にシリアライズされます。開始された更新は、トランザクションがコミットまたはロールバックでファイナライズした場合にのみ完了します。シリアル化は、行をロックしたトランザクションが完了するまで、他の同時実行トランザクションによる行へのアクセスをブロックします。同時トランザクションによるデータへのアクセスを許可する場合は、アプリケーションの正確性を維持するためにトランザクションを制御する必要があります。
マイクロサービス・アプリケーションなどの長時間実行トランザクションでは、リソースは長期間ロックされたままになり、ホット・リソースになる可能性があります。長期リソース・ロックにより、同時実行性が制限されます。旅行予約サービスなどのサービスを提供するマイクロサービス・アプリケーションでは、長時間実行トランザクションでフライト、ホテルおよび車の予約をすることがあります。このようなトランザクションは通常、Sagaとして処理され、Sagaファイナライズまでデータベースがロックされた状態で様々なサービスにまたがります。
通常の過程で同時実行性をどのように処理するかを理解するために、オンライン・ショッピング・カートの例を見てみましょう。ショッピング・カートには、販売前にカートに追加されたアイテムがあります。アプリケーションは、ユーザーが商品をカートに入れると、その商品は他の購入者が入手できなくなるが、まだ売れ残っている、というような、様々な状態での複数のトランザクションを処理できる必要があります。同時トランザクションの場合、在庫の状態および棚卸の管理は複雑になり、複数のトランザクションでカートへのアイテムの追加、カートのチェックアウトや放棄が行われます。コミットまたはロールバックで数量を変更するには、トランザクションの在庫または同類のフィールドをロックする必要があります。長期間データをロックすると、ロックが解除されるまで、他の同時トランザクションはそのアイテムにアクセスできなくなります。したがって、分離を目指し、アプリケーションでトランザクションを個別に実行できるようにすると、同時実行性が制限されます。
多くのビジネス・アプリケーションでは、"ホット"データへの同時アクセスをブロックすると、パフォーマンスに深刻な影響を及ぼし、ユーザー・エクスペリエンスとスループットが低下する可能性があります。トランザクションの原子性、一貫性および永続性の各プロパティを維持しながら、独立性を減じて同時実行性を向上させると、アプリケーションはその恩恵を受ける可能性があります。同時実行性を向上させるには、トランザクション・ライフサイクル中にホット・リソースの状態を捉えて、リソース値が変更される場合にのみデータ・ロックを有効にすることが重要です。
関連項目:
-
予約可能列を使用してトランザクション処理の同時実行性を制御する方法の詳細は、「ロックフリー予約」を参照してください。
29.2 ロックフリー予約の用語
数値集計データ
"データ←値"形式の代入ではなく、数量(数値データ)の減算(消費、減少)または加算(補充、増加)が含まれるデータ。数値集計データの操作は、本質的に交換可能です。
ホット・データまたはホット・リソース
頻繁な読取りおよび更新が必要なトランザクションから高いトラフィックを受け取るデータまたはリソース、または長時間実行トランザクションで読取りまたは更新が行われるリソース。
トランザクション・ライフサイクル
作成からコミットまたはロールバックの状態に遷移する際のビジネス・トランザクションの状態。
補正または補正トランザクション
Sagaの一部でもある他のトランザクションのいずれかが失敗した場合、補正または補正トランザクションが、Sagaのすでにコミットされたトランザクションを補正(ロールバック)します。
予約可能
予約可能は、数値データ型の列に対して定義できる列プロパティです。予約可能列は、列に対して行われた変更のジャーナルを保持します。このようなジャーナルは、同時実行性の制御および自動補正に使用されます。
予約可能列
ホット・リソースが含まれ、ロックフリー予約で識別される列。このような列は、reservable
列プロパティ・キーワードで宣言されます。
予約可能更新
予約可能列に対して行われた数値集計データの更新。
予約ジャーナル
予約ジャーナルは、ユーザー表に関連付けられた表であり、予約可能列に変更(現在の値に対するデルタ量分だけの増減)を記録します。
ロックフリー予約
予約可能更新はロックフリー予約として扱われ、対象となる行を更新するトランザクションは行をロックしませんが、予約可能列の値をデルタ量分だけ変更する意図を示すことを意味します。変更操作は、予約ジャーナルに記録されます。ロックフリー予約は、トランザクションのコミット時に実際の更新に変化します。
ロックフリー予約では、予約可能列を更新するためのコミット時にのみロックが取得されます。ロックフリー予約は、同時実行性の高いホット・データに使用され、暗黙的な補正サポートがあるため、マイクロサービス・トランザクション・モデルに適しています。
オプティミスティック・ロック
オプティミスティック・ロックは、データ・リソースのロックを取得せずにそれらのリソースをトランザクションで使用できるようにする同時実行性の制御方法です。コミットする前に、各トランザクションは、最後に読み取ったデータを他のトランザクションが変更していないことを確認します。
Saga
Sagaでは、複数の独立したマイクロサービスで構成される長時間実行ビジネス・トランザクションをカプセル化します。各マイクロサービスは、同じSagaの一部である1つ以上のローカル・トランザクションで構成されます。
29.3 ロックフリー予約
ロックフリー予約は、次の目的で、予約可能列に対して動作するトランザクションにデータベース内インフラストラクチャを提供します。
-
予約可能列に対して行われた更新でブロックされることなく、同時トランザクションを続行できるようにします
-
取り消されたSagaで成功したトランザクションの予約可能更新に対する自動補正を発行します
ロックフリー予約によって、アプリケーションがトランザクションに同時実行機能と自動補正機能を組み込む方法を次に示します。
予約可能列の宣言
表を作成または変更するときに、reservable
キーワードを使用して予約可能列を宣言できます。ロックフリー予約は数値データ型の列に提供されます。潜在的な予約可能列を識別するには、同時実行性の向上からメリットを受ける可能性のある数値集計データが含まれるホット・リソースを探します。
トランザクション更新の予約
-
トランザクションが予約可能列に対して更新操作を発行すると、予約可能更新はロックフリー予約として予約ジャーナルに記録されます。リバース可能列に対して発行されたすべての更新は、ロックフリー予約として処理されます。
- トランザクション更新は、行(予約可能更新がある行)をロックしませんが、行内の予約可能列をデルタ量分だけ変更(加算または減算)する意図を示します。変更量は、同じ行の予約可能列に対して以前に予約した他の未コミットの同時トランザクションを待機せずにトランザクションが続行できるように予約され確保されます。予約により、他の同時トランザクションは、同じ行に対して予約可能更新を発行できます。
-
変更(予約可能列の現在の値に対するデルタ量分だけ増減)の操作は、予約ジャーナルに記録されます。予約可能列の実際の値を読み取ったり書き込むかわりに、トランザクションは予約可能列の値を増減する操作を発行します。予約可能列に対して行われた更新リクエストは、予約ジャーナルに記録されます。
更新の確認および遅延
トランザクションは、予約可能列に課せられた制約に基づいて、数量が更新の実行に十分かどうかを決定します。トランザクションでは、次の項目をチェックします。
- 更新が成功することを確認します。十分な残余がある場合、予約ジャーナルの更新は続行できます。残余が更新(消費)リクエストを履行するのに十分でない場合、成功するためにアクティブな(まだコミットされていない)補充に依存せずに、予約可能列に対する更新リクエストは失敗します。
- 予約可能列に制約または境界(
CHECK
制約)がないかをチェックしてビジネス・ルールを施行し、アプリケーションの正確性を保証します。CHECK
制約には、予約可能列と非予約可能列のチェックを含めることができます。 - 同時実行性を向上させるために、コミット時間まで予約可能列に対する実際の更新を遅延させます
失敗したトランザクションでの正常な更新の自動補正
ロックフリー予約では、予約可能列の状態遷移を、そのトランザクション・ライフサイクルを通じて追跡できます。残高不足やSagaの取消しなどの理由でトランザクションが取り消された場合(部分的な完了の後)、ロックフリー予約は予約可能更新の自動補正またはロールバックを発行します。
予約可能更新の永続性の確保
ロックフリー予約は、トランザクションのコミット時に実際の更新に変化します。トランザクションのロールバックにより、トランザクションが予約ジャーナルで保持するすべてのロックフリー予約が無効になります。セーブポイントにロールバックすると、影響を受けるセーブポイントの後にトランザクションによって行われたロックフリー予約が削除されます。
ノート:
ロックフリー予約機能はコミット時まで行をロックしないため、自動トランザクション・ロールバック(別の23c機能)は、ロックフリー予約のみを行うトランザクションでは無処理になります。
関連項目:
-
自動トランザクション・ロールバック機能の詳細は、自動トランザクション・ロールバックを参照してください
29.3.1 オプティミスティック・ロックとロックフリー予約の比較
ロックフリー予約では、次のように境界内で操作することで、未処理の予約が保証されます。
-
ジャーナル処理中のリクエスト。
-
保留中のリクエストに基づいて予測値を追跡するための新しい予約ジャーナル列を導入します。
-
未処理の予約に基づいて新しいリクエストを許可または拒否するためにジャーナルに問い合せます。
-
未処理の予約のため、またはリクエスト数量が使用可能数を超えている場合に、満たすことができない新しいリクエストを拒否します。
これに対して、オプティミスティック・ロックでは予約の追跡や保証はしません。
オプティミスティック・ロック手法のその他の難点を次に示します。
- 数量が不十分なため、コミット時にリクエストが満たされない可能性があります。
-
トランザクションが最後に取り消される可能性があります。長時間実行トランザクションが取り消された場合、作業は破棄され、変更をロールバックする必要があります。
29.3.2 表作成時の予約可能列の作成
新しい表を作成するときに、reservable
キーワードを使用して予約可能列を宣言できます。
29.3.2.1 予約ジャーナル表の列
予約ジャーナル表には、次の列情報が含まれます。
DESCRIBE
文は、前述のAccount表に関連付けられた予約ジャーナル表の列の実際の名前を示します。SQL>desc SYS_RESERVJRNL_<object_number_of_base_table>;
表29-1 予約表の列
名前 | NULLかどうか | タイプ | 説明: |
---|---|---|---|
|
|
トランザクションのSaga ID (saga以外のトランザクション場合は0) |
|
|
|
トランザクションのトランザクションID (usn、slot、seqを含む) |
|
|
|
Txn IDのステータス(値: |
|
|
|
DML文タイプ |
|
|
NOT NULL |
NUMBER |
ユーザー表の |
|
VARCHAR2(1) |
補充または消費の'+'または'-'が指定された操作による予約可能列の操作 |
|
|
NUMBER |
予約可能列により予約済で、予約可能列から予約された量 |
29.3.4 予約可能列のCHECK制約について
CHECK
制約により、表の各行で満たす必要がある条件を指定できます。制約を満たすには、表の各行がその条件をTRUE
または不明(NULL
のため)のいずれかにする必要があります。特定の行に対するCHECK制約条件が評価される場合、条件にある列名に、その行の列値が適用されます。
ノート:
Oracleでは、CHECK制約の条件が相互に排他的かどうかは検証しません。このため、1つの列に対して複数のCHECK制約を作成する場合は、制約の用途が矛盾しないように注意する必要があります。また、条件の評価について特別な順序を想定しないでください。
予約可能列の表レベルのCHECK制約
表レベルのCHECK
制約に予約可能列を設定できます。予約可能列と非予約可能列が含まれる、表レベルのCHECK
制約がコミット時に検証に失敗した場合、トランザクションは取り消されます。非予約可能列の値は予約メカニズムを使用して保証できないため、取消しが発生します。
CREATE TABLE Account( ID NUMBER PRIMARY KEY, Name VARCHAR2(10), Balance NUMBER reservable, Earmark NUMBER, Limit NUMBER, CONSTRAINT minimum_balance CHECK (Balance + Limit – Earmark >= 0))
予約可能列を制約なしで定義することもできます。このような予約可能列では、すべてのロックフリー予約が成功します。
予約可能列では、storage句の指定はサポートされていません。
29.3.5 例: 従来のロックとロックフリー予約
次の例は、従来のロック・モードとロックフリー予約モードでの購買トランザクションを示しています。
従来のロック(長期保持ロック)
次の例では、従来のロックを使用して、50ドルの最小残高を維持しながら25ドルのアイテムの購入を許可します。
- まず、
SELECT FOR UPDATE
が、残高を読み取ってロックするために発行されます。 - 残高が75以上の場合は、アイテムの購入が許可されます。
- 次に、
UPDATE
は残高を借方にします。 - その後、トランザクションがコミットされます。
- 残高が不足すると、トランザクションが取り消されます。
CREATE TABLE Account ( ID NUMBER PRIMARY KEY, Name VARCHAR2(10), Balance NUMBER CONSTRAINT minimum_balance CHECK (Balance >= 50)); DECLARE current NUMBER; BEGIN -- Read and Lock account balance SELECT Balance INTO current FROM Account WHERE ID = 12345 FOR UPDATE; IF current >= 75 THEN -- Sufficient funds: Perform item purchase PurchaseItem(); -- Debit account balance and commit UPDATE Account SET Balance = Balance - 25 WHERE ID = 12345; COMMIT; ELSE ROLLBACK; -- Insufficient funds, so cancel END IF; END;
ロックフリー予約(短期保持ロック)
次の例では、ロックフリー予約を使用して、50ドルの最小残高を維持しながら25ドルのアイテムの購入を許可します。予約可能列の制約により、行をロックせずに列値に予約を入れることができます。
- 残高更新では、アカウントをロックせずに25ドルが予約されます。
- 予約が成功すると、アイテムの購入を続行できます。
- 最終コミットでは、アカウント行がロックされ、予約に記録された25ドルの残高借方が適用されます。
- 資金不足のために予約が失敗した場合、update文は
CHECK
制約違反で失敗します。
CREATE Table Account( ID NUMBER PRIMARY KEY, Name VARCHAR2(10), Balance NUMBER RESERVABLE CONSTRAINT minimum_balance CHECK (Balance >= 50)); BEGIN -- Reserve 25 from account balance UPDATE Account SET Balance = Balance - 25 WHERE ID = 12345; -- If reservation succeeds perform item purchase PurchaseItem(); -- The commit finalizes the balance update COMMIT; -- This gets the account row lock EXCEPTION WHEN Check_Constraint_Violated -- This indicates that the reservation failed THEN ROLLBACK; END;
29.3.6 予約可能列ビューの問合せ
予約可能列のディクショナリ・ビューに対して問合せを実行して、予約可能列に関する情報を取得できます。
DBA_TAB_COLUMNS
、USER_TAB_COLUMNS
およびALL_TAB_COLUMNS
ビューに対して問合せを実行して、列が予約可能列として宣言されているかどうかを確認できます。SELECT table_name, column_name , reservable_column FROM user_tab_columns WHERE table_name = <table name>;
DBA_TAB_COLS
、USER_TAB_COLS
およびALL_TAB_COLS
ビューに対して問合せを実行して、列が予約可能列として宣言されているかどうかを確認できます。SELECT table_name, column_name , reservable_column FROM user_tab_cols WHERE table_name = <table name>;
SQL> SELECT table_name, column_name , reservable_column FROM user_tab_cols WHERE table_name = 'ACCOUNT';
TABLE_NAME COLUMN_NAME RES –----------------------------------- ACCOUNT NAME NO ACCOUNT BALANCE YES ACCOUNT ID NO 3 rows selected
DBA_TABLES
、USER_TABLES
およびALL_TABLES
ビューに対して問合せを実行して、ユーザー表に1つ以上の予約可能列があるかどうかを確認できます。SELECT table_name, has_reservable_column FROM user_tables WHERE table_name = <table name>;
SQL> SELECT table_name, has_reservable_column FROM user_tables WHERE table_name = 'ACCOUNT';
TABLE_NAME HAS –------------------- ACCOUNT YES 1 row selected
29.4 ロックフリー予約を使用するメリット
ユーザー・エクスペリエンスおよび同時実行性の向上
ロックフリー予約では、ホット・データのロックを短い時間間隔で保持するため、ユーザー・エクスペリエンスと同時実行性が向上します。トランザクションは、ロックせずに予約可能列値から数量を予約します。ロックが実行されるのは、トランザクションのコミット中に値が変更されるときのみです。
自動補正
補正機能を使用する場合は、データベースを一貫した状態に遷移できるように依存性を追跡する必要があります。Sagaの場合、これらの依存性は長期間(変更が確定されるまで)追跡する必要があります。Saga実装でロックフリー予約を使用できます。ロックフリー予約では、Sagaトランザクションが取り消された場合、すでにコミットされているSagaの他のトランザクションのロールバックを処理するために暗黙的な補正トランザクションが自動的に発行されます。複雑な補正機能を記述する必要はありません。ロックフリー予約により、ジャーナルによる自動補正が可能になり、失敗したSagaの正常な更新を元に戻すことができます。
効率的なリソース使用率
ロックフリー予約を使用すると、複数のトランザクションをパラレルで実行でき、相互にブロックせずにリソースを使用できます。リソース使用率の効率化は、待機時間とレスポンス時間を短縮することで改善されます。
幅広い範囲
予約可能更新は数値集計データに対して実行されます。これは、多種多様なデータに作用する多くのアプリケーションに不可欠です。ロックフリー予約を使用した同時実行性の向上は、予約可能更新を含む行の割合が高い場合に、アプリケーションにメリットをもたらします。長時間実行トランザクションに予約可能更新があるアプリケーションは、トランザクションにおける同時実行性の改善から最も恩恵を受ける可能性があります。これらのアプリケーションには、銀行業務、在庫管理、発券業務およびイベント予約を処理するアプリケーションが挙げられます。
29.5 ロックフリー予約のガイドラインと制限事項
この項では、ロックフリー予約のガイドラインと制限事項について説明します。
29.5.1 予約可能列のガイドラインと制限事項
ロックフリー予約を使用する場合、予約可能列と予約可能列を持つユーザー表については、次のガイドラインに従ってください。
-
ユーザー表のスキーマ定義では、
reservable
キーワードを使用して予約可能列を宣言します。予約可能列はロックフリー予約を提供します。 -
予約可能列は、Oracle数値データ型(
NUMBER
、INTEGER
およびFLOAT
)の列に対してのみ指定できます。 -
予約可能列は集計タイプであるため、予約可能列を
Primary Key
またはアイデンティティ列(または仮想列)にすることはできません。 -
ユーザー表には最大10個の予約可能列を含めることができます。
-
予約可能列が含まれるユーザー表には、
Primary Key
が必要です。 -
予約可能列では、索引はサポートされていません。
-
複合予約可能列は使用できません。
-
ブロック・チェーン表およびシャード表では、予約可能列は使用できません。
-
予約可能列は
CHECK
制約式にのみ含めることができます。CHECK
制約は、列レベルまたは表レベルに指定できます。ユーザー定義の操作上の制約は、アプリケーションの正確性を保証するために、予約可能列に使用されます。-
オプションの
CHECK
制約を、予約可能列に指定できます。 -
予約可能列は他のタイプの制約に含めることはできません。予約可能列を含む
CHECK
以外の制約は使用できません。予約可能列を外部キー制約に含めることはできません。
-
-
外部表、クラスタ表、IOT表および一時表には予約可能列を指定できません。
-
予約可能列では、パーティション化を実行できません。
-
予約可能列が含まれるユーザー表には、2フェーズのオンラインDDL最適化は提供されません。
-
ユーザー表から予約可能列を削除すると、対応するロックフリー予約追跡列が予約ジャーナル表から削除されます。最後の予約可能列がユーザー表から削除されると、予約ジャーナル表は削除されます。予約可能列を削除したり、列を
UNUSED
とマークするには、保留中の予約があるトランザクションをファイナライズする必要があります。
29.5.2 update文のガイドラインおよび制限事項
ロックフリー予約を使用する場合は、次に示すUPDATE
文のガイドラインに従ってください。
-
予約可能列に対する更新は、次のいずれかとして指定する必要があります。
UPDATE <table_name> SET <reservable_column_name> = <reservable_column_name> + (<expression>) WHERE <primary_key_column> = <expression>
UPDATE <table_name> SET <reservable_column_name> = <reservable_column_name> - (<expression>) WHERE <primary_key_column> = <expression>
ノート:
SET <reservable_column_name> = <value>
は使用できません。直接値を割り当てると、エラーが発生します。ノート:
複合主キーの場合、すべての主キー列は、予約可能更新のWHERE
句で指定する必要があります。 -
1つのupdate文で、表の複数の予約可能列を更新できます。
-
同じ更新文に予約可能列の更新と非予約可能列の更新を混在させることはできません。また、予約可能な列更新文では、DML returning句はサポートされていません。
-
予約可能列に対する更新は、トランザクションのコミットまで行をロックしません。かわりに、予約可能列はロックフリー予約を提供します。ロックフリー予約では、他の同時トランザクションがブロックされることなく、予約可能列を更新できます。
-
ロックされた(行の非予約可能列を更新したものと同じトランザクションまたは別のトランザクションによってロックされた)行で発行された予約可能更新では、ロックフリー予約を取得できます。
-
予約可能列の保留中の予約に変換される更新により、保留中の予約を考慮した後、予約可能列の制約に違反していないことが保証されます。
-
トランザクションは、トランザクションが予約可能更新を発行した(ユーザー表に関連付けられている)予約ジャーナル表から選択することで、独自のロックフリー予約を読み取ることができます。他のトランザクションによって行われた予約は表示されません。
29.5.3 挿入および削除のガイドライン
ロックフリー予約を使用する場合、予約可能列がある表に発行されるINSERT
文とDELETE
文については、次のガイドラインに従ってください。
-
トランザクションは、動作を変更せずに、予約可能列の値が含まれる行全体を挿入できます。
-
挿入トランザクションがコミットされるまで、挿入された行は、予約可能更新の他のトランザクションには表示されません。
-
削除するユーザー表の行について保留中の予約があるときに
DELETE
文が発行された場合、削除を続行するには、それらの行に対するアクティブなロックフリー予約が含まれるトランザクションが完了する必要があります。DELETE
文は、内部的に5秒間隔で再試行され、影響を受ける行について保留中の予約がなくなると許可されます。タイムアウト期間後、DELETE
文を続行できなかった場合は、リソース・ビジー・メッセージを含むエラーが発生します。DELETE
文は後で発行できます。