티스토리 뷰
SQL 튜닝을 하다 보면 IN 조건을 자주 사용한다.
WHERE status IN ('PAID', 'READY', 'DONE')
겉으로 보면 단순히 여러 값을 비교하는 조건처럼 보인다.
하지만 실행계획 관점에서 보면 IN 조건은 생각보다 중요한 차이를 만든다.
특히 Oracle에서는 INLIST ITERATOR라는 실행 방식으로 나타날 수 있고, 이 경우 IN 목록의 각 값을 기준으로 인덱스를 반복 탐색한다. 반대로 어떤 경우에는 IN 조건이 인덱스 탐색 범위를 만드는 데 사용되지 않고, 이미 읽은 데이터에 대해 값이 맞는지 확인하는 filter 조건으로 사용되기도 한다.
이 글에서는 IN 조건이 인덱스에서 어떻게 동작하는지, 그리고 왜 항상 INLIST ITERATOR가 좋은 것은 아닌지 정리한다.
1. IN 조건의 두 가지 사용 방식
IN 조건은 실행계획에서 크게 두 가지 성격으로 사용될 수 있다.
첫 번째는 인덱스 Access 조건으로 사용하는 방식이다.
두 번째는 Filter 조건으로 사용하는 방식이다.
예를 들어 다음 조건이 있다고 하자.
WHERE status IN ('PAID', 'READY', 'DONE')
이 조건이 인덱스에서 읽을 범위를 직접 줄이는 데 사용되면 access 조건이다.
반대로 다른 조건으로 먼저 데이터를 읽은 뒤, 읽어온 데이터의 status 값이 'PAID', 'READY', 'DONE' 중 하나인지 확인하는 데 사용되면 filter 조건이다.
| 구분 | 의미 | 동작 방식 |
|---|---|---|
| Access 조건 | 인덱스에서 읽을 범위를 직접 결정 | 조건에 맞는 인덱스 구간을 찾아감 |
| Filter 조건 | 읽은 데이터가 조건에 맞는지 확인 | 먼저 읽고 나중에 걸러냄 |
| INLIST ITERATOR | IN 값을 여러 개의 = 조건처럼 반복 적용 |
IN 값 개수만큼 인덱스 탐색이 반복될 수 있음 |
일반적으로는 access 조건이 filter 조건보다 좋아 보인다.
왜냐하면 access 조건은 처음부터 읽을 범위를 줄이기 때문이다.
하지만 IN 조건에서는 항상 그렇지 않다.INLIST ITERATOR는 IN 값 개수만큼 인덱스 탐색을 반복할 수 있기 때문이다.
2. INLIST ITERATOR란?
Oracle에서 INLIST ITERATOR는 IN 절의 각 값을 equality 조건처럼 공급하는 방식이다.
예를 들어 다음 조건이 있다고 하자.
WHERE status IN ('PAID', 'READY', 'DONE')
이는 개념적으로 다음과 비슷하게 동작할 수 있다.
WHERE status = 'PAID'
OR
WHERE status = 'READY'
OR
WHERE status = 'DONE'
물론 실제 SQL이 이렇게 변환된다는 뜻은 아니다.
개념적으로 IN 목록의 각 값에 대해 인덱스 range scan이 반복될 수 있다는 의미다.
인덱스가 다음과 같다고 하자.
(status)
쿼리는 다음과 같다.
SELECT *
FROM orders
WHERE status IN ('PAID', 'READY', 'DONE');
INLIST ITERATOR가 사용되면 개념적으로 다음처럼 볼 수 있다.
1. status = 'PAID' 구간 탐색
2. status = 'READY' 구간 탐색
3. status = 'DONE' 구간 탐색
B-Tree 인덱스 관점에서는 각 값에 대해 루트 블록에서 시작해 브랜치 블록을 거쳐 리프 블록의 해당 위치를 찾는 과정이 반복될 수 있다.
IN ('PAID', 'READY', 'DONE')
PAID 탐색: root → branch → leaf
READY 탐색: root → branch → leaf
DONE 탐색: root → branch → leaf
다만 이 말이 매번 물리 I/O가 발생한다는 뜻은 아니다.
루트 블록이나 브랜치 블록은 버퍼 캐시에 올라와 있을 가능성이 높고, 실제 비용은 캐시 상태, leaf block 범위, table access 방식에 따라 달라진다.
중요한 것은 논리적으로 IN 값 개수만큼 탐색 단위가 늘어날 수 있다는 점이다.
3. INLIST ITERATOR가 유리한 경우
INLIST ITERATOR는 IN 값 개수가 적고, 각 값의 선택도가 낮을 때 유리하다.
예를 들어 인덱스가 다음과 같다고 하자.
(status, created_at)
쿼리는 다음과 같다.
SELECT *
FROM orders
WHERE status IN ('FAILED', 'CANCELLED')
AND created_at >= TIMESTAMP '2026-07-01 00:00:00'
AND created_at < TIMESTAMP '2026-08-01 00:00:00';
이 경우 인덱스 선두 컬럼인 status에 IN 조건이 걸려 있다.
DB는 다음과 같은 범위를 탐색할 수 있다.
(status = 'FAILED', created_at 7월 범위)
(status = 'CANCELLED', created_at 7월 범위)
그림으로 보면 다음과 같다.
(status, created_at) 인덱스
CANCELLED 2026-07-01 ~ 2026-07-31 ← 읽음
DONE ...
FAILED 2026-07-01 ~ 2026-07-31 ← 읽음
PAID ...
READY ...
FAILED, CANCELLED 데이터가 전체에서 적은 비율이라면 이 방식은 효율적이다.
필요한 상태값의 구간만 찾아가고, 그 안에서 날짜 범위까지 적용할 수 있기 때문이다.
| 조건 | 판단 |
|---|---|
IN 값 개수가 적음 |
반복 탐색 횟수가 적다 |
각 IN 값의 데이터가 적음 |
선택도가 낮다 |
IN 컬럼이 인덱스 선두에 있음 |
인덱스 탐색 범위를 직접 만들 수 있다 |
| 뒤쪽 컬럼에 추가 범위 조건이 있음 | (IN 값, 범위 조건) 조합으로 더 좁게 읽을 수 있다 |
이런 경우에는 IN 조건이 access 조건으로 사용되는 것이 유리할 가능성이 높다.
4. INLIST ITERATOR가 불리할 수 있는 경우
반대로 IN 값이 많고, 각 값이 많은 데이터를 반환한다면 INLIST ITERATOR가 불리할 수 있다.
예를 들어 인덱스가 다음과 같다고 하자.
(status, created_at)
쿼리는 다음과 같다.
SELECT *
FROM orders
WHERE status IN (
'PAID',
'READY',
'DONE',
'SHIPPING',
'DELIVERED',
'CONFIRMED',
'PACKED',
'PICKED',
'REQUESTED'
)
AND created_at >= TIMESTAMP '2026-07-01 00:00:00'
AND created_at < TIMESTAMP '2026-08-01 00:00:00';
만약 전체 status 종류가 10개인데, 그중 9개를 IN 조건으로 조회한다면 어떻게 될까?
이 조건은 사실상 대부분의 데이터를 포함한다.
전체 status 10개 중 9개 포함
→ status 조건으로 걸러지는 데이터가 거의 없음
→ 선택도가 높음
그런데 INLIST ITERATOR로 실행되면 다음과 같이 여러 번 탐색이 반복될 수 있다.
status = 'PAID' 탐색
status = 'READY' 탐색
status = 'DONE' 탐색
status = 'SHIPPING' 탐색
status = 'DELIVERED' 탐색
...
결국 인덱스 탐색은 여러 번 반복되지만, 읽는 데이터는 전체와 크게 다르지 않을 수 있다.
이 경우 비용 구조는 다음과 같다.
IN 값 개수 × 인덱스 탐색 비용
+ 각 값별 leaf block scan 비용
+ 많은 rowid 기반 table access 비용
이런 상황에서는 IN을 굳이 access 조건으로 쓰는 것이 오히려 비효율적일 수 있다.
5. IN을 Filter 조건으로 쓰는 경우
이번에는 인덱스가 다음과 같다고 하자.
(created_at, status)
쿼리는 같다.
SELECT *
FROM orders
WHERE created_at >= TIMESTAMP '2026-07-01 00:00:00'
AND created_at < TIMESTAMP '2026-08-01 00:00:00'
AND status IN ('PAID', 'READY', 'DONE');
이 인덱스에서는 선두 컬럼이 created_at이다.
따라서 DB는 먼저 created_at의 7월 범위를 읽을 수 있다.
created_at 7월 범위 access
→ 읽은 데이터 중 status IN (...) 조건 확인
즉, status IN (...)은 인덱스에 포함된 컬럼이더라도 인덱스 탐색의 시작점과 끝점을 직접 만드는 핵심 조건이 아닐 수 있다.
대신 created_at 범위로 읽은 인덱스 엔트리 또는 테이블 row에 대해 status 값이 조건에 맞는지 확인한다.
그림으로 보면 다음과 같다.
(created_at, status) 인덱스
2026-06-30 ...
2026-07-01 PAID ← 읽고 통과
2026-07-01 CANCELLED ← 읽고 탈락
2026-07-02 READY ← 읽고 통과
2026-07-02 WAIT ← 읽고 탈락
...
2026-07-31 DONE ← 읽고 통과
2026-08-01 ...
이 방식은 status 값마다 인덱스를 반복 탐색하지 않는다.created_at 범위를 한 번 읽으면서 status 조건을 검사한다.
6. created_at이 선두 컬럼으로 고정된 상태에서 status를 인덱스에 포함할지 판단하기
앞에서는 (created_at, status) 인덱스에서 created_at으로 먼저 범위를 읽고, status IN (...) 조건을 filter로 적용하는 방식을 설명했다.
여기서 한 가지 중요한 실무 판단이 있다.
created_at이 인덱스 선두 컬럼으로 고정되어 있다면,status를 인덱스에 포함하는 것이 의미가 있을까?
결론부터 말하면 의미가 있을 수 있다.
특히 created_at 조건만으로는 읽는 데이터가 많고, status 조건까지 적용했을 때 실제로 테이블에서 가져와야 하는 데이터가 매우 적다면 status를 인덱스에 포함하는 것이 도움이 될 수 있다.
예를 들어 인덱스가 다음과 같다고 하자.
(created_at)
쿼리는 다음과 같다.
SELECT *
FROM orders
WHERE created_at >= TIMESTAMP '2026-07-01 00:00:00'
AND created_at < TIMESTAMP '2026-08-01 00:00:00'
AND status IN ('FAILED', 'CANCELLED');
이 경우 인덱스에는 created_at만 있으므로 DB는 7월 범위의 인덱스 엔트리를 읽은 뒤, 각 rowid를 이용해 테이블에 접근해야 한다.
그리고 테이블에서 status 값을 확인한 뒤 FAILED, CANCELLED가 아닌 데이터는 버린다.
개념적으로는 다음과 같다.
(created_at) 인덱스
1. created_at 7월 범위 scan
2. 7월에 해당하는 rowid를 이용해 테이블 access
3. 테이블에서 status 확인
4. FAILED, CANCELLED만 통과
만약 7월 데이터가 100만 건이고, 그중 FAILED, CANCELLED가 1만 건뿐이라면 어떻게 될까?
created_at 조건으로 100만 건 후보 발생
→ 테이블에 접근해서 status 확인
→ 실제 통과 데이터는 1만 건
이 경우 status를 확인하기 위해 불필요한 테이블 access가 많이 발생할 수 있다.
6.1 status를 인덱스에 포함하면 무엇이 달라질까?
이번에는 인덱스가 다음과 같다고 하자.
(created_at, status)
같은 쿼리를 다시 보자.
SELECT *
FROM orders
WHERE created_at >= TIMESTAMP '2026-07-01 00:00:00'
AND created_at < TIMESTAMP '2026-08-01 00:00:00'
AND status IN ('FAILED', 'CANCELLED');
이 경우에도 선두 컬럼은 created_at이다.
따라서 status가 created_at보다 먼저 탐색 범위를 만드는 것은 아니다.
즉, 동작은 여전히 다음에 가깝다.
created_at 7월 범위 access
→ status IN (...) filter
하지만 중요한 차이가 있다.status가 인덱스에 포함되어 있으므로, DB는 테이블에 접근하기 전에 인덱스 엔트리에서 status 값을 확인할 수 있다.
(created_at, status) 인덱스
1. created_at 7월 범위 scan
2. 인덱스 엔트리에서 status 확인
3. FAILED, CANCELLED인 rowid만 테이블 access
4. 나머지는 테이블에 접근하지 않고 제외
즉, status가 access 조건으로 강하게 사용되지 않더라도, 인덱스 filter 조건으로 사용되면서 테이블 access를 줄일 수 있다.
이 차이를 정리하면 다음과 같다.
| 인덱스 | 동작 | 문제 또는 장점 |
|---|---|---|
(created_at) |
created_at 범위 scan 후 테이블에서 status 확인 |
조건에 맞지 않는 row도 테이블 access가 발생할 수 있음 |
(created_at, status) |
created_at 범위 scan 후 인덱스에서 status 확인 |
조건에 맞는 row만 테이블 access할 수 있음 |
핵심은 status가 인덱스 선두 컬럼이 아니더라도 의미가 없지는 않다는 점이다.
선두 컬럼이 아니기 때문에 INLIST ITERATOR처럼 status 값별로 찾아가는 방식은 아닐 수 있다.
하지만 인덱스에 포함되어 있으면 status 값을 테이블에 가지 않고도 확인할 수 있다.
6.2 언제 status를 인덱스에 포함하는 것이 유리할까?
다음 조건이 함께 만족될수록 (created_at, status) 형태가 유리할 수 있다.
| 조건 | 이유 |
|---|---|
created_at 범위로 읽는 데이터가 많음 |
테이블 access 후보가 많다 |
status IN (...) 조건을 적용하면 남는 데이터가 적음 |
인덱스에서 먼저 걸러낼 가치가 크다 |
| 조회 컬럼 때문에 테이블 access가 필요함 | 모든 row에 대해 테이블에 가는 비용을 줄일 수 있다 |
status 컬럼 크기가 작음 |
인덱스에 추가해도 인덱스 크기 증가 부담이 상대적으로 작다 |
| 해당 쿼리가 자주 실행됨 | 인덱스 유지 비용보다 조회 이득이 클 수 있다 |
예를 들어 다음 상황에서는 status를 인덱스에 포함하는 것을 검토할 수 있다.
created_at 7월 범위: 1,000,000건
status IN ('FAILED', 'CANCELLED'): 10,000건
(created_at) 인덱스만 있으면 100만 건에 대해 테이블에 접근한 뒤 status를 확인할 수 있다.
반면 (created_at, status) 인덱스가 있으면 인덱스 단계에서 status를 확인하고, 통과하는 1만 건에 대해서만 테이블에 접근할 수 있다.
(created_at)
→ 100만 건 rowid로 테이블 access 가능성
→ 테이블에서 status 확인
→ 1만 건 통과
(created_at, status)
→ 100만 건 인덱스 엔트리 scan
→ 인덱스에서 status 확인
→ 1만 건만 테이블 access
따라서 이 경우에는 status가 filter 조건이더라도 인덱스에 포함하는 것이 의미가 있다.
6.3 주의할 점
다만 status를 인덱스에 포함한다고 항상 좋은 것은 아니다.
인덱스에 컬럼을 추가하면 인덱스 크기가 커지고, INSERT/UPDATE/DELETE 시 인덱스 유지 비용도 증가한다.
또한 created_at 범위 자체가 너무 넓다면, 결국 많은 인덱스 leaf block을 읽어야 한다.
즉, (created_at, status) 인덱스는 다음을 기대하는 설계다.
created_at으로 대상 범위를 찾고
→ status를 인덱스에서 먼저 확인해서
→ 불필요한 테이블 access를 줄인다
하지만 다음 상황이라면 효과가 작을 수 있다.
| 상황 | 이유 |
|---|---|
created_at 범위가 매우 넓음 |
인덱스 scan 자체가 커진다 |
status 조건으로 거의 걸러지지 않음 |
테이블 access 감소 효과가 작다 |
| 테이블 access가 원래 적음 | 추가 인덱스 컬럼의 이점이 작다 |
| DML이 매우 많은 테이블 | 인덱스 유지 비용이 부담될 수 있다 |
| 조회 컬럼이 모두 인덱스에 있음 | 이미 covering index처럼 동작할 수 있어 별도 판단 필요 |
따라서 created_at이 선두 컬럼으로 고정된 상태에서 status를 추가할지는 다음 기준으로 판단해야 한다.
status를 인덱스에서 먼저 걸러서 줄어드는 table access 비용
>
status를 인덱스에 추가하면서 늘어나는 index scan/저장/DML 비용
7. 왜 Filter 조건이 더 나을 수 있을까?
일반적으로는 filter보다 access가 좋아 보인다.
하지만 INLIST ITERATOR에서는 IN 값 개수만큼 탐색이 반복될 수 있다.
두 방식을 비교해보자.
방식 A. INLIST ITERATOR
status = 'PAID' 탐색
status = 'READY' 탐색
status = 'DONE' 탐색
...
비용 구조는 대략 다음과 같다.
IN 값 개수 × 인덱스 root/branch/leaf 탐색 비용
+ 각 값별 leaf range scan 비용
+ rowid를 통한 table access 비용
방식 B. 다른 컬럼으로 Access 후 IN Filter
created_at 7월 범위 한 번 scan
→ status IN (...) 조건 검사
비용 구조는 대략 다음과 같다.
created_at 범위 scan 비용
+ status 값 비교 비용
+ 필요한 table access 비용
따라서 다음과 같은 상황에서는 방식 B가 더 유리할 수 있다.
IN 값별 반복 탐색 비용 > 넓은 범위 한 번 scan + filter 비용
즉, access 조건이 항상 filter 조건보다 좋은 것은 아니다.
정확히 말하면 다음과 같다.
좋은 access 조건은 읽는 범위를 충분히 줄여주는 조건이다.
IN조건을 access로 사용하더라도 읽는 범위를 거의 줄이지 못하고 반복 탐색만 늘어난다면, filter 조건으로 사용하는 편이 더 나을 수 있다.
8. 예시로 보는 판단 기준
다음과 같은 데이터 분포가 있다고 하자.
| status | 전체 데이터 비율 |
|---|---|
| PAID | 45% |
| READY | 30% |
| DONE | 20% |
| CANCELLED | 3% |
| FAILED | 2% |
아래 조건은 선택도가 낮다.
WHERE status IN ('CANCELLED', 'FAILED')
전체의 약 5%만 조회한다.
이 경우 (status, created_at) 인덱스를 사용해서 INLIST ITERATOR로 접근하는 것이 유리할 수 있다.
반대로 아래 조건은 선택도가 높다.
WHERE status IN ('PAID', 'READY', 'DONE')
전체의 약 95%를 포함한다.
이 경우 status를 access 조건으로 사용해도 읽는 데이터가 크게 줄어들지 않는다.
만약 날짜 조건이 함께 있고, 날짜 조건이 충분히 강하다면 (created_at, status) 인덱스로 날짜 범위를 먼저 읽고 status는 filter로 처리하는 편이 더 나을 수 있다.
| 조건 | 유리할 수 있는 방식 |
|---|---|
status IN ('CANCELLED', 'FAILED') |
status를 access 조건으로 사용 |
status IN ('PAID', 'READY', 'DONE') |
다른 조건으로 access 후 status는 filter |
IN 값이 적고 선택도가 낮음 |
INLIST ITERATOR 유리 |
IN 값이 많고 선택도가 높음 |
Filter 조건 유리 가능 |
| 날짜 조건이 매우 강함 | (created_at, status) 형태가 유리 가능 |
| 상태 조건이 매우 강함 | (status, created_at) 형태가 유리 가능 |
9. 실행계획에서 확인해야 할 것
튜닝할 때는 실행계획의 operation 이름만 보면 안 된다.
특히 Oracle에서는 Predicate Information에서 해당 조건이 access인지 filter인지 확인해야 한다.
예를 들어 다음처럼 나오면 status가 access 조건으로 사용된 것이다.
access("STATUS"='PAID' OR "STATUS"='READY')
반대로 다음처럼 나오면 created_at이 access 조건이고, status는 filter 조건이다.
access("CREATED_AT">=:FROM_DT AND "CREATED_AT"<:TO_DT)
filter("STATUS"='PAID' OR "STATUS"='READY')
이 차이가 중요하다.
status가 인덱스에 포함되어 있다고 해서 반드시 access 조건으로 사용되는 것은 아니다.
인덱스 컬럼 순서, 조건의 형태, 데이터 분포, 통계 정보에 따라 access로 쓰일 수도 있고 filter로 쓰일 수도 있다.
10. MySQL에서는 어떻게 볼 수 있을까?
MySQL에서는 Oracle처럼 INLIST ITERATOR라는 이름이 실행계획에 직접 나타나지는 않는다.
대신 MySQL은 IN() 조건을 여러 개의 equality range로 보고 range optimization 대상으로 처리할 수 있다.
즉, 개념적으로는 다음과 같은 여러 index interval을 읽는 방식이다.
WHERE status IN ('PAID', 'READY', 'DONE')
status = 'PAID' interval
status = 'READY' interval
status = 'DONE' interval
따라서 MySQL에서도 핵심 판단은 비슷하다.
IN 값이 적고 선택도가 낮으면 range access로 유리할 수 있음
IN 값이 많고 선택도가 높으면 반복 range 탐색의 이점이 줄어듦
다만 DBMS마다 실행계획 표기 방식과 세부 최적화 방식은 다르므로, 실제 판단은 각 DBMS의 실행계획과 실제 수행 통계를 기준으로 해야 한다.
11. 튜닝 관점에서의 정리
IN 조건을 튜닝할 때는 다음 순서로 판단하는 것이 좋다.
11.1 IN 값 개수를 본다
IN 값이 적으면 반복 탐색 비용이 크지 않다.
하지만 값이 많아질수록 반복 탐색 비용이 커질 수 있다.
| IN 값 개수 | 판단 |
|---|---|
| 적음 | access 조건으로 유리할 가능성이 높음 |
| 많음 | 반복 탐색 비용 확인 필요 |
| 매우 많음 | parse 비용, plan 안정성, 임시 테이블 조인 방식도 검토 필요 |
| 전체 도메인 대부분 | 조건으로서의 효과가 낮을 수 있음 |
11.2 각 값의 선택도를 본다
IN 값 개수보다 더 중요한 것은 선택도다.
WHERE status IN ('FAILED', 'CANCELLED')
위 조건처럼 값은 2개뿐이고 전체 데이터의 5%만 포함한다면 좋은 조건이다.
반대로 다음 조건은 값이 3개뿐이어도 좋지 않을 수 있다.
WHERE status IN ('PAID', 'READY', 'DONE')
이 세 값이 전체 데이터의 95%를 차지한다면 선택도가 높다.
즉, IN 값 개수만 보면 안 된다.
각 값이 얼마나 많은 데이터를 포함하는지도 함께 봐야 한다.
11.3 다른 조건이 더 강한지 본다
다음 쿼리를 보자.
WHERE created_at >= TIMESTAMP '2026-07-09 00:00:00'
AND created_at < TIMESTAMP '2026-07-10 00:00:00'
AND status IN ('PAID', 'READY', 'DONE')
여기서 created_at 하루 조건이 매우 강하다면, 날짜 범위를 먼저 읽고 status를 filter하는 방식이 더 나을 수 있다.
반대로 다음 쿼리는 다를 수 있다.
WHERE created_at >= TIMESTAMP '2026-01-01 00:00:00'
AND created_at < TIMESTAMP '2027-01-01 00:00:00'
AND status IN ('FAILED')
기간은 1년으로 넓고, status = 'FAILED'가 매우 적다면 status를 먼저 access 조건으로 사용하는 편이 유리할 수 있다.
12. 최종 요약
| 구분 | INLIST ITERATOR / Access | IN Filter |
|---|---|---|
| 동작 | IN 값별로 인덱스 탐색 반복 |
다른 조건으로 읽은 뒤 값 검사 |
| 비용 구조 | IN 개수 × index range scan |
1차 access 범위 scan + 값 비교 |
| 인덱스 예 | (status, created_at) |
(created_at, status) |
| 장점 | 읽는 범위를 직접 줄임 | 반복 탐색을 피할 수 있음 |
| 추가 장점 | 특정 status 구간만 찾아갈 수 있음 | status가 인덱스에 있으면 테이블 access 전에 걸러낼 수 있음 |
| 단점 | 값이 많으면 반복 탐색 비용 증가 | 먼저 읽는 범위가 넓으면 인덱스 scan 자체는 커질 수 있음 |
| 확인 포인트 | Predicate Information의 access |
Predicate Information의 filter |
정리하면 다음과 같다.
IN 값이 적고 잘 걸러진다
→ INLIST ITERATOR / access 조건이 유리할 가능성이 높다
IN 값이 많고 대부분의 데이터를 포함한다
→ 반복 탐색 비용이 커질 수 있다
→ 다른 조건으로 access 후 IN은 filter가 나을 수 있다
복합 인덱스 설계에서는
→ IN 컬럼을 앞에 둘지
→ 다른 조건 컬럼을 앞에 두고 IN을 filter로 둘지
→ filter로 쓰더라도 인덱스에 포함해 table access를 줄일 수 있는지
→ 데이터 분포와 실행계획을 기준으로 판단해야 한다
가장 중요한 기준은 이것이다.
IN을 access 조건으로 쓸 것인지는 문법적으로 가능한지가 아니라, 반복 탐색 비용보다 줄어드는 I/O가 더 큰지로 판단해야 한다.
'Database' 카테고리의 다른 글
| Redis가 정말 싱글 쓰레드로 작동할까? (0) | 2026.06.07 |
|---|---|
| 인덱스 풀스캔(Index Full Scan) 훑어보기 (0) | 2026.04.18 |
| count(*) / count(1) / count(컬럼) 차이 (0) | 2022.04.15 |