Apex

Apexバッチのscopeの決まり方と同時実行数の上限

件数が多いデータを一括更新すると、同期処理の上限にすぐ当たります。Apexバッチはレコードを自動で分割し、非同期のトランザクションで処理します。

2023.02.21

Apexバッチを使う場面

取引先を1万件まとめて更新するような処理を、同期のApexでそのまま書くとSOQLやDMLの回数、実行時間の上限にすぐ当たります。Apexバッチは対象レコードを自動的に決まった件数ずつへ分割し、それぞれを別のトランザクションとして非同期に実行します。分割と非同期実行を、自分でコントローラを書かずに任せられるのが利点です。

Database.Batchableの3つのメソッド

Database.Batchableインターフェースを実装すると、3つのメソッドを書く決まりになります。

メソッド呼ばれるタイミング役割
startジョブ開始時に1回処理対象のレコード集合を返します
execute分割されたバッチごとに複数回渡されたレコードの一部(scope)を処理します
finish全バッチの処理後に1回後処理やメール通知などを行います

startはSOQLの結果をそのまま返すDatabase.QueryLocatorと、任意の集合を返せるIterable<sObject>のどちらかを返せます。SOQLで対象を絞れるならQueryLocatorで十分です。

Account_Batchable.cls

public with sharing class Account_Batchable implements Database.Batchable<sObject> {
    public Database.QueryLocator start(Database.BatchableContext bc) {
        return Database.getQueryLocator('SELECT Id, Name FROM Account');
    }

    public void execute(Database.BatchableContext bc, List<Account> scope) {
        for (Account acc : scope) {
            acc.Name = acc.Name + 'BatchTest';
        }
        update scope;
    }

    public void finish(Database.BatchableContext bc) {
        System.debug('バッチ処理が終了しました');
    }
}

取引先を検索してscope件ずつ名前を書き換え、1回のupdateでまとめて保存します。ループの中にDMLはありません。

scopeの既定値と上限

Database.executeBatchを呼ぶときに、scope引数で1回のexecuteに渡す件数を指定できます。

指定件数
省略した場合200件ずつに分割されます
startがQueryLocatorを返す場合の上限2,000件まで指定できます

⚠️ 最後のバッチだけは、scopeで指定した件数より少ないことがあります。対象が1,000件でscopeを300にすると、最後のexecuteには100件だけが渡ります。executeの中で件数が常にscopeと同じだとは仮定しないでください。

バッチをスケジュールで定期実行する

Schedulableインターフェースを実装したクラスから、Database.executeBatchを呼び出す形で定期実行できます。

Account_Schedule.cls

public class Account_Schedule implements Schedulable {
    private final Integer BATCH_SIZE = 200;

    public void execute(SchedulableContext sc) {
        Account_Batchable b = new Account_Batchable();
        Database.executeBatch(b, BATCH_SIZE);
    }
}

スケジューラからバッチを起動します。BATCH_SIZEがexecuteBatchのscope引数になります。

登録は開発者コンソールからSystem.scheduleを呼ぶか、設定画面の「Apexをスケジュール」から行います。

設定のApexクラス一覧画面。Apex使用率と、下部に「Apexをスケジュール」ボタンが並ぶ
設定のApexクラス画面です。画面下のボタンの並びに「Apexをスケジュール」があります。
Apexをスケジュール画面。ジョブ名とApexクラスにAccount_Scheduleを指定し、毎週の曜日と開始日・希望開始時刻を設定している
画面から登録する場合の入力内容です。頻度・開始日・希望開始時刻の3つを指定します。
String jobId = System.schedule('Account_Schedule_Daily', '0 0 2 * * ?', new Account_Schedule());

毎日午前2時に実行するcron式の例です。過去の日時を指定すると、スケジュール登録の時点でエラーになります。

開発者コンソールで動かして確認する

  1. Execute Anonymous Windowを開く

    開発者コンソールのメニューから「Debug」→「Open Execute Anonymous Window」を選びます。

  2. バッチを実行するコードを書いて実行する

    Account_Batchable b = new Account_Batchable(); Database.executeBatch(b, 200);を入力し、「Execute」を押します。

  3. ジョブの状態を確認する

    設定の「Apexジョブ」を開き、対象のジョブが「完了」になっていることを確認します。取引先の一覧を開き、名前に「BatchTest」が付いていることも確認します。

  4. スケジュールの登録を確認する

    System.scheduleを実行した場合は、設定の「スケジュール済みジョブ」に登録されていることを確認します。

