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. 디버그 로그 설정 (필수)
✔ 설정 방법
- Setup → Debug Logs
- New → 사용자 선택
- Debug Level을 Apex Code = FINEST, Apex Profiling = FINEST 로 설정
- 다시 실행
이제 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 발생
원인:
- Flow가 Account 업데이트
- Trigger After Update 실행
- Helper에서 다시 Contact 조회
- Contact 갱신 후 → Account 업데이트
- Flow가 다시 실행
- 반복…
💡 해결:
- Flow의 Update 제거
- Trigger에서 Static 변수로 재귀 차단
- Contact 조회를 루프 외부로 통합
- Update 횟수 Bulkify
이렇게 하면 101 에러가 바로 해결됨.
## 8. 최종 정리
Too Many SOQL Query 101 오류는 아래 3가지만 해결하면 90%는 잡힙니다.