[PostgreSQL] MyBatis foreach + UNION ALL 쿼리의 성능 문제와 PostgreSQL 최적화

2025. 7. 5. 01:40·개발 (Development)/PostgreSQL

최근 프로젝트에서 MyBatis를 사용하여 여러 자산(asset) 정보를 기준으로 태그(tag) 데이터를 조회하는 기능을 구현하던 중, 성능 저하 문제가 발생했습니다. 문제의 원인을 추적해보니 foreach 루프 안에서 UNION ALL로 다수의 SELECT 구문을 반복 생성하는 방식이 주요 원인이었습니다. 본 글에서는 해당 구조의 문제점과 PostgreSQL에 적합한 최적화 방법을 실제 경험을 바탕으로 정리하였습니다.

문제 상황: MyBatis foreach + UNION ALL 사용

처음 작성한 쿼리는 아래와 같은 형태였습니다.

<select id="getTagList" resultType="java.util.HashMap">
  <foreach item="item" index="index" collection="itemList" separator="UNION ALL">
    SELECT
      item_code,
      tag_code,
      tag_name,
      #{item.assetId} AS asset_physical_name,
      #{item.logicalId} AS asset_name
    FROM
      tag_efficiency_table
    WHERE
      logical_id = #{item.logicalId}
      AND item_code = #{item.itemCode}
  </foreach>
</select>

이 방식은 각 조건마다 개별 SELECT 쿼리를 만들어 UNION ALL로 연결합니다. 데이터 건수가 적을 때는 문제없이 동작하지만, 50건 이상 넘어가면 SQL 자체가 길어지고 DB 처리 시간이 급격히 증가했습니다.

원인 분석

  1. 반복된 SELECT 생성
    MyBatis의 <foreach>는 루프마다 SQL 문을 생성하므로, 100개의 조건이 있다면 100개의 SELECT 문이 생성됩니다.
  2. 캐시 비효율
    바인딩 값이 SQL 문장 내 직접 삽입되므로, DB 캐시가 제대로 작동하지 않게 됩니다.
  3. SQL 과부하
    PostgreSQL은 SQL의 길이나 복잡도에 따라 실행 계획 비용이 증가할 수 있으므로, UNION ALL 남용은 오히려 병목이 됩니다.

해결 방향: 서브쿼리 + JOIN 방식

PostgreSQL에서는 서브쿼리와 JOIN을 통한 조건 필터링을 매우 효율적으로 처리합니다. 이를 활용해 다음과 같이 구조를 변경하였습니다.

최적화된 MyBatis 쿼리

<select id="getTagList" resultType="java.util.HashMap">
  SELECT
    t.item_code,
    t.tag_code,
    t.tag_name,
    m.asset_id AS asset_physical_name,
    m.logical_id AS asset_name
  FROM
    tag_efficiency_table t
  INNER JOIN (
    <foreach item="item" index="index" collection="itemList" separator="UNION ALL">
      SELECT
        #{item.assetId} AS asset_id,
        #{item.logicalId} AS logical_id,
        #{item.itemCode} AS item_code
    </foreach>
  ) m ON t.item_code = m.item_code
       AND t.logical_id = m.logical_id
</select>

결과 및 성능 개선

항목 개선 전 개선 후
SQL 실행 횟수 N회 (N개의 SELECT) 1회
SQL 문자열 길이 N배 증가 일정
PostgreSQL 쿼리 실행 시간 수 초 수십~수백 ms
유지보수성 낮음 (반복 구조 복잡) 높음 (단일 쿼리 구조)

또한 tag_efficiency_table의 logical_id, item_code에 복합 인덱스를 추가함으로써 쿼리 성능을 더욱 끌어올릴 수 있었습니다.

CREATE INDEX idx_logical_item ON tag_efficiency_table (logical_id, item_code);

마무리하며

이번 최적화 경험을 통해 다음과 같은 인사이트를 얻을 수 있었습니다:

  • MyBatis의 foreach + UNION ALL은 간편하지만, 반복 수가 많아지면 반드시 성능 병목을 점검해야 한다.
  • PostgreSQL에서는 UNION ALL보다 JOIN을 활용한 조건 매칭이 훨씬 효율적이다.
  • 가능한 한 SQL을 정적이고 단일 구조로 구성하여 DB 캐시 효율을 높이는 것이 좋다.

MyBatis를 활용한 복합 조건 처리 시, 위와 같은 방식으로 리팩토링하면 운영 중 성능 문제를 예방할 수 있습니다.

반응형

'개발 (Development) > PostgreSQL' 카테고리의 다른 글

[PostgreSQL] PostgreSQL에서 threshold 값 이력 관리 및 최신값 조회 테이블 설계하기  (0) 2025.06.28
[PostgreSQL] 두 테이블 비교 시 기준 테이블의 데이터를 모두 유지하는 방법  (0) 2025.05.25
[PostgreSQL] 시퀀스 자동 증가 설정과 오류 해결 방법 정리  (0) 2025.05.18
[PostgreSQL] 실전 활용 - 문자열 포함, 쉼표 구분 배열 매칭, JSON 배열 필터링  (0) 2025.04.26
[PostgreSQL] PostgreSQL JSON 쿼리 성능 개선과 Java에서의 활용  (1) 2025.04.06
'개발 (Development)/PostgreSQL' 카테고리의 다른 글
  • [PostgreSQL] PostgreSQL에서 threshold 값 이력 관리 및 최신값 조회 테이블 설계하기
  • [PostgreSQL] 두 테이블 비교 시 기준 테이블의 데이터를 모두 유지하는 방법
  • [PostgreSQL] 시퀀스 자동 증가 설정과 오류 해결 방법 정리
  • [PostgreSQL] 실전 활용 - 문자열 포함, 쉼표 구분 배열 매칭, JSON 배열 필터링
LoopThinker
LoopThinker
모르는 것을 알아가고, 아는 것을 더 깊게 파고드는 공간
  • LoopThinker
    CodeMemoir
    LoopThinker
  • 전체
    오늘
    어제
    • 분류 전체보기 (192) N
      • 개발 (Development) (128) N
        • Algorithm (1)
        • Angular (1)
        • AWS (4)
        • DeepSeek (2)
        • Docker (6)
        • Git (3)
        • Java (23) N
        • JavaScript (4)
        • Kafka (4)
        • Kubernetes (4)
        • Linux (6)
        • PostgreSQL (33)
        • Python (19) N
        • React (3)
        • TypeScript (3)
        • Vue.js (5)
        • General (7) N
      • 데이터 분석 (Data Analysis) (1)
      • 알고리즘 문제 풀이 (Problem Solving.. (27)
      • 자격증 (Certifications) (24)
        • ADsP (14)
        • 정보처리기사 (4)
        • Linux Master (5)
        • SQLD (1)
      • 기술 동향 (Tech Trends) (10)
      • 기타 (Others) (2)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    MyBatis
    백준온라인저지
    Linux
    springboot
    리눅스 마스터
    Kubernetes
    데이터분석
    백준자바
    Vue.js
    deepseek
    Kafka
    ADsP
    typescript
    자바
    pandas
    리눅스 마스터 2급 2차
    AWS
    JSON
    Vue
    Linux master
    백준알고리즘
    java
    PostgreSQL
    python
    javascript
    오답노트
    DevOps
    백준
    리눅스 마스터 2급
    docker
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
LoopThinker
[PostgreSQL] MyBatis foreach + UNION ALL 쿼리의 성능 문제와 PostgreSQL 최적화
상단으로

티스토리툴바