Skip to Content

12장. 일부러 안 타는 경우 — 선택도와 랜덤 접근 비용

{/* 삽화 자리 — 지름길 표지판 앞에서 걸음을 멈추고 큰길로 도로 나서는 사람. 지름길은 멀쩡히 열려 있다 */}

옵티마이저가 인덱스를 버리는 지점을 재고, 그 판단을 FORCE INDEX로 강제로 뒤집어 실제로 어느 쪽이 빨랐는지 확인했습니다. 13.1%에서 버렸는데 실제로 풀스캔이 이기기 시작한 건 26~34% 구간이었어요. 그 사이에서 옵티마이저는 2.3배 손해를 보고 있었습니다.

면접 실전 질문: ① 인덱스가 있는데 왜 풀스캔을 고르나요? ② 선택도가 몇 %면 인덱스가 손해인가요? ③ FORCE INDEX를 써도 되나요?


배경 — 이번엔 아무도 잘못하지 않았습니다

3부를 여기까지 오면서 인덱스가 안 타는 이유를 둘 봤습니다.

10장은 옵티마이저가 볼 게 없어서 틀렸습니다. 통계가 없으니 filtered=10 같은 상수를 꽂았죠. 11장은 우리가 못 보게 만들어서 안 탔습니다. 컬럼에 함수를 씌우면 정렬을 쓸 수 없으니까요.

이 장은 둘 중 어느 쪽도 아닙니다. 인덱스도 멀쩡하고, 통계도 있고, 쿼리도 정상이에요. 그런데도 옵티마이저가 안 쓰기로 합니다. 그리고 그건 대체로 옳은 판단입니다.

4장에서 이유는 이미 봤습니다. 세컨더리 인덱스로 찾은 행마다 클러스터드 인덱스로 왕복해야 하고(7장), 그 왕복이 많아지면 차라리 테이블을 처음부터 끝까지 훑는 게 쌉니다.

남은 질문은 하나입니다. 그 판단이 정말 옳았나요?

4장은 옵티마이저가 마음을 바꾸는 지점을 쟀을 뿐, 그 지점이 맞는 지점인지는 안 쟀습니다. 이 장이 그걸 잽니다.

먼저 말이 헷갈리지 않게 둘로 갈라두겠습니다.

  • 버리는 지점 — 옵티마이저가 range에서 ALL로 마음을 바꾸는 곳. 4장이 잰 게 이겁니다.
  • 실제 교차점 — 풀스캔이 실제로 더 빨라지기 시작하는 곳. 이 장이 재려는 게 이겁니다.

둘이 같으리라는 보장은 어디에도 없습니다.

(4장은 scan_demo 200만 행에 SELECT *였고, 이 장은 7·8장에서 만든 rt_random 50만 행에 COUNT(body)입니다. 테이블도 쿼리도 다르니 4장의 13.7%와 이 장의 13.1%가 비슷한 건 우연이에요. 같은 측정이 아닙니다.)

스토리 — 판단을 강제로 뒤집어보기

방법은 단순합니다. 같은 쿼리를 세 가지로 재는 거예요.

-- 자율: 옵티마이저가 알아서 고른다 SELECT COUNT(body) FROM rt_random WHERE txno BETWEEN LPAD(1,20,'0') AND LPAD(100000,20,'0'); -- 인덱스 강제 / 풀스캔 강제 SELECT COUNT(body) FROM rt_random FORCE INDEX (idx_txno) WHERE ...; SELECT COUNT(body) FROM rt_random IGNORE INDEX (idx_txno) WHERE ...;

옵티마이저가 고른 쪽과 버린 쪽을 나란히 재면 됩니다. 버린 쪽이 더 빨랐다면 그 판단은 틀린 겁니다.

COUNT(body)로 잰 건 의도적입니다 — body는 인덱스에 없으니 행마다 클러스터드 인덱스로 왕복해야 합니다. 11장처럼 커버링으로 빠져나가면 이 장이 재려는 왕복 비용이 안 잡히니까요.

그리고 이 장의 모든 비율은 행 비율입니다. 조건에 맞는 행 ÷ 전체 행이요. txno 값 범위의 비율이 아닙니다 — 두 값은 크게 달라요. (txnoid에서 왔는데 그 idauto_increment 구멍이 있습니다. 그래서 값 범위를 두 배로 넓혀도 행은 두 배가 안 돼요. 표의 행 수가 16,384·32,768·65,536처럼 2의 거듭제곱으로 떨어지는 것도 그 때문입니다.)

