MyBatis 샾(#) 달러($) 차이
select * from TestTable
where testId = #{testId}
MyBatis에서 위와 같은 쿼리문을 실행하게 되면 database에서는 아래 쿼리문에 대한 의미, 구문 분석 및 파싱 작업을 진행하게 된다.
select * from TestTable
where testId = ?
만약 testId에 1 값이 바인드 되게 되면 database에서는 이미 파싱되어 있는 쿼리문을 재활용하게 되기에 위의 작업들을 줄일 수 있다.
단, 데이터베이스 옵티마이저에 대한 수행 계획은 항상 동일하다라는 것이 단점이다.
수행 계획에 따른 데이터 추출은 데이터의 분포도에 영향을 받게 되는데 만약 1 이라는 값이 TestTable에 4개 존재하고, 2 라는 값이 1000개 존재한다고 했을 때
1을 추출할 때에는 인덱스 스캔이 유리하고
2를 추출할 때에는 풀 테이블 스캔이 유리하다.
즉, 위의 쿼리문을 파싱할 때에는 testId 컬럼에 인덱스가 있다면 무조건 인덱스 스캔 수행 계획을 수립하게 되므로 2 값이 들어왔을 때에도 인덱스 스캔으로 인한 성능 저하가 발생할 수 있다.
물론 샾(#)을 사용할 때의 이점도 있는데 이는 SQL Injection 대비가 가능하다는 것이다.
쿼리문을 실행하기에 앞서 구문 분석, 의미 분석, 파싱 작업을 진행하기에 sql injection의 코드를 만나게 되면 오류가 발생한다.
그럼 아래와 같은 쿼리문은 어떠할까?
샾(#) 대신에 달러($) 를 사용한 케이스이다.
select * from TestTable
where testId = ${testId}
${testId}가 100 이라고 할때 위의 쿼리문을 실행하게 되면 다음과 같은 쿼리가 생성이 되고 데이터베이스에서는 의미, 구문 분석 및 파싱 작업을 진행하게 된다.
select * from TestTable
where testId = 100
여기서 유념해야 할 부분은 # 을 사용했을 때에는 ? 으로 치환이 되는 반면에 $ 를 사용한 케이스에서는 ${testId} 영역에 100이라는 상수로 치환되어 쿼리를 수행하게 된다.
중요한 포인트는 101, 102, 103 과 같이 숫자가 바뀔 때마다 파싱 작업을 항상 진행해야 한다라는 것이다.
성능상에 단점이 존재한다라는 것을 알 수 있다.
또한, 달러($) 방식은 SQL Injection에 취약한 것이 흠이다.
그래서 난 중요한 쿼리문에서는 달러($) 를 사용하지 않고, 샾(#)을 사용한다.
달러($)를 사용하는 케이스는 대게 정렬을 위한 컬럼명에 사용한다. (즉, 아래와 같은 방법으로..)
order by ${columnName} DESC
그렇다고 달러($) 방식이 단점만 있느냐?
그건 또 아니다.
위에서 언급했지만 옵티마이저 수행 계획에 이점을 주는 경우도 존재한다.
예를 들어 인사 테이블에 직책 컬럼이 있다고 하자.
직책 컬럼에 존재할 수 있는 데이터는 대충 사장, 부장, 차장, 과장, 대리, 사원 정도이다.
데이터 분포도가 아래와 같다고 했을 때 직책별 데이터 조회 시 달러($)를 사용하게 되면 매번 옵티마이저의 수행 계획은 달라질 것이다.
사장 : 1명
부장 : 10명
차장 : 30명
과장 : 50명
대리 : 300명
사원 : 100명
예상하는 수행 계획은 다음과 같을 것이다.
사장 : 인덱스 스캔
부장 : 인덱스 스캔
차장 : 인덱스 스캔
과장 : 인덱스 스캔
대리 : 풀 테이블 스캔
사원 : 풀 테이블 스캔
보편적으로 개발을 진행할 때 달러($)를 쓰면 위험하다는 인식으로 인해 대부분의 쿼리들이 샾(#) 만을 사용하고 있는데 이보다는 $, # 에 따른 차이를 이해하고 각 상황에 맞게 사용하는 것이 더 좋은 서비스를 만들기 위한 하나의 방법이라고 생각한다.
'프로그래밍' 카테고리의 다른 글
jedis returnBrokenResource 메서드에 대한 오해 (0) | 2016.03.09 |
---|---|
Mybatis 쿼리 로그 출력 (0) | 2016.03.02 |
비트단위의 논리곱을 이용한 데이터 활용 방법 - 논리곱(and) & (1) | 2016.01.31 |
진수 변환 (0) | 2016.01.26 |
tag 라이브러리 동작 원리에 대해서 생각해 보자 (0) | 2016.01.18 |
Spring Scoped Proxy Beans (prototype 빈 DI로 주입해서 사용) (0) | 2016.01.14 |
Java Generic 정리 (3) | 2016.01.13 |
Spring RestTemplate 사용 시 HTTP request, response 로그 남기기 (0) | 2015.10.16 |