SalesforceのToo many SOQL queries: 101の原因と直し方|複数のApex処理をログで切り分け

複数のApex処理によるToo many SOQL queries: 101をログで切り分けるイメージ

SalesforceでApexを実行したとき、次のエラーが表示されることがあります。

System.LimitException: Too many SOQL queries: 101

このエラーは、1つのトランザクション内で実行したSOQLクエリの回数が上限を超えたときに発生します。ループ内のSOQLが代表的な原因ですが、実際には複数のApexクラスやトリガーで使った回数が積み上がり、最後に実行したSOQLで上限を超えることもあります。

そのため、エラーが表示された行だけを見て直すのではなく、同じトランザクションでSOQLがどこから積み上がったかを確認することが重要です。

先に結論です。Too many SOQL queries: 101が発生したら、まずデバッグログのLIMIT_USAGE_FOR_NSで使用回数を確認します。続いてLimits.getQueries()SOQL_EXECUTE_BEGIN、スタックトレースを追い、複数のApex処理のどこで使用回数が増えたかを切り分けます。LIMIT 1や取得項目の削減だけでは、SOQLの発行回数は減らないため直接的な解決にはなりません。

目次

Too many SOQL queries: 101の確認ポイント

最初に、原因と確認場所を一覧にすると次のようになります。

主な原因確認する場所対処の方向
ループ内でSOQLを実行しているApexコード、SOQL_EXECUTE_BEGINIDをSetに集め、ループ外で一括取得する
少数レコードだけを前提にしている一括登録・更新時のログ、テストクラストリガーとクラスを複数レコード対応にする
Apexトリガーが再帰しているスタックトレース、同じトリガーの実行回数起動条件と更新設計を見直す
Flowがループ内でレコードを取得しているFlowの要素、デバッグログGet Recordsをループ外へ移す
ApexとFlowなど複数の自動化が動いている実行順序、ログ全体同一オブジェクトの自動化を横断して確認する
非同期処理を含む設計になっているQueueable、Batch、Scheduled Apexトランザクション境界と処理量を確認する
ボンボン

ApexにSOQLが数個しかなくても、Flowや別のトリガーが先に使っていることがあるよ。エラー行だけじゃなく、ログ全体を見てみよう。

SOQL 101が発生するガバナ制限を確認する

これはSOQLの構文エラーではなく、Salesforceのガバナ制限に関するエラーです。Salesforceはマルチテナント環境のリソースを保護するため、1つのApexトランザクションで使用できるSOQLやDMLなどに上限を設けています。

Salesforce公式のExecution Governors and Limitsでは、同期Apexと非同期ApexのSOQL上限を次のように定めています。

実行コンテキストSOQLクエリの上限
同期処理100回画面操作、同期Apex、通常のApexトリガー
非同期処理200回Queueable Apex、Futureメソッドなど

上限は「取得したレコードの件数」ではなく、基本的にはSOQLを実行した回数です。1回のSOQLで100件取得する場合と、1件ずつ100回SOQLを実行する場合では、消費するクエリ回数が異なります。

非同期処理へ移せば必ず解決するわけではありません。上限が増えて一時的にエラーが消えても、ループ内SOQLや再帰が残っていれば、データ量の増加によって再発します。まずSOQLの発行回数を減らす設計を確認します。

Too many SOQL queries: 101を再現する

今回の検証は2026年7月に、Salesforceの検証用組織と開発者コンソールを使って行いました。

同じ挙動を確認する場合は、SandboxまたはDeveloper Editionなどの検証用組織で、開発者コンソールの「Execute Anonymous Window」から次のコードを実行します。

for (Integer i = 0; i < 101; i++) {
    List<Account> accounts = [
        SELECT Id
        FROM Account
        LIMIT 1
    ];
}

同期Apexでは100回までSOQLを実行できますが、101回目のクエリでSystem.LimitExceptionが発生します。ループ回数を100へ変更した場合と比較すると、境界を確認できます。

ループ回数SOQL実行回数実行結果
99回99回成功
100回100回成功
101回101回Too many SOQL queries: 101
Execute AnonymousでSystem.LimitException: Too many SOQL queries: 101が表示された画面

LIMIT 1を付けても101エラーになる理由

再現コードではLIMIT 1を付けていますが、取得件数を1件にしてもSOQL自体はループのたびに実行されます。そのため、101回目でエラーになります。

取得項目を減らす、WHERE条件を追加する、インデックスを利用するといった対応は、取得量やクエリ性能の改善には有効です。ただし、同じ回数のSOQLを実行している限り、Too many SOQL queriesの直接的な解決にはなりません。

SOQLを100回実行した成功結果と101回実行したエラー結果の比較