먼저 옵티마이저가 무엇을 보고 결정하는지 optimizer_trace로 꺼내봤습니다.

실제 행행 비율추정 행배율인덱스 범위 비용풀스캔 비용트레이스 판정
42,2488.4%98,4002.33배34,440.350,764.2chosen: true
65,53613.1%151,4022.31배52,991.050,764.2chosen: false, cause: "cost"

✅ 실측 (MySQL 8.0.46 / Docker / Apple M1 / macOS, 2026-08. 재현: ./ch12/verdict.sh)

풀스캔 비용은 고정입니다. 테이블 전체를 훑는 값이니 범위가 얼마든 50,764.2예요. 인덱스 범위 비용만 범위가 넓어질수록 올라갑니다. 그러다 어느 순간 넘어서고, 그때 옵티마이저는 인덱스를 놓습니다.

두 값의 차이가 **4.4%**입니다. 52,991.0 대 50,764.2요. 이 얇은 차이가 실행계획을 통째로 갈아치웁니다.

그럼 그 4.4%가 현실에서도 4.4%였을까요.

핵심 — 13.1%에서 버렸는데 실제 교차점은 26~34%였습니다

범위를 넓혀가며 셋을 다 재봤습니다.

실제 행행 비율추정 행배율범위 비용풀스캔 비용자율인덱스 강제풀스캔 강제실제 승자옳았나
16,3843.3%35,1422.14배12,300.050,764.2range20.6 ms177 ms인덱스
32,7686.6%71,9442.20배25,180.750,764.2range44.2 ms183 ms인덱스
42,2488.4%98,4002.33배34,440.350,764.2range55.4 ms180 ms인덱스
65,53613.1%151,4022.31배52,991.050,764.2ALL79.8 ms180 ms인덱스아니오
65,53613.1%151,4022.31배52,991.050,764.2ALL83.8 ms179 ms인덱스아니오
84,48116.9%199,6602.36배69,881.350,764.2ALL107 ms183 ms인덱스아니오
131,07226.2%247,6011.89배86,660.650,764.2ALL165 ms181 ms인덱스아니오
168,94733.8%247,6011.47배86,660.650,764.2ALL219 ms197 ms풀스캔

✅ 실측 (같은 환경. rt_random 50만 행, 108MB, 버퍼풀 1GB로 전부 상주. 시간은 워밍업 뒤 5회 중앙값. 재현: ./ch12/verdict.sh. 전체 소스: docs/book/code/db-index/ch12/verdict.sh)

비용은 4.4% 차이였는데 실제 시간은 126% 차이였습니다. 79.8ms 대 180ms요. 4.4%를 보고 갈아탄 결과가 2.3배입니다.

(13.1% 줄이 둘인 건 오타가 아닙니다. 스크립트가 도는 여덟 구간 중 두 개가 같은 65,536행으로 떨어져요 — 위에 적은 auto_increment 구멍 때문입니다. 지우지 않고 둔 건 같은 조건을 두 번 잰 값이 79.8과 83.8로 얼마나 흔들리는지 보이려고요.)

옵티마이저는 13.1%에서 인덱스를 버렸는데, 풀스캔이 실제로 이긴 건 33.8%였습니다. 26.2%에서는 아직 인덱스가 이겼고요(165 대 181). 그러니 실제 교차점은 26%와 34% 사이 어딘가이고, 이 실행에서는 33.8%였습니다. 소수점까지 단정할 값이 아니에요 — 풀스캔 시간이 177~197ms로 흔들리는데 판정 폭도 그 정도라서요.

그래도 결론은 흔들리지 않습니다. 13.1%부터 26.2%까지는 어느 쪽으로 재도 인덱스가 이겼고, 옵티마이저는 그 구간 내내 풀스캔을 골랐습니다.

왜 일찍 포기했나 — 범인은 비용 모델이 아니라 추정입니다

표의 배율 열을 보세요. 옵티마이저가 인덱스를 잘 쓰고 있던 구간에서도 추정은 계속 어긋나 있습니다. 2.14배, 2.20배, 2.33배 — 일관되게 2배 넘게 많이 봅니다. 그리고 마음을 바꾸는 그 지점에서도 2.31배였어요. 65,536행짜리 범위를 151,402행으로 본 겁니다.

여기서 계산을 해보려면 “비용이 행 수에 비례하는가”부터 확인해야 합니다. 표에 답이 있어요.

