쿼리 계획(Query Planning)

쿼리 계획(Query Planning)

SQL의 가장 큰 장점은 선언적(declarative) 언어라는 점이에요. SQL로 프로그래밍할 때는 "무엇을 계산할지"를 알려주지, "어떻게 계산할지"는 알려주지 않아요. 그 어떻게를 알아내는 일은 데이터베이스 엔진 안의 쿼리 플래너(query planner) 하위 시스템이 맡죠. 어떤 SQL 문장이든 수행하는 알고리즘은 수백·수천·수백만 가지가 될 수 있는데, 모두 정답을 주지만 속도는 제각각이에요. 쿼리 플래너가 그중 가장 빠르고 효율적인 알고리즘을 고르는 역할을 해요.

출처: https://www.sqlite.org/queryplanner.html

쿼리 플래너의 역할

쿼리 플래너는 각 SQL 문장에 가장 빠르고 효율적인 알고리즘을 고르려 시도하는 일종의 "지능"이에요. 대부분의 경우 SQLite의 쿼리 플래너는 잘 작동해요. 다만 플래너가 효율적으로 일하려면 인덱스(index) 가 필요해요. 이 인덱스는 보통 프로그래머가 직접 설계해서 추가해야 하죠. 아주 드물게 플래너가 차선의 알고리즘을 고를 때도 있는데, 그럴 때는 프로그래머가 더 나은 인덱스를 만들거나 플래너에 힌트를 줘서 도와줄 수 있어요.

인덱스가 없는 테이블: 전체 스캔

대부분의 SQLite 테이블은 고유한 정수 키(기본적으로 rowid 또는 INTEGER PRIMARY KEY)를 가진 행들로 이뤄져요(WITHOUT ROWID 테이블은 예외). 행들은 rowid 오름차순으로 논리적으로 저장돼요.

인덱스 없이 특정 값을 찾으려면 테이블을 처음부터 끝까지 읽어야 하는데, 이를 전체 테이블 스캔(full table scan) 이라고 해요. 예를 들어 7행짜리 테이블에서는 전체 스캔이 무난하지만, 7백만 행이라면 단일 8바이트 숫자를 찾으려고 수 메가바이트를 읽어야 할 수도 있어요. 그래서 보통 전체 테이블 스캔은 피하려고 해요.

rowid로 찾기

전체 스캔을 피하는 한 방법은 rowid(또는 동등한 INTEGER PRIMARY KEY)로 조회하는 거예요.

SELECT price FROM fruitsforsale WHERE rowid=4;

rowid 조회는 B-트리 탐색을 한 번만 하면 되기 때문에 훨씬 빠르죠.

WHERE 절과 복합 인덱스

WHERE 절에 AND로 연결된 조건이 여러 개라면, 최대 성능을 위해 각 AND 조건의 컬럼을 담은 다중 컬럼 인덱스를 만드는 게 좋아요. 인덱스 끝에 "출력(output)" 컬럼을 추가하면 원래 테이블을 참조하지 않아도 값을 얻을 수 있어서, 쿼리당 이진 탐색 횟수를 절반으로 줄일 수 있어요(인덱스만으로 답하는 커버링 효과죠). 이는 상수 배의 성능 개선(대략 두 배)을 가져와요.

정렬(ORDER BY)과 인덱스

SQLite도 다른 SQL 엔진처럼 인덱스를 정렬에 쓸 수 있어요. 예를 들어 WHERE로 특정 값을 거르고 ORDER BY로 정렬해야 하는 쿼리에서, 정렬에 쓰는 컬럼이 인덱스에 포함돼 있으면 정렬 비용을 크게 줄일 수 있어요. 인덱스를 이용한 부분 정렬은 인덱스만으로 완전히 정렬할 수 없을 때도 항상 시도돼요. LIMIT 절이 있으면 전체 테이블을 스캔하지 않을 수도 있고요.

WITHOUT ROWID 테이블

여기까지의 기본 원리는 일반 rowid 테이블과 WITHOUT ROWID 테이블 모두에 동일하게 적용돼요. 유일한 차이는 테이블의 키로 쓰이는 rowid 컬럼(인덱스에서 맨 오른쪽 항으로 나타나는)이 WITHOUT ROWID에서는 PRIMARY KEY로 대체된다는 점이에요.

더 알아보기