MyBatis 바인딩 파라미터와 오라클 실행계획 차이로 인한 데이터 미조회 현상
현상: 로그에는 정상, 데이터는 안 들어옴?
실무에서 MyBatis를 사용하다 보면, 쿼리 로그에는 분명히 정상적으로 SQL이 찍히는데 실제로는 데이터가 DTO에 매핑되지 않는 황당한 경험을 하신 적이 있으신가요?
특히 오라클(Oracle)과 같이 바인딩 파라미터(:param) 방식에 민감한 DB에서는 이런 현상이 종종 발생합니다.
예시 상황
예를 들어, 아래와 같이 직원 번호와 이름을 모두 검색하는 동적 쿼리가 있다고 가정해봅시다.
<!-- MyBatis Mapper -->
SELECT *
FROM employee
WHERE REGEXP_LIKE(emp_no, #{keyword}, 'i')
OR emp_no IN (
SELECT emp_no FROM employee_info WHERE emp_name LIKE '%' || #{keyword} || '%'
)
로그에는 아래처럼 실제 값이 치환되어 잘 찍힙니다.
SELECT *
FROM employee
WHERE REGEXP_LIKE(emp_no, 'kim', 'i')
OR emp_no IN (
SELECT emp_no FROM employee_info WHERE emp_name LIKE '%kim%'
)
이 쿼리를 SQL Developer에서 직접 실행하면 정상적으로 결과가 나옵니다.
하지만! 실제 서버에서는?
실제 MyBatis가 서버에서 실행하는 쿼리는 아래처럼 바인딩 파라미터 형태로 동작합니다.
SELECT *
FROM employee
WHERE REGEXP_LIKE(emp_no, :1, 'i')
OR emp_no IN (
SELECT emp_no FROM employee_info WHERE emp_name LIKE '%' || :1 || '%'
)
이렇게 실행했을 때, 아무런 결과도 조회되지 않는 경우가 발생할 수 있습니다.
원인: 오라클의 실행계획(Execution Plan) 차이
오라클은 바인딩 파라미터(:1, :searchValue)와 리터럴('kim')이 들어갔을 때 실행계획을 다르게 세우는 경우가 많습니다.
대표적인 차이점
- LIKE, REGEXP_LIKE와 바인딩 파라미터
REGEXP_LIKE(emp_no, :1, 'i')
처럼 파라미터가 들어가면 옵티마이저가 제대로 인덱스를 타지 않거나, 예상을 벗어난 실행계획을 세울 수 있습니다. - 반면, 리터럴이 들어가면(예:
'kim'
) 좀 더 효율적인 실행계획을 만듭니다. - OR 조건의 조합OR 조건 내부에 바인딩 파라미터가 들어가면 옵티마이저가 예상치 못한 결과를 낼 수 있습니다.
- 특히 LIKE, REGEXP_LIKE, IN 서브쿼리 조합에서 바인딩값 사용시 결과가 아예 안 나올 수도 있습니다.
해결 방법
1. 검색 조건을 명확히 분리
동적 쿼리로, emp_no 검색이면 그 조건만, emp_name 검색이면 그 조건만 실행하도록 쿼리를 분리합니다.
<!-- MyBatis 동적쿼리 예시 -->
<select id="searchEmployee" parameterType="map" resultType="Employee">
SELECT *
FROM employee
<where>
<if test="searchType == 'emp_no'">
AND REGEXP_LIKE(emp_no, #{keyword}, 'i')
</if>
<if test="searchType == 'emp_name'">
AND emp_no IN (
SELECT emp_no FROM employee_info WHERE emp_name LIKE '%' || #{keyword} || '%'
)
</if>
</where>
</select>
2. OR 조합이 불가피하다면
- 파라미터를 직접 리터럴로 치환하는 방식도 고려할 수 있습니다.
- (단, 이때는 SQL Injection 등 보안 이슈에 주의!)
- 혹은 쿼리를 쪼개서 각각 실행 후 결과를 합치는 방식도 실무에서 종종 사용합니다.
3. 실행계획을 강제로 리프레시
- 오라클 힌트 사용 (예:
/*+ dynamic_sampling(4) */
등) - 바인딩 파라미터의 타입/포맷을 명확히 맞춰서 전달
정리
- MyBatis 로그에 찍힌 쿼리와 실제 서버에서 실행되는 쿼리는 다를 수 있습니다.
- 오라클은 바인딩 파라미터로 들어가면, 리터럴과는 다른 실행계획을 세워서 결과가 다르게 나올 수 있습니다.
- 동적쿼리로 조건을 분리하거나, 쿼리를 분할하는 방법으로 해결 가능합니다.
실무 팁
- 바인딩 파라미터와 실행계획의 차이를 의심해보세요.
- SQL Developer에서 바인딩 모드로 쿼리를 실행해보면 서버와 동일한 결과를 볼 수 있습니다.
- 로그에만 의존하지 말고, 실제 바인딩 쿼리와 결과를 꼭 확인해보세요!
이런 현상은 실무에서 한 번쯤 꼭 마주치게 됩니다.
문제의 원인을 빠르게 파악하고, 위와 같은 방법으로 해결해보세요!
궁금한 점이나 추가로 다뤄줬으면 하는 내용이 있다면 댓글로 남겨주세요!
댓글