12,300.0 ÷ 35,142 = 0.3500 34,440.3 ÷ 98,400 = 0.3500 25,180.7 ÷ 71,944 = 0.3500 52,991.0 ÷ 151,402 = 0.3500 69,881.3 ÷ 199,660 = 0.3500 86,660.6 ÷ 247,601 = 0.3500

여섯 지점 전부 행당 0.3500입니다. 가정이 아니라 관측이에요. 그러면 정확한 행 수였을 때의 비용이 바로 나옵니다.

65,536 × 0.35 = 22,938

풀스캔의 50,764.2보다 한참 쌉니다. 옵티마이저는 인덱스를 놓지 않았을 거예요.

즉 비용 모델이 틀린 게 아니라 입력이 틀렸습니다. 모델은 받은 숫자로 옳게 계산했는데, 받은 숫자가 2.3배 부풀어 있었어요. 4장이 “추정이 몇 배 틀려야 옵티마이저가 잘못된 경로를 고를까요?”라고 물었는데, 이 테이블에서의 답은 2.3배입니다.

페이지 값을 내려봤지만 소수점까지 그대로였습니다

처음에 제가 확인하려던 건 다른 쪽이었습니다. 4장에서 풀스캔 비용을 row_evaluate_cost 0.1과 memory_block_read_cost 0.25로 분해해 맞췄죠. 그렇다면 디스크 상수io_block_read_cost(기본 1.0)는 데이터가 메모리에 있을 때 거의 청구되지 않아야 합니다. 인덱스 경로에서도 그런지 보려고 값을 4분의 1로 내려봤습니다.

실제 행행 비율io=1.0범위 비용io=0.25범위 비용
32,7686.6%range25,180.7range25,180.7
65,53613.1%ALL52,991.0ALL52,991.0
84,48116.9%ALL69,881.3ALL69,881.3
131,07226.2%ALL86,660.6ALL86,660.6
168,94733.8%ALL86,660.6ALL86,660.6

✅ 실측 (같은 환경. 재현: ./ch12/costconst.sh)

소수점 한 자리까지 똑같습니다. 디스크 상수를 4분의 1로 깎았는데 비용이 1원도 안 움직였어요.

📄 문서 기반 (미검증) — MySQL 8.0은 페이지 읽기 비용을 낼 때 InnoDB가 알려주는 “버퍼풀에 있을 확률”로 두 상수를 가중 평균합니다. 이 테이블은 전부 메모리에 있다고 추정되니 디스크 상수의 가중치가 0에 가깝고, 그래서 아무 일도 안 일어난 겁니다.

그러니까 이건 4장과 같은 결과입니다. 인덱스 경로도 메모리 상수로 청구되고 있었어요. 그리고 4장에서 비용의 96%가 페이지 값이 아니라 행 값이었으니, 페이지 상수를 만져봐야 나머지 4%를 만지는 셈입니다.

여는 글에서 미리 밝혀둔 게 이거였습니다. 페이지는 읽을 양의 단위지 시간의 단위가 아니라고요.

10장의 처방도 안 들었습니다

입력이 틀린 게 원인이라면, 10장에서 배운 도구로 고칠 수 있어야 합니다. 셋 다 써봤어요.

시도type추정 행배율
손대기 전ALL151,4022.31배
ANALYZE TABLEALL151,4022.31배
STATS_SAMPLE_PAGES=200 (기본 20)ALL151,4022.31배
ANALYZE TABLE ... UPDATE HISTOGRAM ON txno WITH 128 BUCKETSALL151,4022.31배

✅ 실측 (같은 환경. 조건에 맞는 실제 행 65,536. 재현: ./ch12/stats.sh)

전부 안 통했습니다. 통계를 새로 뜨든, 샘플을 10배로 늘리든, 히스토그램을 128버킷으로 만들든 추정은 151,402에서 1행도 안 움직여요.

📄 문서 기반 (미검증) — 히스토그램이 안 듣는 건 그럴 만합니다. MySQL은 인덱스가 있는 컬럼의 범위 추정에 히스토그램 대신 인덱스 다이브를 씁니다. 실제 인덱스를 찔러 세는 방식이라 원래 더 정확해야 하죠.

그런데 여기선 그게 2.31배 부풀었습니다. 왜 부풀었는지는 못 밝혔습니다. txno는 50만 행에 고유값이 50만 개로 중복이 하나도 없는데도 그래요.

10장에서 “히스토그램은 추정을 고치지 실행계획을 고치지 않는다”고 했는데, 여기선 추정조차 못 고쳤습니다.

같은 크기인데 판단이 갈립니다

