바인드 변수란?
바인드 변수란 SQL에서 특정 값이 동적으로 바뀔 수 있는 위치에 사용되는 변수이다. 주로 SQL 쿼리문에서 WHERE 절의 조건 값으로 사용되며, 값을 실행 시점에 설정한다.
String SQL = SELECT * FROM NAME WHERE NAME = ?;
PreparedStatement = st = con.prepareStatement(SQL);
st.setString(1, name);
하지만 우리는 다음과 같이 리터럴 변수를 이용하여 쿼리를 더 간결하게 작성할 수도 있는데, 바인드 변수를 왜 사용해야 하는지 의문이 들 수도 있다.
String SQL = SELECT * FROM NAME WHERE NAME = 'name' ;
PreparedStatement = st = con.prepareStatement(SQL);
이를 먼저 알기 위해서는 DBMS가 쿼리를 요청 받았을 때, 어떻게 처리하는지 먼저 알아야한다.
DBMS가 쿼리를 요청받았을 때 처리 과정
- SQL 파싱 (Parsing):
- DBMS가 쿼리를 받으면, 먼저 쿼리 문법이 올바른지 확인하는 과정인 파싱을 수행한다. 이 단계에서 SQL 문법 오류를 잡아낸다.
- 파싱 결과로 파싱 트리가 생성된다. 이 파싱 트리는 DBMS가 쿼리를 어떻게 실행할지 결정하는 데 필요한 구조를 제공한다.
- 최적화 (Optimization):
- 파싱된 쿼리는 DBMS의 쿼리 최적화기(Query Optimizer)에 의해 최적화된다.
- 최적화기는 다양한 실행 계획을 고려하고, 가장 효율적인 방법을 선택한다. 예를 들어, 인덱스를 사용할지, 테이블을 어떻게 조인할지 등을 결정하게 된다.
- 이 단계에서 쿼리의 성능을 개선할 수 있는 여러 가지 실행 계획들이 만들어지며, DBMS는 가장 효율적인 실행 계획을 선택한다.
- 로우 소스 생성
- SQL 옵티마이저가 선택한 실행결로를 실제 실행 가능한 코드 또는 프로시저 형태로 포맷팅을 하게 된다.
- 실행 (Execution):
- DBMS는 최적화된 실행 계획을 기반으로 실제 데이터를 검색한다.
- 이 단계에서는 DBMS가 데이터를 읽고, 필요한 계산을 수행하며, 최종 결과를 반환한다.
- 결과 반환 (Result Return):
- 최종 결과는 클라이언트 애플리케이션으로 반환된다.
이때, 사용자가 SQL문을 전달하면 DBMS는 SQL을 파싱한 후, 해당 SQL이 *라이브러리 캐시에 존재하는지 확인하게 되는데, SQL을 캐시에서 찾아 곧바로 실행단계로 넘어가는 것을 “소프트 파싱”이라 하고, 캐시에서 찾지 못해 최적화 및 로우 소스 생성 단계가지 모두 거치는 것을 “하드 파싱”이라고 한다.
*라이브러리 캐시: SQL 파싱, 최적화 로우 소스 생성 과정을 거쳐 생성한 프로시저를 재사용할 수 있도록 캐싱해 두는데, 이러한 메모리 공간을 “라이브러리 캐시”라고 한다.
바인드 변수를 사용하는 이유
결론부터 말하면, 바인드 변수를 사용하는 주된 이유는 DBMS의 부하를 줄이고 성능을 최적화하기 위함이다.
DBMS는 SQL을 실행하기 전에 파싱과 최적화 단계를 거쳐 실행 계획을 생성하게 되며, 생성된 실행 계획은 SGA 공간의 라이브러리 캐시에 저장되어 동일한 SQL이 반복적으로 실행될 때 재사용된다. 이때, 라이브러리 캐시에서 SQL을 찾기위해 사용하는 키 값이 “SQL문 그 자체” 인데, 한 부분이라도 다르다면 키 값이 달라져서 캐시된 값을 찾지 못한다.
다음과 같은 쿼리가 있다고 가정한다.
String SQL = "SELECT * FROM NAME WHERE NAME = 'name' "
이 쿼리를 통해 100만명의 사용자가 서비스를 이용하면 라이브러리 캐시에는 다음과 같이 저장된다.
SELECT * FROM NAME WHERE NAME = 'John';
SELECT * FROM NAME WHERE NAME = 'Alice';
SELECT * FROM NAME WHERE NAME = 'Michael';
SELECT * FROM NAME WHERE NAME = 'Sophia';
SELECT * FROM NAME WHERE NAME = 'Daniel';
SELECT * FROM NAME WHERE NAME = 'Emma';
SELECT * FROM NAME WHERE NAME = 'James';
SELECT * FROM NAME WHERE NAME = 'Olivia';
SELECT * FROM NAME WHERE NAME = 'William';
SELECT * FROM NAME WHERE NAME = 'Isabella';
위와 같이 서로 다른 값으로 인해 동일한 SQL 구조라도 각각 별도의 실행 계획을 새로 생성하며 하드 파싱을 하게 된다. 이로 인해 DBMS의 부하가 증가한다. 이 문제를 해결하기 위해, 캐시된 실행 계획을 재사용할 수 있도록 파라미터 방식으로 SQL을 작성하는 방법이 바로 바인드 변수의 사용이다.
위의 쿼리를 다음과 같이 수정한다.
String SQL = "SELECT * FROM NAME WHERE NAME = ? "
이렇게 수정하면, 해당 SQL에 대한 쿼리 최적화는 최초 한 번만 일어나고 라이브러리 캐시에는 다음과 같이 저장된다. 즉, 캐시된 SQL을 100만 사용자가 공유하며 재사용하게 된다.
String SQL = "SELECT * FROM NAME WHERE NAME = :1 "
이때 :1은 첫 번째 바인드 변수로, ?와 같은 역할을 하며 실행 시점에 값을 대입할 수 있게 된다. 이 방식은 값이 바뀌더라도 동일한 실행 계획을 재사용하게 되어 소프트 파싱이 이루어진다.
결론
바인드 변수는 DB 성능 최적화와 보안을 강화하는 데 중요한 역할을 한다. SQL 실행 시 파싱과 최적화 과정의 부하를 줄이고 동일한 실행 계획을 재사용할 수 있어 성능을 향상시킨다. 또한, 값과 쿼리 구조를 분리하여 SQL 인젝션과 같은 보안 위협을 효과적으로 방지할 수 있다. 더불어, 쿼리의 재사용성과 코드 가독성을 높이고, 캐시 메모리를 효율적으로 관리할 수 있어 유지보수성을 개선하는 데 기여한다.
참고
'Databases' 카테고리의 다른 글
수직적 탐색 수평적 탐색 (0) | 2025.02.21 |
---|---|
NL 조인 (0) | 2025.02.07 |
DELETE vs TRUNCATE (0) | 2025.02.03 |
뷰(View)의 개념과 뷰(View)를 사용해야 하는 이유 (2) | 2024.12.06 |
인덱스란? (0) | 2024.09.06 |