189. 동적 SQL (Dynamic SQL)
핵심 인사이트: SQL은 보통 딱딱하게 굳어있는 문장이다. 하지만 "사용자가 검색창에 이름을 입력하면 이름으로 조회하고, 안 쓰면 전체 조회하라" 같은 상황에선 쿼리가 고무줄처럼 변해야 한다. 문자열 조각들을 런타임에 이리저리 레고처럼 조립해서 그 자리에서 실행시키는 마법이 동적 SQL이다.
Ⅰ. 정적 SQL과 동적 SQL의 비교
프로그래밍 언어(Java, Python 등)나 절차적 SQL(PL/SQL) 내에서 SQL을 실행할 때, 쿼리의 형태가 고정되어 있는지 변하는지에 따라 나뉩니다.
1. 정적 SQL (Static SQL)
- 소스 코드에
SELECT * FROM EMP WHERE EMP_ID = 10처럼 쿼리 문장이 완벽하게 완성된 형태(Hard-coded) 로 고정되어 있는 방식입니다. - 컴파일 시점에 구문 오류(Syntax)를 100% 잡아낼 수 있고, DB가 미리 실행 계획을 짜둘 수 있어 성능이 빠릅니다.
2. 동적 SQL (Dynamic SQL)
- 실행 시점(Runtime)에 사용자의 입력이나 조건에 따라 문자열(String) 조각들을 이어 붙여서(Concatenation) SQL 문장을 동적으로 생성해 내고 실행하는 방식입니다.
Ⅱ. 동적 SQL이 반드시 필요한 상황
- 다이내믹 검색 조건: 쇼핑몰 검색 필터처럼, 고객이 '카테고리'만 고를 수도 있고 '가격 범위'를 추가할 수도 있어서
WHERE절이 수십 가지로 변할 때. - 테이블/컬럼명 동적 변경: "올해 데이터를 조회하면
SALES_2024테이블을 읽고, 작년이면SALES_2023테이블을 읽어라"처럼FROM절의 테이블 이름 자체가 변수일 때. (정적 SQL에서는 테이블명을 변수로 줄 수 없습니다) - DDL 실행: 프로시저 내부에서 테이블을 생성(
CREATE)하거나 파티션을 자를(DROP) 때는 정적 SQL이 작동하지 않으므로 무조건 동적 SQL로 문자열을 조립해 실행해야 합니다.
Ⅲ. PL/SQL에서의 동적 SQL 실행 예시 (EXECUTE IMMEDIATE)
오라클에서는 조립된 문자열 쿼리를 EXECUTE IMMEDIATE 명령어에 던져주어 즉시 실행합니다.
DECLARE
v_sql VARCHAR2(1000); -- 쿼리를 담을 빈 문자열 변수
v_table_name VARCHAR2(20) := 'EMPLOYEES'; -- 동적으로 변하는 테이블명
v_count NUMBER;
BEGIN
-- 문자열 조립: 'SELECT COUNT(*) FROM EMPLOYEES' 가 됨
v_sql := 'SELECT COUNT(*) FROM ' || v_table_name;
-- 조립된 쿼리를 그 자리에서 펑! 하고 실행하여 결과를 변수에 담음
EXECUTE IMMEDIATE v_sql INTO v_count;
END;
Ⅳ. 동적 SQL의 치명적 단점과 방어책
| 단점 | 설명 |
|---|---|
| SQL 인젝션(Injection) 취약점 | 사용자가 입력한 문자열을 쿼리에 그대로 이어 붙이기 때문에, 해커가 입력창에 ' OR 1=1 -- 같은 파괴적인 코드를 넣으면 데이터가 통째로 털립니다. |
| 하드 파싱(Hard Parsing) 오버헤드 | 동적으로 만들어지는 쿼리가 띄어쓰기 1개라도 다르면, DB 옵티마이저는 이를 "태어나서 처음 보는 쿼리"로 인식하여 매번 값비싼 실행 계획을 새로 짜야(하드 파싱) 합니다. 이는 DB CPU를 100%로 치솟게 합니다. |
- 해결책: 동적 SQL을 만들 때 문자열을 직접 더하지 말고, 반드시 바인드 변수(Bind Variable,
?또는:val) 를 사용하여 조립해야 인젝션을 막고 파싱 결과를 재사용할 수 있습니다.
📢 섹션 요약 비유: 정적 SQL이 공장에서 이미 단단하게 굳어져 나온 '기성품 블록'이라면, 동적 SQL은 현장에서 고객이 원하는 모양대로 잘라서 본드로 붙여 만드는 '맞춤형 진흙 블록'입니다. 유연하고 어떤 모양이든 짤 수 있지만, 매번 새로 빚어야 하니 속도가 느리고(하드 파싱), 고객이 이상한 걸 섞어달라면 그대로 폭발(인젝션)할 위험이 큽니다.