같은 실험을 rt_ordered로도 돌렸습니다. 행 수도 컬럼도 같고, 차이는 하나예요 — 세컨더리 키 순서가 PK 순서와 맞물리는가.

rt_ordered : txno 순서로 읽으면 id 가 1,2,3,4,5,6,7,8,9,10,11,12 rt_random : txno 순서로 읽으면 id 가 168115,39796,193981,350739,174567,...
테이블행 비율추정 배율자율인덱스 강제풀스캔 강제옳았나
rt_ordered13.1%2.01배range56.3 ms186 ms
rt_ordered16.9%2.06배ALL73.8 ms187 ms아니오
rt_ordered33.8%1.47배ALL146 ms191 ms아니오
rt_random13.1%2.31배ALL84 ms188 ms아니오
rt_random16.9%2.36배ALL106 ms187 ms아니오
rt_random33.8%1.47배ALL211 ms193 ms

✅ 실측 (같은 환경. 재현: ./ch12/correlation.sh)

흩어진 쪽이 더 일찍 놓습니다. rt_ordered는 13.1%까지 붙들고 rt_random은 거기서 이미 놓았어요. 그리고 rt_ordered는 33.8%에서도 인덱스가 이깁니다(146 대 191) — 실제 교차점이 더 뒤에 있다는 뜻이죠.

방향은 맞습니다. 왕복이 여기저기로 튀면 실제로 더 비싸니까요(7·8장). 그런데 옵티마이저가 “배치”를 알아서 그런 건 아닙니다. InnoDB의 통계를 꺼내보면 이렇습니다.

테이블n_rowsidx_txno 리프 페이지idx_txno 전체 페이지
rt_ordered495,2031,0631,125
rt_random495,2031,6541,959

✅ 실측 (mysql.innodb_index_stats / innodb_table_stats)

같은 50만 행인데 인덱스가 1.74배 큽니다. 난수 순서로 넣은 쪽이 페이지 분할을 겪어 더 부풀었어요(그게 왜 그런지는 13·14장입니다).

그러니까 옵티마이저가 본 건 순서가 아니라 페이지 수입니다. 이 책이 계속 세온 그 단위요. 추정 배율도 2.01배 대 2.31배로 달랐고요. 둘 다 rt_random을 더 비싸 보이게 만듭니다.

다만 두 테이블 다, 놓는 시점에서는 여전히 인덱스가 2배 이상 빨랐습니다.

반대로 틀리기도 합니다

지금까지는 “너무 일찍 포기한다”였습니다. 10장의 skew 테이블에서는 정반대가 나왔어요. grade 값이 심하게 치우친 50만 행 테이블입니다.

조건맞는 행비율자율인덱스 강제풀스캔 강제옳았나
grade='C'4,9961.0%ref1.15 ms77.5 ms
grade='B'44,9759.0%ref10.5 ms73.7 ms
grade='A'450,02990.0%ref103 ms94.1 ms아니오
grade<>'C'495,00499.0%range119 ms103 ms아니오

✅ 실측 (같은 환경. 재현: ./ch12/selectivity.sh)

먼저 밝힐 게 있습니다. 이건 SELECT COUNT(*)grade 하나만 있으면 답이 나오는 커버링 조회입니다. 왕복이 없으니 인덱스 경로가 앞의 실험보다 훨씬 싸요. 그래서 90%를 긁어와도 손해가 10~16%에 그칩니다.

그래도 방향은 분명히 반대입니다. 90%와 99%에서도 인덱스를 놓지 않아요.

왜 반대인지도 10장에 답이 있습니다. 거기서 grade='A'의 추정은 248,675였는데 실제는 450,029였어요. 0.55배 — 과소 추정입니다.

세 사례를 나란히 놓으면 그림이 하나로 모입니다.

사례실제 행추정 행배율결과
rt_random 13.1%65,536151,4022.31배 과대너무 일찍 놓는다
skew grade='A'450,029248,6750.55배 과소너무 늦게까지 붙든다
scan_demo <>1,999,9991,575,8410.79배 과소안 좁혀지는데 붙든다

옵티마이저가 한 방향으로 보수적인 게 아닙니다. 틀린 추정의 부호가 반대일 뿐이에요. 과대 추정하면 일찍 놓고, 과소 추정하면 늦게까지 붙듭니다. 판단은 두 경우 다 받은 숫자에 충실했고요.

