최근 프로젝트에서 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 처리 시간이 급격히 증가했습니다.
원인 분석
- 반복된 SELECT 생성
MyBatis의<foreach>
는 루프마다 SQL 문을 생성하므로, 100개의 조건이 있다면 100개의 SELECT 문이 생성됩니다. - 캐시 비효율
바인딩 값이 SQL 문장 내 직접 삽입되므로, DB 캐시가 제대로 작동하지 않게 됩니다. - 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 |