LimitsクラスでSOQLの使用回数を確認する

Apexコードの途中でSOQLの使用回数を確認したい場合は、Limitsクラスを利用できます。

System.debug(
    LoggingLevel.ERROR,
    'SOQL Queries: ' +
    Limits.getQueries() +
    ' / ' +
    Limits.getLimitQueries()
);

Limits.getQueries()は現在のトランザクションで実行済みのSOQL回数、Limits.getLimitQueries()はそのコンテキストで実行できる上限を返します。詳細はSalesforce公式のLimitsクラスで確認できます。

既存処理のどこでクエリ数が増えているか分からない場合は、処理の節目へ一時的にログを追加し、メソッドの前後で数値を比較します。

System.debug('before: ' + Limits.getQueries());

executeBusinessLogic();

System.debug('after: ' + Limits.getQueries());

Limitsクラスは調査に便利ですが、上限そのものを増やす機能ではありません。原因箇所を絞り込んだら、SOQLの回数を減らす実装修正が必要です。

Limits.getQueriesで処理前後のSOQL実行回数を確認したデバッグログ

デバッグログから複数箇所の積み上がりを特定する

ここまでは、1つの処理でSOQLを101回実行する単純な例を確認しました。実際の開発では、複数のApexクラスやメソッドが同じトランザクション内で動き、少しずつ使用回数を積み上げていることがあります。

そこで、取引先処理で40回、取引先責任者処理で35回、最後の商談処理で26回SOQLを実行する検証用コードを作ります。エラーが出るのは商談処理ですが、その時点ですでに75回消費している状態です。

処理SOQL実行回数処理後の累計
Soql101AccountService40回40回
Soql101ContactService35回75回
Soql101MultiScenarioの商談処理26回101回
複数のApex処理でSOQL使用回数が40回、75回、101回と積み上がる流れ

以下は検証用コードです。SOQLを意図的に繰り返すため、本番組織や本番データでは実行せず、SandboxまたはDeveloper Editionを使用してください。

1.複数のApex処理を用意する

最初に、SOQLを40回実行する取引先処理を作成します。

public with sharing class Soql101AccountService {
    public static void consume() {
        System.debug(
            LoggingLevel.ERROR,
            'AccountService開始: ' + Limits.getQueries()
        );

        for (Integer i = 1; i <= 40; i++) {
            List<Account> accounts = [
                SELECT Id FROM Account LIMIT 1
            ];
        }

        System.debug(
            LoggingLevel.ERROR,
            'AccountService終了: ' + Limits.getQueries()
        );
    }
}

続いて、SOQLを35回実行する取引先責任者処理を作成します。

public with sharing class Soql101ContactService {
    public static void consume() {
        System.debug(
            LoggingLevel.ERROR,
            'ContactService開始: ' + Limits.getQueries()
        );

        for (Integer i = 1; i <= 35; i++) {
            List<Contact> contacts = [
                SELECT Id FROM Contact LIMIT 1
            ];
        }

        System.debug(
            LoggingLevel.ERROR,
            'ContactService終了: ' + Limits.getQueries()
        );
    }
}

最後に2つの処理を呼び出し、商談のSOQLを実行するクラスを作成します。商談処理は、100回で止める場合と101回目まで進める場合を比較できるようにしています。

public with sharing class Soql101MultiScenario {
    public static void run(Integer opportunityQueryCount) {
        System.debug(
            LoggingLevel.ERROR,
            '処理開始: ' + Limits.getQueries()
        );

        Soql101AccountService.consume();
        Soql101ContactService.consume();

        System.debug(
            LoggingLevel.ERROR,
            '商談処理開始前: ' + Limits.getQueries()
        );

        for (Integer i = 1; i <= opportunityQueryCount; i++) {
            if (i == 1 || i == 25 || i == 26) {
                System.debug(
                    LoggingLevel.ERROR,
                    '商談クエリ実行前 ' + i + '回目: ' +
                    Limits.getQueries()
                );
            }

            List<Opportunity> opportunities = [
                SELECT Id FROM Opportunity LIMIT 1
            ];
        }
    }
}
SOQLを複数箇所で実行する3つのApexクラス

2.100回と101回の実行結果を比較する

まず、Execute Anonymous Windowから次のコードを実行します。

Soql101MultiScenario.run(25);

40回+35回+25回で合計100回となり、今回の検証では正常終了しました。続いて、商談処理を1回増やします。

Soql101MultiScenario.run(26);

今度は合計101回となり、今回の検証では最後の商談SOQLでSystem.LimitException: Too many SOQL queries: 101が発生しました。

複数のApex処理によるSOQL100回の成功結果と101回のエラー結果