그러니 “몇 % 넘으면 인덱스가 손해”라는 고정 숫자는 없습니다. 이 장에서만 실제 교차점이 26~34%(rt_random)와 34% 초과(rt_ordered)와 90%(skew)로 갈렸어요. 선택도가 무의미하다는 건 아닙니다 — 1%에서 인덱스가 67배 빠른 건 확실하니까요. 다만 경계는 데이터마다 다릅니다.

11장이 남긴 질문 — 못 타는 것과 안 좁혀지는 것

11장 마지막 질문이 transaction_no <> '...'였습니다. 200만 행 테이블에서 재보면 이렇습니다.

type=range key=idx_txno rows=1,575,841 Extra=Using where; Using index

인덱스를 탑니다. typerange이고 key도 찍혀 있어요. 11장의 따옴표 누락과는 다릅니다 — 그건 index(전체 훑기)였죠. FORCE INDEX를 붙여도 같은 계획입니다.

다른 건 좁혀지는 정도입니다. <>는 “한 값을 뺀 나머지 전부”라서, 인덱스를 정상적으로 타고도 데이터의 거의 전부가 범위 안에 들어옵니다. 실제로는 1,999,999행 — 200만 중 딱 한 행만 빠졌어요.

그리고 여기서도 추정이 어긋납니다. 1,575,841은 EXPLAINrows, 즉 추정치예요. 실제보다 21% 모자랍니다. 방금 “추정이 2.3배 부풀었다”고 쓴 장에서 이번엔 모자란 겁니다 — 위 표의 셋째 줄이 그거고요.

11장은 인덱스를 쓸 수 없게 만드는 문제였고, 이 장은 인덱스를 써도 안 좁혀지는 문제입니다. 고치는 곳도 다릅니다. 앞은 쿼리를 고치면 되고, 뒤는 조건 자체가 데이터의 전부를 원하니 고칠 게 없어요.

그래서 FORCE INDEX를 쓰라는 건가요

아닙니다. 이 장은 FORCE INDEX측정 도구로 썼습니다.

운영 쿼리에 박아두는 건 다른 얘기예요. 강제는 오늘의 데이터에 대한 판단을 코드에 고정하는 일입니다. 데이터가 커지고 분포가 변하면 그 판단은 언젠가 틀리는데, 옵티마이저와 달리 힌트는 스스로 마음을 바꾸지 않습니다. 지금 2.3배 이득인 힌트가 1년 뒤 2.3배 손해로 남을 수 있어요.

이 장에서 가져갈 건 힌트가 아니라 절차입니다.

하는 일어떻게
이게 일어나는지 찾는다슬로우 쿼리 로그의 Rows_examined ÷ Rows_sent, 또는 performance_schema.events_statements_summary_by_digestSUM_ROWS_EXAMINED. 반환 행에 비해 훑은 행이 크면 후보다
추정이 얼마나 어긋났는지 본다EXPLAINrows ÷ 실제 COUNT(*). 2배 넘게 벌어지면 의심한다. ALL이면 optimizer_tracerange_scan_alternatives에서 버려진 후보의 rows를 본다
버린 쪽을 실제로 재본다FORCE INDEX / IGNORE INDEX + EXPLAIN ANALYZE로 양쪽. 한 번 말고 여러 번
통계로 고쳐본다ANALYZE TABLESTATS_SAMPLE_PAGES → 히스토그램 순. 안 들을 수도 있다
그래도 차이가 크면힌트 대신 인덱스 설계를 본다. 커버링으로 왕복을 없애면(16장) 판이 바뀐다

정말 박아야 한다면 FORCE INDEX보다 옵티마이저 힌트(/*+ INDEX(t idx) */)를 쓰고, 재검토 날짜를 주석에 남기세요. 힌트는 스스로 마음을 안 바꾸니 사람이 대신 바꿔줘야 합니다.

마지막 줄이 4부로 넘어가는 다리입니다. 이 장에서 인덱스 경로가 비쌌던 이유는 행마다 클러스터드 인덱스로 왕복해야 해서였습니다. 왕복이 없으면 이 비교 자체가 달라져요 — skew 표가 이미 그 맛보기였고요.

