Too Many SOQL 101 오류 해결방법 — 디버그 찍는 법부터 완전 정리

Salesforce 개발을 하다 보면 가장 많이 마주치는 에러가 바로 “Too many SOQL queries: 101” 입니다.
이 오류는 한 트랜잭션에서 SOQL을 100번 이상 실행하면 발생하며, 트리거·Flow·Apex 클래스가 얽힌 복합 로직에서 특히 자주 등장하는 문제입니다.

이 글에서는 단순한 이론이 아니라 “어디서 몇 번 SOQL이 돌고 있는지 실제로 파악하는 방법(=디버그 찍는 법)” → “문제 원인 찾기” → “코드 개선 방법” 순서로 완전히 정리해드립니다.


## 1. Too Many SOQL 101이 왜 발생하는가?

Salesforce의 Apex는 한 트랜잭션당 SOQL을 최대 100번까지만 허용합니다.
문제는 아래와 같은 구조에서 SOQL이 무한히 반복될 때입니다.

  • 루프 내부에서 쿼리 실행
  • 트리거가 여러 번 반복 실행
  • Flow + Apex + Trigger가 한 트랜잭션에 모두 실행
  • Helper 클래스에서 중복된 쿼리 실행

즉, “어디에서 반복되고 있는지” 를 먼저 찾아야 합니다.

이를 위해 필수인 것이 바로 SOQL 실행 횟수를 추적하는 디버그 로그입니다.


## 2. 디버그로 SOQL 반복 지점을 정확하게 찾는 방법

Too Many SOQL 문제는 대부분 “나는 쿼리를 3번밖에 안 썼는데?”라고 느끼지만,
뒤에서 Flow → Trigger → Handler → Helper → Validation Rule 등이 한꺼번에 도는 경우가 많습니다.

따라서 SOQL 실행 횟수를 직접 로그로 확인해야 합니다.


## 3. 디버그 로그 설정 (필수)

✔ 설정 방법

  1. Setup → Debug Logs
  2. New → 사용자 선택
  3. Debug Level을 Apex Code = FINEST, Apex Profiling = FINEST 로 설정
  4. 다시 실행

이제 SOQL, DML, Trigger 호출이 모두 기록됩니다.


## 4. SOQL 실행 횟수를 직접 찍는 디버그 코드

아래 코드는 “어디서 SOQL이 반복되는지” 가장 빠르게 찾는 실전 디버그 방법입니다.


### 📌 (1) SOQL 카운트 출력용 유틸리티 만들기

public class QueryCounter {
    public static Integer count = 0;

    public static List<SObject> runQuery(String q) {
        count++;
        System.debug('### SOQL COUNT: ' + count + ' / Query: ' + q);
        return Database.query(q);
    }
}

이제 기존 쿼리를 모두 QueryCounter.runQuery()로 바꾸면 쿼리 횟수를 실시간으로 추적할 수 있습니다.


### 📌 (2) 반복되는 쿼리 찾기

예시:

for(Account acc : Trigger.new){
    List<Contact> cons = QueryCounter.runQuery(
        'SELECT Id, Name FROM Contact WHERE AccountId = \'' + acc.Id + '\''
    );
}

실행 로그를 보면:

### SOQL COUNT: 1 / Query: SELECT...
### SOQL COUNT: 2 / Query: SELECT...
### SOQL COUNT: 3 / Query: SELECT...
...

이렇게 루프가 돌 때마다 카운트가 올라가면 루프 내부에서 쿼리 실행되는 것이 원인이라는 걸 바로 알 수 있습니다.


## 5. SOQL 반복 발생 구간 찾기 — 실전 방법

아래 순서대로 확인하면 누구든지 정확하게 원인을 찾을 수 있습니다.


### ✔ (1) Trigger → Handler → Helper 순으로 타고 들어가기

디버그 로그에서 찾기:

ENTERING Trigger
ENTERING Handler.updateProcess
ENTERING Helper.getContactList