3.ログから40回、75回、100回の節目を確認する

対象ユーザーへ追跡フラグを設定してからrun(26)を再実行し、デバッグログを開きます。今回追加した出力を検索すると、SOQLの使用回数が次のように増えていることを確認できます。

処理開始: 0
AccountService開始: 0
AccountService終了: 40
ContactService開始: 40
ContactService終了: 75
商談処理開始前: 75
商談クエリ実行前 25回目: 99
商談クエリ実行前 26回目: 100

今回の検証では、エラーが表示されたのは商談処理でした。一方、実際にログを確認すると、その前の2クラスですでに75回使っていたことが分かりました。最後のSOQLだけを修正対象と考えるのではなく、取引先処理と取引先責任者処理も含めて見直す必要があります。

ポイント:例外の行は「101回目を実行しようとした場所」です。SOQLを積み上げた原因箇所が、同じクラスや同じ行にあるとは限りません。

複数のApexクラスでSOQL使用回数が積み上がるデバッグログ

4.LIMIT_USAGE_FOR_NSとSOQL_EXECUTE_BEGINを確認する

ログ内でLIMIT_USAGE_FOR_NSを検索すると、SOQLやDMLなどの使用状況を確認できます。Salesforce公式のデバッグログの説明にも、SOQLクエリ数が出力されることが記載されています。

LIMIT_USAGE_FOR_NS|(default)|
  Number of SOQL queries: 100 out of 100

上限到達を確認したら、SOQL_EXECUTE_BEGINとスタックトレースから、どのクラスと行番号でSOQLが繰り返されたかを確認します。今回なら、エラー直前の商談処理だけでなく、ログ上部のSoql101AccountServiceSoql101ContactServiceまで確認対象です。

SalesforceのWell-Architectedでも、ガバナ制限を超えた処理が根本原因とは限らず、再帰的・重複した自動化を含めて確認する必要があると説明されています。

SalesforceのデバッグログからSOQL上限と原因クラスを確認する画面

原因1:ループ内でSOQLを実行している

最も分かりやすい原因は、forループの中でSOQLを実行しているケースです。

修正前:レコードごとにSOQLを実行する

for (Contact contactRecord : Trigger.new) {
    Account accountRecord = [
        SELECT Id, Name
        FROM Account
        WHERE Id = :contactRecord.AccountId
    ];

    // accountRecordを使った処理
}

このコードは、対象の取引先責任者が1件ならSOQLも1回です。しかし、複数レコードを一括更新すると、レコード数に応じてSOQLの回数も増えます。

修正後:IDを集めてループ外で一括取得する

Set<Id> accountIds = new Set<Id>();

for (Contact contactRecord : Trigger.new) {
    if (contactRecord.AccountId != null) {
        accountIds.add(contactRecord.AccountId);
    }
}

Map<Id, Account> accountMap = new Map<Id, Account>([
    SELECT Id, Name
    FROM Account
    WHERE Id IN :accountIds
]);

for (Contact contactRecord : Trigger.new) {
    Account accountRecord =
        accountMap.get(contactRecord.AccountId);

    // accountRecordを使った処理
}

最初のループでは取引先IDだけをSetへ集め、SOQLはループ外で1回だけ実行します。取得結果をMapにすると、後続のループで取引先IDから該当レコードを参照できます。

Salesforce公式のRunning Apex within Governor Execution Limitsでも、ループ内のSOQLは反復ごとに実行され、100回の上限を超える可能性があるため避けるよう案内されています。

原因2:一括処理を想定できていない

通常の画面操作では1件ずつ更新されるため問題がなくても、データローダー、API連携、リストビューの一括更新などで複数件が同時に処理されると、急に101エラーが発生することがあります。

ApexトリガーではTrigger.newに複数レコードが入る前提で、SOQLとDMLを一括実行できるようにします。テストクラスでも1件だけでなく、複数レコードをまとめて登録・更新するテストを用意すると見つけやすくなります。

  • メソッドの引数が単一IDだけを前提にしていないか
  • Trigger.newのループ内でSOQLやDMLを実行していないか
  • ヘルパークラスがレコードごとに呼び出されていないか
  • テストクラスで複数レコードを一括処理しているか

原因3:Apexトリガーが再帰している

トリガーやFlowが対象レコードを更新し、その更新をきっかけに同じ自動化が再び起動すると、SOQLが繰り返し実行されます。

FlowとApexが同じレコードを更新したときの値の上書きと再実行は、次の関連記事で実際に検証しています。