정리

  • 옵티마이저가 인덱스를 안 쓰는 건 고장이 아니라 비용 비교의 결과입니다. 풀스캔 비용은 고정이고 인덱스 범위 비용만 자라서, 어느 순간 넘어섭니다.
  • 실측: 두 비용이 50,764.2 대 52,991.0 — 4.4% 차이로 실행계획이 통째로 바뀝니다. 그런데 실제 시간은 126% 차이였어요(79.8ms 대 180ms).
  • 그 판단이 항상 옳지는 않습니다. rt_random에서 13.1%에 인덱스를 버렸는데 실제 교차점은 26~34% 구간이었고, 그 사이에서 2.3배 손해였습니다. 서로 다른 일곱 지점 중 셋이 틀렸어요. “대체로 옳다”는 맞지만 그 “대체로”가 생각보다 좁습니다.
  • 원인은 비용 모델이 아니라 입력이었습니다. 범위 행 수를 2.1~2.4배 많게 봤고, 비용이 행당 정확히 0.3500으로 선형이니 정확한 값이었다면 22,938 — 풀스캔의 50,764.2보다 한참 쌌습니다. 4장이 물은 “몇 배 틀려야 경로가 뒤집히나”의 답이 2.3배입니다.
  • io_block_read_cost를 1.0에서 0.25로 내려도 비용이 소수점까지 그대로였습니다. 인덱스 경로도 4장처럼 메모리 상수로 청구되고 있었어요.
  • ANALYZE TABLE·STATS_SAMPLE_PAGES=200·히스토그램 128버킷 — 셋 다 추정을 1행도 못 움직였습니다. 왜 2.31배 부풀었는지는 못 밝혔습니다.
  • 틀릴 때는 양쪽으로 틀립니다. 과대 추정(rt_random 2.31배)이면 일찍 놓고, 과소 추정(skew 0.55배, <> 0.79배)이면 늦게까지 붙듭니다. 한 방향으로 보수적인 게 아니라 틀린 추정의 부호가 반대입니다.
  • 같은 50만 행인데 rt_random이 더 일찍 놓은 건 옵티마이저가 배치를 알아서가 아니라 인덱스가 1.74배 커서입니다(1,959 대 1,125페이지).
  • “몇 % 넘으면 인덱스가 손해”라는 고정 숫자는 없습니다. 이 장에서만 26~34%, 34% 초과, 90%로 갈렸어요.
  • <>는 인덱스를 못 타는 게 아니라 안 좁혀지는 것입니다. type=range인데 실제 1,999,999행이 범위에 들어옵니다.
  • FORCE INDEX는 측정 도구로 쓰세요. 운영에 박으면 오늘의 판단이 코드에 고정됩니다.

생각해볼 질문

  1. 인덱스 경로가 비쌌던 건 행마다 클러스터드 인덱스로 왕복했기 때문입니다. skew 표는 커버링이라 90%까지도 인덱스가 버텼고요. 그럼 왕복이 아예 없다면 이 장의 교차점은 어디로 갈까요? (16장)
  2. 이 장의 측정은 전부 메모리에 올라온 108MB 테이블입니다. 데이터가 메모리보다 훨씬 커서 풀스캔이 디스크를 실제로 긁어야 한다면, 26~34%라는 교차점은 어느 쪽으로 움직일까요? (8장의 물리 read를 떠올려보세요)
  3. rt_random의 인덱스는 같은 행 수에 1,959페이지, rt_ordered는 1,125페이지였습니다. 넣은 데이터가 같은데 왜 한쪽만 부풀었을까요? (13·14장)

3부는 여기까지입니다. “왜 안 타는가”를 네 장에 걸쳐 봤어요 — 읽는 법(9장), 통계가 없을 때(10장), 우리가 못 쓰게 만들 때(11장), 그리고 쓸 수 있는데 안 쓰기로 할 때(12장).

네 장이 공통으로 가리킨 게 하나 있습니다. 추정과 실제의 간극입니다. 10장은 filtered=10이라는 상수가 실제와 얼마나 벌어지는지였고, 11장은 key에 인덱스가 찍혀 있는데 5,608회를 요청하더라는 이야기였고, 12장은 2.3배 부푼 추정이 실행계획을 뒤집는 장면이었어요.

그 간극이 가장 비싸게 청구되는 자리가 왕복이었습니다. 세컨더리 인덱스로 찾은 행마다 클러스터드 인덱스로 다시 내려가는 그 비용이 교차점을 정했으니까요.

그런데 지금까지는 전부 읽는 쪽 이야기였습니다. 인덱스는 읽기를 위해 만드는 물건이니 당연하죠.

4부는 청구서입니다. 그 읽기를 위해 쓰기가 무는 값이요. 방금 본 1,959페이지 대 1,125페이지가 그 첫 장면이고, 그 값은 인덱스를 몇 개 걸었느냐로 정해지지 않습니다.

13장. 인덱스 개수는 비용의 단위가 아니다 — 단조 증가 키 vs 난수 키

Last updated on