取引先の詳細画面。取引先名が「dd」になっている
バッチを流す前の取引先です。取引先名が「dd」であることを確認しておきます。
取引先の詳細画面。取引先名が「ddBatchTest」に変わっている
バッチを流した後の同じ取引先です。取引先名の末尾に「BatchTest」が付いています。
取引先の詳細画面。取引先名が「dd」の状態
スケジュール実行を試す前の取引先です。ここでも取引先名は「dd」のままです。
取引先の詳細画面。取引先名が「ddBatchTest」に変わっている
スケジュール実行のあとの取引先です。画面から登録した場合も同じ結果になります。
設定のスケジュール済みジョブ一覧。アクション・ジョブ名・登録実行者・次の実行スケジュール・種別の列が並ぶ
設定の「スケジュール済みジョブ」です。登録したジョブがここに並び、次の実行予定を確認できます。

バッチの同時実行数と1日の上限

項目上限
組織全体で同時にキュー投入・実行できるバッチジョブ数5個まで(超えた分はApex Flexキューで「保留中」として待機します)
Apex Flexキューで待機できるバッチジョブ数100個まで
24時間あたりのバッチApexメソッド実行数(start・execute・finishの合計)250,000回、または組織のユーザーライセンス数×200のいずれか大きい方まで

24時間の上限は、バッチApexだけでなく他の非同期Apex(キューアブルなど)とも共有します。バッチを細かく分けすぎると、executeの呼び出し回数がかさんでこの上限に近づきます。

トランザクションをまたいで値を持ち越す

バッチはexecuteが呼ばれるたびに別のトランザクションになるため、インスタンス変数は毎回リセットされます。処理件数の合計を数えるなど、バッチ全体を通して値を持ち越したいときはDatabase.Statefulを付けます。

public with sharing class Account_Batchable implements Database.Batchable<sObject>, Database.Stateful {
    public Integer processedCount = 0;

    public Database.QueryLocator start(Database.BatchableContext bc) {
        return Database.getQueryLocator('SELECT Id, Name FROM Account');
    }

    public void execute(Database.BatchableContext bc, List<Account> scope) {
        for (Account acc : scope) {
            acc.Name = acc.Name + 'BatchTest';
        }
        update scope;
        processedCount += scope.size();
    }

    public void finish(Database.BatchableContext bc) {
        System.debug('処理件数: ' + processedCount);
    }
}

Database.Statefulを付けると、processedCountがexecuteをまたいでも保持されます。付けないと毎回0からになります。

Database.Statefulで保持されるのはインスタンス変数だけです。静的変数はDatabase.Statefulを付けても、トランザクションごとにリセットされます。

大量件数を扱うときの選択肢

バッチApexは1日あたりの実行回数に上限があるため、数百万件を超えるような初期移行やデータクレンジングには向きません。そうした件数は、Apexを介さずに直接データを送り込める形式のBulk APIや、それを内部で使うデータローダを使います。バッチサイズの目安として、公式ガイドラインは1万件からの開始を勧めています。処理時間を見ながら増減させます。

ここで間違えやすい

間違い何が起きるか
scopeに2,000を超える値を指定するQueryLocator利用時は、指定しても2,000件ずつに分割されます。指定した件数では渡りません
Database.Statefulを付けずに合計を数えるexecuteのたびにインスタンス変数が0に戻り、正しく集計できません
過去の日時でcron式を組むSystem.scheduleの時点でエラーになり、ジョブが登録されません
バッチを次々にキューへ積む同時にキュー投入・実行できるのは5個までです。超えた分はApex Flexキューで待機し、Flexキューの100個も超えるとLimitExceptionになります

確認した環境

  • 2026年9月 / Salesforce Summer '26(APIバージョン67.0)時点の公式ドキュメントで、Database.Batchableの仕様、scopeの既定値と上限、同時実行数と24時間あたりの実行回数の上限を確認しています

まとめ

  • Apexバッチはstart・execute・finishの3メソッドを実装します。対象の分割と非同期実行はSalesforceが行います
  • scopeは省略すると200件、QueryLocator使用時の上限は2,000件です。最後のバッチはそれより少ないことがあります
  • 組織全体で同時に動かせるバッチジョブは5個まで、24時間の実行回数上限は250,000回か「ライセンス数×200」の大きい方です
  • トランザクションをまたいで値を保持したいときはDatabase.Statefulを付けます
  • 数百万件規模の処理は、バッチApexではなくBulk APIやデータローダを検討します

Salesforceの導入・運用についてご相談ください

導入前の検討から、お使いの環境の改修・運用、AIとの連携まで承ります。状況を伺ったうえで、進め方をご提案します。