이런 패턴을 보면
Handler → Helper 내부에서 반복 쿼리가 발생하는지 확인해야 합니다.


### ✔ (2) Flow가 같은 오브젝트를 Update하는지 확인

Flow가 아래 구조면 101 발생 확률이 매우 높습니다.

  • Before Save Flow → Record Update
  • After Save Flow → Record Update
  • Apex Trigger가 다시 Update
    → 무한 반복 구조

Flow Debug로 실행하면 “Triggered Flow Iteration 1,2,3…” 식으로 반복이 보입니다.


### ✔ (3) 배치 안에서 쿼리가 반복되는 경우

Batch execute() 내부에서 루프 쿼리를 수행하는 경우.

execute:
  START LOOP
    SOQL 1
    SOQL 2
  END LOOP

이런 구조면 반드시 루프 밖으로 SOQL 이동해야 합니다.


## 6. 101 에러 해결 코딩 패턴 (가장 중요한 부분)


### ✔ 1) 쿼리를 루프 밖으로 빼기

❌ 잘못된 코드

for(Account acc : Trigger.new){
    List<Contact> cons = [SELECT Id FROM Contact WHERE AccountId = :acc.Id];
}

✔ 수정한 코드

Set<Id> accIds = new Set<Id>();
for(Account acc : Trigger.new){
    accIds.add(acc.Id);
}

Map<Id, List<Contact>> contactMap =
    new Map<Id, List<Contact>>([
        SELECT Id, AccountId 
        FROM Contact 
        WHERE AccountId IN :accIds
    ]);

한 번의 쿼리로 전체 처리 가능.


### ✔ 2) Selective Query + 필터 최적화

WHERE 조건이 비효율적이면 중복 쿼리가 더 많이 발생함.

가능하면:

  • IN 조건 사용
  • Status / Type 등 인덱스 컬럼 활용
  • FIELDS() 대신 필요한 필드만 조회

### ✔ 3) Trigger Context 조건 분기

Trigger.new, Trigger.old 등을 잘못 중복 호출하면 SOQL이 불필요하게 실행됨.

if(Trigger.isAfter && Trigger.isUpdate){
    // 이 안에서만 로직 실행 → 중복 실행 방지
}

### ✔ 4) Static 변수로 재귀 방지

public class RecursionGuard {
    public static Boolean isRun = false;
}

Trigger에서:

if(RecursionGuard.isRun == false){
    RecursionGuard.isRun = true;
    // 실행 로직
}

재귀 실행이 막히면 SOQL 반복도 동시에 줄어듦.


### ✔ 5) Flow → Apex 분리 (중복 호출 차단)

Flow에서 같은 레코드를 Update하면 Trigger가 반복 실행되므로
Update 작업을 Apex로 옮기고, Flow에서의 Write는 최소화해야 함.


## 7. 실제 해결 과정 예시 (가장 많이 겪는 케이스)


✔ 사례 : Account 업데이트 시 SOQL 101 발생

원인:

  1. Flow가 Account 업데이트
  2. Trigger After Update 실행
  3. Helper에서 다시 Contact 조회
  4. Contact 갱신 후 → Account 업데이트
  5. Flow가 다시 실행
  6. 반복…

💡 해결:

  1. Flow의 Update 제거
  2. Trigger에서 Static 변수로 재귀 차단
  3. Contact 조회를 루프 외부로 통합
  4. Update 횟수 Bulkify

이렇게 하면 101 에러가 바로 해결됨.


## 8. 최종 정리

Too Many SOQL Query 101 오류는 아래 3가지만 해결하면 90%는 잡힙니다.


✔ 1️⃣ SOQL 횟수를 디버그로 직접 측정 (QueryCounter 활용)

✔ 2️⃣ 쿼리 반복 발생 구간 정확히 찾기

✔ 3️⃣ 루프 밖으로 쿼리 이동 + Flow/Apex 중복 호출 제거