← 학습 카테고리

Learn

SQL

32개 모듈 · 현재 4번째

SQL 모듈 4/32 sql-learn-04

쿼리 최적화와 실전 시나리오

80 SQL Interview Questions for Data Engineers (Real Asks, 2026) — DataVidhya blog Scenario-Based & Optimization, Q31-39 (pp.14-17)

이 모듈을 다 읽으면

  • 느린 쿼리를 실행 계획(Execution Plan)으로 진단하는 절차를 설명할 수 있다
  • N+1 문제가 무엇이며 세트 기반 SQL로 어떻게 해결하는지 설명할 수 있다
  • SCD Type 2를 SQL로 구현하는 표준 패턴(MERGE, 유효기간 컬럼)을 설명할 수 있다
  • TRUNCATE와 DELETE의 차이, 그리고 엔진별 시퀀스 초기화 동작 차이를 구분할 수 있다

느린 쿼리 진단부터 N+1 문제, 2등 급여 찾기 같은 클래식 문제, SCD Type 2 구현, TRUNCATE/DELETE 차이, 저장 프로시저·트리거, 빅데이터 SQL 처리, OLTP까지 실전 시나리오형 질문을 다룬다.

느린 쿼리 진단과 실행 계획

느린 쿼리를 최적화하는 첫 단계는 실행 계획을 확인해 풀 테이블 스캔 같은 병목을 찾는 것이다. 실행 계획은 쿼리 옵티마이저가 쿼리를 처리하기 위해 어떤 단계를 밟을지 보여주는 보고서로, 인덱스 사용 여부, 조인 방식(해시 조인 vs 네스티드 루프 조인), 각 연산의 예상 비용을 담고 있다. `EXPLAIN`(또는 일부 엔진의 `EXPLAIN ANALYZE`)을 쿼리 앞에 붙이면 실제 데이터 대신 이 계획이 반환된다.

흔한 개선책은 누락된 인덱스 추가, 오래된 통계 정보 갱신, `SELECT *` 지양, 문자열 맨 앞에 와일드카드를 쓰는 패턴(`LIKE '%foo'`) 줄이기, 복잡한 로직을 작은 CTE나 임시 테이블로 쪼개기 등이다.

핵심 포인트

  • 실행 계획은 인덱스 사용 여부, 조인 방식, 연산별 예상 비용을 보여주는 옵티마이저의 보고서다.
  • EXPLAIN(또는 EXPLAIN ANALYZE)을 쿼리 앞에 붙이면 실제 데이터 대신 실행 계획이 반환된다.

N+1 문제와 2등 급여 찾기

N+1 문제는 애플리케이션이 ID 목록을 얻기 위해 쿼리 1번을 실행한 뒤, 각 ID의 상세 정보를 얻기 위해 또 N번의 개별 쿼리를 실행하는 안티패턴이다. 데이터 엔지니어링에서는 프로그래밍 언어의 반복문으로 쿼리를 여러 번 날리는 대신, 테이블을 한 번의 SQL 쿼리 안에서 조인하는 세트 기반(set-based) 로직으로 해결한다.

2등 급여를 찾는 고전적인 문제는 서브쿼리로 `SELECT MAX(salary) FROM Employee WHERE salary < (SELECT MAX(salary) FROM Employee)`처럼 풀거나, 급여 내림차순 정렬 후 `LIMIT 1 OFFSET 1`을 쓰는 방법이 있다. 다만 동점 급여가 여러 명일 때 결과가 달라질 수 있으므로, 실무에서는 DENSE_RANK() 윈도우 함수로 '몇 등인지'를 명확히 정의하는 방식이 더 견고하다.

핵심 포인트

  • N+1 문제는 목록 쿼리 1번 + 상세 쿼리 N번의 안티패턴이며, 조인을 이용한 세트 기반 SQL로 해결한다.
  • 2등 급여는 서브쿼리(MAX < MAX) 또는 OFFSET/LIMIT으로 찾을 수 있지만, 동점이 있을 때는 DENSE_RANK가 더 명확하다.

SCD Type 2 구현과 TRUNCATE vs DELETE

SCD Type 2(Slowly Changing Dimension Type 2)는 값이 바뀔 때마다 새 행을 추가해 이력을 보존하는 방식이다. `valid_from`/`valid_to` 타임스탬프나 `is_current` 불리언 플래그를 두어, 특정 시점의 데이터가 어땠는지 '특정 시점 조회(point-in-time lookup)'가 가능하게 한다.

