ApexのDML制限を踏まえたベストプラクティス


Salesforceの **DML（データ操作言語）** には制限があり、適切に処理しないと **ガバナ制限違反** でエラーが発生します。  
今回は、**DMLの制限と、それを考慮したベストプラクティスとアンチパターン** を具体的なコードとともに整理しました。

---

## **DMLの主な制限**
1. **実行されるDMLステートメントの合計数**  
   - **1回のトランザクションで150回まで** の `INSERT`, `UPDATE`, `DELETE`, `UPSERT`, `MERGE`, `UNDELETE` が実行可能。
  
2. **DMLステートメントの結果として処理されるレコードの合計数**  
   - **1回のトランザクションで10,000件まで** のレコードを処理可能。
  
3. **ヒープメモリの合計サイズ**  
   - **6MB（同期処理）または12MB（非同期処理）まで**。
   - メモリを使いすぎると `System.LimitException: Apex heap size too large` エラーが発生。

---

## **1. ❌ アンチパターン: DMLステートメントを個別実行**
### **✅ 理由**
- **DML制限（最大150回）にすぐ到達** してしまう。
- **処理速度が遅くなる**（DMLは1回ごとにデータベースアクセスが発生するため）。

---

### **📌 ❌ 悪い例（DMLステートメントを個別実行）**
```apex
public void updateContacts() {
    List<Contact> contacts = [SELECT Id, LastName FROM Contact];
    
    for (Contact con : contacts) {
        con.LastName = 'Updated Name';
        update con; // ❌ ループ内でDMLを実行（150件超えるとエラー）
    }
}
```
⛔ **問題点**
- **コンタクトが150件以上あるとエラー（DML制限違反）**
- **データベースへのアクセス回数が多く、パフォーマンスが低下**

---

### **✅ 良い例（バッチ処理を使用）**
```apex
public void updateContactsBatch() {
    List<Contact> contacts = [SELECT Id, LastName FROM Contact];

    for (Contact con : contacts) {
        con.LastName = 'Updated Name';
    }

    update contacts; // ✅ 1回のDMLでまとめて更新
}
```
✅ **DMLを1回にまとめることで、制限回避＆高速化！**

---

## **2. ✅ 原則：DMLステートメントのためのバッチデータ**
### **✅ 理由**
- **ループ内のDMLを削減し、ガバナ制限を回避** する。
- **データをコレクションに集め、DMLを一括で実行することで高速化**。

---

### **📌 ❌ 悪い例（ループ内でDMLを実行）**
```apex
public void insertAccountsIndividually() {
    for (Integer i = 0; i < 200; i++) {
        Account acc = new Account(Name = 'Test Account ' + i);
        insert acc; // ❌ ループごとにDMLが発生（150件超えるとエラー）
    }
}
```
⛔ **問題点**
- **150回以上のDML実行でエラー** (`System.LimitException: Too many DML statements`)
- **データベースへの負荷が大きく、処理速度が低下**

---

### **📌 ✅ 良い例（リストを使ってDMLをバッチ処理）**
```apex
public void insertAccountsBatch() {
    List<Account> accountsToInsert = new List<Account>();

    for (Integer i = 0; i < 200; i++) {
        accountsToInsert.add(new Account(Name = 'Test Account ' + i));
    }

    insert accountsToInsert; // ✅ 1回のDMLで一括処理
}
```
✅ **データをリストにまとめ、DMLを1回で実行することで、パフォーマンス向上＆ガバナ制限回避！**

---

## **3. ✅ 原則：SOQL for ループを使用して200件ごとのバッチを作成**
### **✅ 理由**
- **ヒープサイズ制限を回避する**（データを少量ずつ処理）。
- **SOQLの結果を自動的にバッチ処理できる**。

---

### **📌 ❌ 悪い例（全レコードを一度に取得）**
```apex
List<Account> accounts = [SELECT Id, Name FROM Account];

for (Account acc : accounts) {
    System.debug(acc.Name);
}
```
⛔ **問題点**
- **取得したデータが多すぎると、ヒープサイズ制限（6MB）に達する可能性あり**。

---

### **📌 ✅ 良い例（SOQL for ループで200件ずつ処理）**
```apex
for (Account acc : [SELECT Id, Name FROM Account]) {
    System.debug(acc.Name); // ✅ 少量ずつ処理し、メモリ使用量を抑える
}
```
✅ **SOQL for ループを使うと、Salesforceが自動的にレコードをバッチ処理し、ヒープサイズを抑える！**

---

### **📌 ✅ 大量データを安全に更新するバッチ処理**
```apex
List<Account> accountsToUpdate = new List<Account>();

for (Account acc : [SELECT Id, Name FROM Account]) {
    acc.Name = acc.Name + ' Updated';
    accountsToUpdate.add(acc);

    if (accountsToUpdate.size() == 200) {
        update accountsToUpdate; // ✅ 200件ごとにDML実行
        accountsToUpdate.clear();
    }
}

// 残りのレコードを処理
if (!accountsToUpdate.isEmpty()) {
    update accountsToUpdate;
}
```
✅ **200件ごとに処理することで、ガバナ制限を回避＆パフォーマンス向上！**

---

## **🎯 まとめ**
| 項目 | ❌ 悪い例 | ✅ 良い例 |
|------|---------|---------|
| **DMLをループ内で実行** | `update con;` をループ内で実行 | **リストにまとめて1回のDMLで処理** |
| **DMLのバッチ処理** | `insert acc;` を200回実行 | **200件ごとにDMLを実行** |
| **SOQL for ループを活用** | `List<Account> accounts = [SOQL]` で全取得 | **for (record : [SOQL]) を使用** |