테마
쿼리 실행과 풀 스캔
옵티마이저(optimizer)는 같은 결과를 내는 여러 실행 방법 중 비용이 가장 싸다고 계산된 계획을 고른다. 모든 행을 읽는 풀 스캔은 그 자체로 나쁜 게 아니라, 다른 방법과 비용을 비교하는 출발점이다.
쿼리 실행 절차
다이어그램 로딩…
- 파서(parser): SQL 문장의 문법을 검사하고, 서버가 다룰 수 있는 트리 구조인 파스 트리(parse tree)로 바꾼다.
- 전처리: 사용자에게 그 테이블을 쓸 권한(privilege)이 있는지 확인한다. 뷰(view)와 서브쿼리(subquery)를 펼쳐 풀어 쓰고, 실행 전에 답이 정해지는 상수 조건을 정리한다.
- 옵티마이저: 조인 순서·사용할 인덱스·스캔 방식을 비용으로 비교해 고른다.
- 실행: 실행 엔진이 스토리지 엔진(storage engine)의 핸들러 API(handler API)를 호출해 행을 읽고, 읽은 행을 조합해 결과를 만든다.
규칙 기반 vs 비용 기반
- RBO(Rule-Based Optimizer, 규칙 기반 옵티마이저): 데이터 분포를 보지 않고 정해진 우선순위(인덱스가 있으면 풀 스캔보다 인덱스 등)로 고른다. 옛 방식이다.
- CBO(Cost-Based Optimizer, 비용 기반 옵티마이저): 통계 정보로 I/O(Input/Output, 입출력)와 CPU 비용을 어림해 가장 싼 것을 고른다. 통계에는 행 수, 카디널리티(cardinality), 페이지(page) 수 등이 들어간다. MySQL이 쓰는 방식이다.
CBO는 통계로 비용을 추정하므로 통계가 틀리면 계획이 틀어진다. 그래서 통계를 다시 수집하는 ANALYZE TABLE이 튜닝의 첫 단추다(통계 정보와 코스트 모델 참고).
풀 테이블 스캔과 풀 인덱스 스캔
- 풀 테이블 스캔(full table scan): 테이블의 모든 행을 처음부터 끝까지 읽는다. InnoDB 테이블은 행 자체가 기본 키 순서로 저장된 클러스터링 인덱스(clustered index)이므로, 이 인덱스를 처음부터 끝까지 훑는 것과 같다. 순서대로 읽기 때문에 다음 페이지를 미리 올려 두는 미리 읽기(read-ahead)의 도움을 받을 수 있다. 그래서 읽을 행의 비율이 높으면, 세컨더리 인덱스(secondary index)로 찾은 뒤 행마다 클러스터링 인덱스를 다시 찾아가는 반복 탐색보다 싸다.
- 풀 인덱스 스캔(full index scan): 인덱스 하나를 처음부터 끝까지 순서대로 읽는다. 쿼리에 필요한 칼럼이 모두 그 인덱스에 들어 있는 커버링 인덱스(covering index)라면 테이블 행을 따로 읽지 않고 끝나므로 테이블 스캔보다 가볍다.
"풀 스캔은 무조건 나쁘다"가 아니다. 옵티마이저가 계산해서 싼 쪽을 고른 결과면 정당하다. 문제는 통계가 낡아 잘못 고르는 경우와 원래 써야 할 인덱스를 못 쓰는 쿼리다. 인덱스 칼럼을 함수로 가공한 조건, 칼럼과 비교 값의 타입이 달라 변환이 일어나는 조건, LIKE '%abc'처럼 앞에 와일드카드가 붙은 조건이 대표적이다.
병렬 처리
InnoDB는 CHECK TABLE(테이블 오류 검사)과 일부 병렬 인덱스 읽기 작업에서 innodb_parallel_read_threads(병렬 읽기에 쓸 스레드 수 설정)를 사용한다. 이것을 범용 병렬 쿼리 기능으로 이해하면 안 된다. OLTP(Online Transaction Processing, 온라인 트랜잭션 처리)의 일반 SELECT 하나를 여러 작업자가 나눠 실행해 주지는 않는다. OLTP는 주문·결제처럼 짧은 트랜잭션(transaction)이 아주 많이 동시에 들어오는 업무 형태다. 무거운 분석은 읽기 전용 레플리카(replica)로 보내거나 분석 엔진을 분리하는 방안을 검토한다.
면접 포인트
- "옵티마이저가 뭘 기준으로 고르나요?" → 비용 기반 + 통계. ANALYZE 연결.
- "풀 스캔이 무조건 나쁜가요?" → 아니다. 읽기 비율이 높으면 정당. 잘못 고를 때가 문제.
- "인덱스가 있는데 풀 스캔을 타요" → 통계·함수 가공·타입·선택도(selectivity) 순서로 의심.