DELETE는 행을 한 줄씩 지우는 DML 명령으로, 각 삭제를 로그에 남겨 롤백이 가능하고 WHERE 절로 조건을 줄 수 있다. TRUNCATE는 테이블의 데이터 페이지 전체를 통째로 할당 해제하는 DDL 명령으로 훨씬 빠르지만 WHERE로 필터링할 수 없다.

— 정정: 원문은 TRUNCATE가 '일반적으로 identity 컬럼을 리셋한다'고 설명하지만 엔진마다 동작이 다르다. SQL Server와 MySQL의 AUTO_INCREMENT는 기본적으로 TRUNCATE 시 시퀀스가 리셋된다. 하지만 PostgreSQL은 기본 동작이 `CONTINUE IDENTITY`이므로, `TRUNCATE TABLE ... RESTART IDENTITY`를 명시적으로 지정하지 않는 한 시퀀스 값이 초기화되지 않는다. 엔진별 방언 차이를 인터뷰에서 언급하면 더 정확한 답이 된다.

핵심 포인트

  • SCD Type 2는 변경마다 새 행을 추가하고 valid_from/valid_to 또는 is_current로 이력을 관리한다.
  • DELETE는 행 단위 DML(롤백 가능, WHERE 가능)이고 TRUNCATE는 테이블 전체를 비우는 DDL(더 빠르지만 WHERE 불가)이다.
  • TRUNCATE가 identity/시퀀스를 리셋하는지는 엔진에 따라 다르다 — PostgreSQL은 RESTART IDENTITY를 명시해야 리셋된다 — 정정.

저장 프로시저·트리거, 빅데이터 SQL

저장 프로시저는 데이터베이스 서버에 컴파일되어 저장된 SQL 문의 묶음으로, 반복문·변수 같은 절차적 로직을 지원하고, 테이블 접근을 추상화해 보안을 높이며, 데이터 근처에서 로직을 실행해 네트워크 트래픽을 줄인다. 트리거는 INSERT/UPDATE/DELETE 같은 특정 이벤트에 자동으로 실행되는 특수한 저장 프로시저로, 감사 로그 기록이나 단순 제약으로는 표현하기 어려운 복잡한 비즈니스 규칙 강제에 쓰인다.

수십억 행 규모의 빅데이터는 표준 관계형 데이터베이스 한 대로는 감당하기 어렵다. 데이터 엔지니어는 Snowflake, BigQuery, Spark SQL 같은 분산 시스템을 쓰는데, 이런 시스템들이 워크로드를 여러 노드에 분산하는 대규모 병렬 처리(MPP)와 더불어, 쿼리에 필요한 컬럼만 읽어 I/O를 줄이는 컬럼 지향 저장 포맷(Parquet, ORC 등)을 활용한다는 점이 핵심이다.

— 정정: 원문은 'SparkSQL이 컬럼 지향 저장을 사용한다'고 단순화했지만, 정확히는 컬럼 지향 저장은 Spark 자체의 속성이 아니라 Spark가 주로 읽고 쓰는 파일 포맷(Parquet, ORC) 의 속성이다. Spark의 실행 엔진(Tungsten)은 내부적으로 메모리상에서 컬럼형 배치(columnar batch)를 활용해 벡터화 연산을 하지만, 이는 저장 계층과는 별개의 실행 계층 최적화다. 즉 '분산 SQL 엔진 + 컬럼 지향 파일 포맷'의 조합이 빅데이터 SQL 처리의 핵심이지, SparkSQL이라는 엔진 자체가 컬럼 지향 저장소인 것은 아니다.

핵심 포인트

  • 저장 프로시저는 절차적 로직·보안·네트워크 트래픽 감소를 위해 서버에 컴파일되어 저장된다.
  • 트리거는 INSERT/UPDATE/DELETE 같은 이벤트에 자동 실행되는 저장 프로시저다.
  • 빅데이터 SQL 처리는 대규모 병렬 처리(MPP)와 컬럼 지향 파일 포맷(Parquet/ORC)의 조합이며, 컬럼 지향성은 Spark 엔진 자체가 아니라 파일 포맷의 속성이다 — 정정.