Part 12: ガバナ制限 (Governor Limits) の超過を防ぐエラーハンドリング
前回は プラットフォームイベントとスケジュールバッチのエラーハンドリング を解説しました。
今回は Salesforce のガバナ制限 (Governor Limits) に関するエラーを防ぐ方法 について詳しく解説します。
1. ガバナ制限とは?
Salesforce は マルチテナント環境 で動作するため、1 つの組織がシステムリソースを独占しないように 「ガバナ制限 (Governor Limits)」 を設定しています。
例えば:
- SOQL クエリ制限 → 1 トランザクションで 100 件まで
- DML ステートメント制限 → 1 トランザクションで 150 件まで
- CPU タイム制限 → 同期処理は最大 10 秒、非同期処理は最大 60 秒まで
- ヒープサイズ制限 → 6MB (標準)、非同期処理は 12MB
- コールアウト制限 → 1 回のトランザクションで最大 120 秒
これらの制限を超えると LimitException が発生し、処理が中断されます。
2. LimitException の発生を防ぐ方法
2.1. SOQL クエリ制限 (System.QueryException)
SOQL クエリの実行回数が 1 トランザクションあたり 100 件 を超えると QueryException が発生します。
❌ NG 例: ループ内で SOQL クエリを実行
for (Account acc : Trigger.new) {
Contact c = [SELECT Id FROM Contact WHERE AccountId = :acc.Id LIMIT 1];
}
→ ガバナ制限エラー (System.LimitException: Too many SOQL queries: 101) が発生!
✅ OK 例: SOQL を 1 回だけ実行
Map<Id, Contact> contactMap = new Map<Id, Contact>();
for (Contact c : [SELECT Id, AccountId FROM Contact WHERE AccountId IN :Trigger.newMap.keySet()]) {
contactMap.put(c.AccountId, c);
}
// ループ内で SOQL を実行しない
for (Account acc : Trigger.new) {
Contact c = contactMap.get(acc.Id);
}
→ SOQL クエリを 1 回だけ実行し、マップを活用することで制限を回避!
適用ケース:
トリガー (Trigger) やバッチ処理で SOQL を実行する際、クエリ回数を削減したい場合
2.2. DML 制限 (System.DmlException)
DML (insert, update, delete, upsert) の実行回数が 1 トランザクションあたり 150 件 を超えると DmlException が発生します。
❌ NG 例: ループ内で DML を実行
for (Account acc : accounts) {
update acc;
}
→ ガバナ制限エラー (System.LimitException: Too many DML statements: 151) が発生!
✅ OK 例: DML を 1 回だけ実行
update accounts;
→ DML を 1 回でまとめて実行 (バルク処理) することで制限を回避!
適用ケース:
多数のレコードを更新する際に DML 制限を回避したい場合
2.3. CPU タイム制限 (System.LimitException)
同期処理では 最大 10 秒、非同期処理では 最大 60 秒 を超えると LimitException が発生します。
❌ NG 例: 非効率な処理
for (Integer i = 0; i < 1000000; i++) {
System.debug('処理中: ' + i);
}
→ 不要なループが多すぎると、CPU タイム制限に引っかかる!
✅ OK 例: 必要な処理だけ実行
for (Integer i = 0; i < 1000000; i+=100000) { // 10万ごとにログ出力
System.debug('処理中: ' + i);
}
→ 不要な処理を削減し、CPU タイムを節約!
適用ケース:
大量データ処理を行う場合に CPU タイム制限を回避したい場合
2.4. ヒープサイズ制限 (System.LimitException)
ヒープサイズの制限は 同期処理で 6MB、非同期処理で 12MB。
大量データを扱う場合、ヒープサイズを超えると LimitException が発生します。
❌ NG 例: 大量データをすべてメモリに保持
List<Account> allAccounts = [SELECT Id, Name FROM Account];
→ 全レコードを取得すると、ヒープサイズ制限を超える可能性あり!
✅ OK 例: バッチ処理を活用
Database.QueryLocator query = Database.getQueryLocator(
'SELECT Id, Name FROM Account'
);
→ Database.QueryLocator
を使うと、レコードを一括取得せずに処理できる!
適用ケース:
大量のレコードを扱う際にヒープサイズ制限を回避したい場合
2.5. コールアウト制限 (System.CalloutException)
コールアウト (外部 API 呼び出し) の制限は 1 トランザクションあたり 120 秒。
❌ NG 例: ループ内で API を呼び出す
for (Account acc : accounts) {
HttpRequest req = new HttpRequest();
req.setEndpoint('https://api.example.com/data');
req.setMethod('GET');
HttpResponse res = new Http().send(req);
}
→ ガバナ制限エラー (System.CalloutException: Callout loop detected) が発生!
✅ OK 例: コールアウトをバッチ処理に分割
// コールアウトをバッチ処理で実行
public class CalloutBatch implements Database.Batchable<SObject>, Database.AllowsCallouts {
public Iterable<SObject> start(Database.BatchableContext bc) {
return [SELECT Id FROM Account LIMIT 100];
}
public void execute(Database.BatchableContext bc, List<SObject> scope) {
HttpRequest req = new HttpRequest();
req.setEndpoint('https://api.example.com/data');
req.setMethod('GET');
HttpResponse res = new Http().send(req);
}
public void finish(Database.BatchableContext bc) {}
}
→ Database.AllowsCallouts
を使ってバッチ処理でコールアウト!
適用ケース:
API コールアウトを効率的に処理し、制限を回避したい場合
まとめ
今回は ガバナ制限 (Governor Limits) を超えないためのエラーハンドリング を解説しました。
✅ SOQL クエリ制限
- ループ内で SOQL を実行せず、マップを活用する
✅ DML 制限
- DML を 1 回だけ実行 (バルク処理)
✅ CPU タイム制限
- 不要なループや処理を減らす
✅ ヒープサイズ制限
- バッチ処理 (
Database.QueryLocator
) を活用
✅ コールアウト制限
- バッチ処理 (
Database.AllowsCallouts
) を使う
次の Part 13 では、例外をトラブルシューティングする方法 (デバッグ手法) について解説します!