再帰を防ぐときは、単に静的変数で2回目以降を止めるだけでなく、次の点を確認します。

  • 更新前後で対象項目が変化したときだけ処理する
  • 同じレコードの項目更新なら、可能な場合はbeforeトリガーや高速項目更新を使う
  • 処理済みレコードIDを管理し、必要な再実行まで止めないようにする
  • ApexとFlowの両方で同じ項目を更新していないか確認する

補足:実際の組織ではFlowや別トリガーも確認する

今回の検証では、原因を追いやすくするために3つのApexクラスだけを使用し、Flowは混ぜていません。

ただし、実際のレコード保存では、before-save Flow、Apexトリガー、after-save Flowなどが決められた順序で実行されます。詳しい順序はSalesforce公式のTriggers and Order of Executionで確認できます。

Apex側のSOQLが少なくても、前後で動くFlow、別オブジェクトのトリガー、サブフローなどを合計すると上限へ達する場合があります。Apexだけを修正しても使用回数が下がらない場合は、次の範囲まで確認します。

  • 同じApexトリガーやクラスが繰り返し実行されていないか
  • FlowのGet Recordsがループ内にないか
  • FlowとApexが同じレコードを相互に更新していないか
  • 関連レコードの更新で別オブジェクトの自動化が起動していないか

Flowの実行ユーザーやデバッグログの確認方法は、次の関連記事でも実際の画面を使って整理しています。

ApexコードにSOQLが見つからないときの確認順

エラーがApexから返っていても、確認しているクラスにSOQLが見つからないことがあります。その場合は、次の順番で範囲を広げます。

  1. スタックトレースから呼び出し元のクラスとトリガーを確認する
  2. 対象オブジェクトに設定されたレコードトリガーフローを確認する
  3. 同じレコードを再更新している処理がないか確認する
  4. 関連レコードの更新で別オブジェクトの自動化が起動していないか確認する
  5. インストール済みパッケージの処理がログに含まれていないか確認する
  6. Limits.getQueries()を処理の節目へ追加し、増加箇所を絞り込む
ボンボン

「自分のクラスにSOQLがない=Apexは無関係」とは限らないよ。呼び出し先や後続処理まで、トランザクション単位で確認しよう。

効果がない、または注意が必要な対処

対処101エラーへの効果注意点
LIMIT 1を付ける直接的には解決しない取得件数は減るが、SOQLの実行回数は減らない
SELECT項目を減らす直接的には解決しないヒープや性能の改善には有効
WHERE条件やインデックスを見直す直接的には解決しない選択性や実行時間の改善には有効
非同期処理へ移す状況による上限が200回へ増えても、設計上の問題が残る可能性がある
バッチサイズを下げる状況による処理単位は減るが、根本原因を残さないか確認する

非同期化が要件に合う場合は、Salesforceが現在案内しているQueueable Apexなどを検討します。ただし、同期で完了すべき処理を上限回避だけのために非同期へ移すと、エラー処理や実行順序が複雑になります。

解決できないときのチェックリスト

  • デバッグログのLIMIT_USAGE_FOR_NSを確認したか
  • SOQL_EXECUTE_BEGINが同じ場所で繰り返されていないか
  • ループ内にSOQLやDMLがないか
  • 1件ではなく複数件を一括処理したときも動くか
  • Apexトリガーが同じレコードを再更新していないか
  • レコードトリガーフローやサブフローが起動していないか
  • FlowとApexで同じ項目を相互に更新していないか
  • 別オブジェクトの自動化まで連鎖していないか
  • Limits.getQueries()で増加箇所を比較したか
  • 非同期化やバッチサイズ変更で問題を隠していないか

まとめ

Too many SOQL queries: 101は、1つのトランザクションでSOQLの上限を超えたときに発生します。ループ内SOQLを修正するだけで解決するケースもありますが、Apexトリガーの再帰やFlowとの組み合わせが原因になることもあります。

  • 最初にLIMIT_USAGE_FOR_NSでSOQLの使用数を確認する
  • SOQL_EXECUTE_BEGINとスタックトレースから繰り返し箇所を探す
  • IDをSetへ集め、ループ外でSOQLを一括実行する
  • ApexだけでなくFlowや後続の自動化も確認する
  • LIMIT 1や取得項目の削減だけでは、SOQLの回数は減らない

エラーが出た行は、単に101回目のクエリを実行しようとした場所かもしれません。そこだけを直すのではなく、同じトランザクションで何が動いたかをログから順番に追うと、根本原因を見つけやすくなります。

参考リンク

よかったらシェアしてね!
  • URLをコピーしました!

この記事を書いた人

ちゃあむのアバター ちゃあむ エンジニア

Web開発やSaaS(ServiceNow、Salesforce)、業務システムに携わる、猫と食べることが大好きなインドア系ITエンジニアです。

コメント

コメントする


目次