Apexにおけるトランザクションと実行コンテクスト

By | July 6, 2022

トランザクションとは何か?

Apexにおける「トランザクション(Transaction)」とは、「一単位として実行される一連の操作(a set of operations that are executed as a single unit)」すなわち「処理単位」を意味する。

「実行コンテクスト(Execution Context)」とも呼ばれる。

トランザクションは(Apex実行の起点となる)任意のイベントによって開始し、DBに対するDML操作のコミット(またはロールバック)によって終了する。

ApexトランザクションはDML操作のコミット単位(またはロールバック単位)である限りにおいて、一般的なRDBの文脈における「トランザクション」と同義とみなすことができる一方で、以下のApex独自の特徴を有する。

  • static variableのscopeとlifetimeを規定する
  • ガバナ制限の適用単位である

従って、(一般的なRDBの文脈における「トランザクション」とは異なり)DBに対するSQL文が一つも発行されしない処理に対してもApexトランザクションという言葉は適用され得る。

トランザクション(実行コンテクスト)の開始と終了

Apex実行の起点となる任意のイベントによって開始したトランザクション(実行コンテクスト)は、”同期的に”実行される一連の処理が完了するまで終了することはない。

従って、以下のようにApex TriggerA→Apex TriggerB→フロー→Apex Methodが連続実行される処理があった場合、これらの処理は全て「単一のトランザクション(実行コンテクスト)」において実行される。

(レコード更新→)Apex Trigger A 起動→Method A-1実行→Method A-2実行(レコード作成)→Apex Trigger B起動→MethodB-1実行(レコード更新)→レコードトリガフロー起動→トリガアクション実行→Invocable Method C実行(レコード更新)

また単一のCRUD操作から複数のビジネスロジックが起動する設定になっている場合(例えば、取引先のAfter Insert Triggerが複数存在する場合)も、それらは同一のApexトランザクションにおいて(順不同で)実行される。

(ここまでに記載した内容の振り返りとなるが)上記全てが同一のトランザクションで処理されることは以下を意味している。

  • トランザクションを通じてstatic変数が保持される
  • トランザクションを通じてガバナ制限が適用される
  • 一連の処理の中で実行されたDB変更が最終的にコミットされるのはトランザクションの一番最後のタイミングである
    • 基本的に、任意のタイミングでエラーが発生した場合、同一トランザクション内でのそこまでのDB変更は全てロールバックされる。ただし、DMLで明示的にAllOrNone=falseオプションをしている場合や、AllOrNone原則がデフォルト適用されないバッチ処理・Open Anonymous Execute Window経由などについてはその限りではない。

バッチ処理の場合

バッチ処理の場合、(Database.executeBatchで定義した単位で分割された)各jobはそれぞれ異なるトランザクションで実行される。

従って、特定のjobでエラーが発生しても、別のjobを跨いで全体の処理がrollbackされることはない。

また同様に、Database.savepointメソッドを利用しても、全体の処理をその地点までロールバックすることはできない。

トランザクションを通じてstatic変数が保持されることの意義

static変数に「Trigger起動済みかどうか」の状態を持たせることで多重起動を防止するといったことが可能となる。

サンプルコード↓

public static boolean isAlreadyCalled = false;
trigger AccountTrigger on Account (before insert) {
 if(TestClass.isAlreadyCalled == false){
  TestClass.isAlreadyCalled = true;
  //任意の処理
 }
}

◾️2023年6月追記

ApexTrigger重複起動防止を目的とした上記実装には、(allOrNone=falseのDMLを含む)一括DML 例外処理において破綻をきたすリスクがあるという指摘がなされている。

詳細はTomita氏による「Apexトリガ二重起動抑止パターンと一括レコード処理時の破綻ケースについて」の記事を参照されたい。

トランザクションの実行順序

お馴染みの↓

1. 元のレコードがデータベースから読み込まれるか、upsert ステートメント用にレコードが初期設定されます。
2. 要求から新しいレコード項目の値が読み込まれ、古い値を上書きします。
3. レコードの保存前に実行されるように設定されたレコードトリガフローを実行します。
4. すべての before トリガが実行されます。
5. すべての必須項目に null 以外の値が入力されていることの確認や、カスタムの入力規則の実行など、システム検証のほとんどの手順がもう一度実行されます。
6. 重複ルールが実行されます。
7. レコードはデータベースに保存されます(が、まだ確定されません。)
8. すべての after トリガが実行されます。
9. 割り当てルールが実行されます。
10. 自動応答ルールが実行されます。
11. ワークフロールールが実行されます。
12. エスカレーションルールが実行されます。
13. フロー自動化が実行されます。
14. エンタイトルメントルールが実行されます。
15. レコードの保存後に実行されるように設定されたレコードトリガフローを実行します。
16. 積み上げ集計項目が実行されます。
17. 条件に基づく共有の評価が実行されます。
18. すべての DML 操作がデータベースで確定されます。
19. 変更がデータベースにコミットされた後、メール送信などのコミット後ロジックを実行し、キューに追加された非同期 Apex ジョブ (キュー可能ジョブや future メソッドなど) を実行します。

https://developer.salesforce.com/docs/atlas.ja-jp.234.0.apexcode.meta/apexcode/apex_triggers_order_of_execution.htm

ACIDプロパティ

(DML操作を含む)Apexトランザクションは以下のACIDプロパティを有する。

  • Atomicity(原子性):トランザクション内の操作が、全て完了か全て失敗のいずれかであること。すなわち、何らかの理由で特定の操作に失敗した場合、操作全体が中断され、全体の操作がなかったことになること。
  • Consistency(一貫性):データベースに変更が加えられた場合に、(そのトランザクションの終了状態に関わらず)データベースの整合性が保たれていること
  • Isolation(独立性):複数のトランザクションが干渉することなく同時に動作すること。
  • Durability(耐久性):トランザクションが完了すると、システム障害が発生しても更新内容が失われることがないこと。