본문 바로가기

데이터베이스(DA, AA, TA)

[MyBatis] 샾(#), 달러($)의 차이

 

SELECT * FROM TEST
WHERE testId = #{testId}

MyBatis에서 위와 같은 쿼리문을 실행하게 되면 데이터베이스에서는 아래 쿼리문에 대한 의미, 구문 분석 및 파싱 작업을 진행하게 됩니다.

 

SELECT * FROM TEST
WHERE testId = ?

만약 testId에 1 값이 바인드 되게 되면 database에서는 이미 파싱되어 있는 쿼리문을 재활용하게 되기에 위의 작업들을 줄일 수 있습니다. 단, 데이터베이스 옵티마이저에 대한 수행 계획은 항상 동일하다라는 것이 단점입니다. 수행 계획에 따른 데이터 추출은 데이터의 분포도에 영향을 받게 되는데 만약 1이라는 값이 TEST에 4개 존재하고, 2라는 값이 1000개 존재한다고 했을 때,

 

1을 추출할 때에는 인덱스 스캔이 유리하고, 2를 추출할 때에는 풀테이블 스캔이 유리합니다.

 

즉, 위의 쿼리문을 파싱할 때에는 testId 칼럼에 인덱스가 있다면 무조건 인덱스 스캔 수행 계획을 수립하게 되므로 2 값이 들어왔을 때에도 인덱스 스캔으로 인한 성능 저하가 발생할 수 있습니다.

 

물론 샾(#)을 사용할 때의 이점도 있는데 이는 SQL Injection 대비가 가능하다는 것입니다. 쿼리문을 실행하기에 앞서 구문 분석, 의미 분석, 파싱 작업을 진행하기에 sql injection의 코드를 만나게 되면 오류가 발생합니다.

 

그럼 아래와 같은 쿼리문은 어떠할까요? 아래 쿼리문은 샾(#) 대신에 달러($)를 사용한 케이스입니다.

 

SELECT * FROM TEST
WHERE testId = ${testId}

${testId}가 100이라고 할 때 위의 쿼리문을 실행하게 되면 다음과 같은 쿼리가 생성이 되고 데이터베이스에서는 의미, 구문 분석 및 파싱 작업을 진행하게 됩니다.

 

SELECT * FROM TEST
WHERE testId = 100

여기서 유념해야 할 부분은 #을 사용했을 때에는 ?으로 치환이 되는 반면에 $를 사용한 케이스에서는 ${testId} 영역에 100이라는 상수로 치환되어 쿼리를 수행하게 됩니다. 중요한 포인트는 101, 102, 103과 같이 숫자가 바뀔 때마다 파싱 작업을 항상 진행해야 한다라는 것입니다. 성능상 단점이 존재한다라는 것을 알 수 있습니다.

 

또한, 달러($) 방식은 SQL Injection에 취약한 것이 흠입니다. 그래서 중요한 쿼리문에서는 달러($)보다는 샾(#)이 권장됩니다. 달러($)를 사용하는 케이스는 대개 정렬을 위한 컬럼명에서 사용됩니다.

 

ORDER BY ${columnName} DESC

그렇다고 달러($) 방식이 단점만 있을까요? 그건 또 아닙니다. 위에서 언급했지만 옵티마이저 수행 계획에 이점을 주는 경우도 존재합니다.

 

예를 들어 인사 테이블에 직책 칼럼이 있다고 가정해보겠습니다. 직책 칼럼에 존재할 수 있는 데이터는 대충 사장, 부장, 차장, 과장, 대리, 사원 정도입니다. 데이터 분포도가 아래와 같다고 했을때 직책별 데이터 조회시 달러($)를 사용하게 되면 매번 옵티마이저의 수행 계획은 달라질 것입니다.

 

사장: 1명

부장: 10명

차장: 30명

과장: 50명

대리: 300명

사원: 100명

 

예상하는 수행 계획은 다음과 같습니다.

 

사장: 인덱스 스캔

부장: 인덱스 스캔

차장: 인덱스 스캔

과장: 인덱스 스캔

대리: 풀 테이블 스캔

사원: 풀 테이블 스캔

 

보편적으로 개발을 진행할 때 달러($)를 쓰면 위험하다는 인식으로 인해 대부분의 쿼리들이 샾(#) 만을 사용하고 있는데 이보다는 $,#에 따른 차이를 이해하고 각 상황에 맞게 사용하는 것이 더 좋은 서비스를 만들기 위한 하나의 방법이라고 생각합니다.

 

 

출처 - http://lng1982.tistory.com/246