동일한 131,072행에서 histogram 없음, fresh, stale 상태의 MySQL 8.4.10 예상 행 수와 실제 Filter 128행을 plan, bucket, row flow로 비교합니다.
No Histogram
- access_type
ALL - selected key
null - scan estimate
- 130917
- filtered
- 10.00%
- produced estimate
- 13091
- Filter actual
- 128
- absolute error
- 12963
- filesort input
- 128
- Sort / LIMIT output
- 100
Histogram
COLUMN_STATISTICS: 0 rows
조건, 정렬, LIMIT을 한 문장으로 읽습니다
order_events 131,072행에서 sales_channel='PARTNER'인 row를 찾고, ordered_at과 id 역순으로 정렬해 최신 100행을 반환하는 SQL입니다.
세 plan 모두 같은 ALL access를 사용합니다
sales_channel secondary index가 없으므로 selected key는 null이고 access type은 ALL입니다. Table scan 다음에 Filter, filesort, Limit가 이어집니다.
COLUMN_STATISTICS에 sales_channel row가 없습니다
조회 결과가 0행이라 histogramPresent=false입니다. 이 상태는 파일 부재가 아니라 실제 COLUMN_STATISTICS 조회 결과로 기록됐습니다.
최종 데이터 분포는 세 variant에서 같습니다
DIRECT 65,408, INTERNAL 32,768, MARKETPLACE 32,768, PARTNER 128행입니다. PARTNER의 실제 비율은 0.09765625%입니다.
PARTNER value에 대한 histogram bucket이 없습니다
singleton bucket과 cumulative frequency가 없는 상태입니다. 실제 최종 분포 128행은 fixture 영역에서 별도로 유지됩니다.
filtered 10.00%가 13,091행을 예상합니다
scan estimate 130,917에 filtered 10.00%를 적용한 계산값은 13,091.7이고, native rows_produced_per_join은 13,091입니다.
Table scan은 실제 131,072행을 한 번 읽습니다
EXPLAIN ANALYZE의 Table scan iterator는 actual rows 131,072, loops 1입니다. 이 값은 optimizer의 scan estimate와 구분해서 봅니다.
sales_channel='PARTNER'가 128행을 남깁니다
고정 fixture에서 128행이 predicate를 통과하고 130,944행이 제외됩니다. Filter iterator도 actual rows 128, loops 1을 기록했습니다.
예상 13,091행과 실제 128행의 차이는 12,963행입니다
비교 대상은 Table scan estimate가 아니라 Filter가 만들 것으로 예상한 row 수입니다. 실제 Filter output보다 크게 예상했습니다.
128행이 filesort에 들어가고 100행이 반환됩니다
Filter output 128행이 Top-N Sort의 입력입니다. Sort와 LIMIT output은 100행이고 SORT_ROWS도 100입니다. cutoff는 id 116, 다음 제외 row는 id 112입니다.
세 variant가 같은 최신 100행을 반환합니다
결과는 id 512부터 116까지 4씩 감소하며 checksum은 16e6f9b5…9ecd입니다. histogram 상태는 결과 값이나 순서를 바꾸지 않았습니다.
histogram 부재와 10.00% estimate를 같은 근거에서 확인합니다
COLUMN_STATISTICS 0행, EXPLAIN의 ALL·filtered·rows_produced, EXPLAIN ANALYZE actual rows, statement counter, 100행 result를 함께 대조합니다.
캡처 결과 100행
MySQL 8.4.10 ·
16e6f9b59437241a7ed4ccff509bb6afb870a02f79d0dd45718f52812bec9ecd
Fresh Histogram
- access_type
ALL - selected key
null - scan estimate
- 130917
- filtered
- 0.10%
- produced estimate
- 130
- Filter actual
- 128
- absolute error
- 2
- filesort input
- 128
- Sort / LIMIT output
- 100
Histogram
singleton · sampling 1.0 · manual · 4 buckets
조건, 정렬, LIMIT을 한 문장으로 읽습니다
order_events 131,072행에서 sales_channel='PARTNER'인 row를 찾고, ordered_at과 id 역순으로 정렬해 최신 100행을 반환하는 SQL입니다.
세 plan 모두 같은 ALL access를 사용합니다
sales_channel secondary index가 없으므로 selected key는 null이고 access type은 ALL입니다. Table scan 다음에 Filter, filesort, Limit가 이어집니다.
skew mutation 뒤에 Fresh Histogram을 만들었습니다
최종 분포에서 manual UPDATE로 만든 singleton histogram입니다. sampling-rate 1.0, 4 buckets, auto-update false가 캡처됐습니다.
최종 데이터 분포는 세 variant에서 같습니다
DIRECT 65,408, INTERNAL 32,768, MARKETPLACE 32,768, PARTNER 128행입니다. PARTNER의 실제 비율은 0.09765625%입니다.
누적 빈도 차이로 PARTNER mass 0.0009765625를 읽습니다
MARKETPLACE CDF 0.9990234375에서 PARTNER CDF 1.0까지의 차이가 PARTNER value mass입니다. 실제 128/131,072와 같습니다.
filtered 0.10%가 130행을 예상합니다
scan estimate 130,917에 표시된 filtered 0.10%를 적용하면 130.917이고, native rows_produced_per_join은 정수 130입니다.
Table scan은 실제 131,072행을 한 번 읽습니다
EXPLAIN ANALYZE의 Table scan iterator는 actual rows 131,072, loops 1입니다. 이 값은 optimizer의 scan estimate와 구분해서 봅니다.
sales_channel='PARTNER'가 128행을 남깁니다
고정 fixture에서 128행이 predicate를 통과하고 130,944행이 제외됩니다. Filter iterator도 actual rows 128, loops 1을 기록했습니다.
예상 130행과 실제 128행의 차이는 2행입니다
Fresh Histogram이 기억한 PARTNER 비율과 실제 최종 분포가 맞아 Filter output estimate가 실제 128행에 가깝습니다.
128행이 filesort에 들어가고 100행이 반환됩니다
Filter output 128행이 Top-N Sort의 입력입니다. Sort와 LIMIT output은 100행이고 SORT_ROWS도 100입니다. cutoff는 id 116, 다음 제외 row는 id 112입니다.
세 variant가 같은 최신 100행을 반환합니다
결과는 id 512부터 116까지 4씩 감소하며 checksum은 16e6f9b5…9ecd입니다. histogram 상태는 결과 값이나 순서를 바꾸지 않았습니다.
Fresh bucket, 130행 estimate, 실제 128행을 연결합니다
raw histogram CDF, EXPLAIN의 filtered와 rows_produced, Filter actual rows, SORT_ROWS 100, 동일 result checksum을 locator별로 대조합니다.
캡처 결과 100행
MySQL 8.4.10 ·
16e6f9b59437241a7ed4ccff509bb6afb870a02f79d0dd45718f52812bec9ecd
Stale Histogram
- access_type
ALL - selected key
null - scan estimate
- 130917
- filtered
- 25.00%
- produced estimate
- 32729
- Filter actual
- 128
- absolute error
- 32601
- filesort input
- 128
- Sort / LIMIT output
- 100
Histogram
singleton · sampling 1.0 · manual · 4 buckets
조건, 정렬, LIMIT을 한 문장으로 읽습니다
order_events 131,072행에서 sales_channel='PARTNER'인 row를 찾고, ordered_at과 id 역순으로 정렬해 최신 100행을 반환하는 SQL입니다.
세 plan 모두 같은 ALL access를 사용합니다
sales_channel secondary index가 없으므로 selected key는 null이고 access type은 ALL입니다. Table scan 다음에 Filter, filesort, Limit가 이어집니다.
mutation 전 Histogram이 ordinary ANALYZE 뒤에도 남았습니다
균등 분포에서 만든 manual histogram의 raw JSON, bucket, timestamp가 mutation과 일반 ANALYZE TABLE 뒤에도 동일했습니다.
최종 데이터 분포는 세 variant에서 같습니다
DIRECT 65,408, INTERNAL 32,768, MARKETPLACE 32,768, PARTNER 128행입니다. PARTNER의 실제 비율은 0.09765625%입니다.
Stale Histogram은 PARTNER mass를 0.25로 기억합니다
네 value가 각각 25%였던 초기 분포의 CDF가 유지됩니다. 현재 PARTNER 실제 비율 0.0009765625와 크게 다릅니다.
filtered 25.00%가 32,729행을 예상합니다
scan estimate 130,917의 25%는 32,729.25이고, native rows_produced_per_join은 32,729입니다. 초기 PARTNER 32,768행에 가깝습니다.
Table scan은 실제 131,072행을 한 번 읽습니다
EXPLAIN ANALYZE의 Table scan iterator는 actual rows 131,072, loops 1입니다. 이 값은 optimizer의 scan estimate와 구분해서 봅니다.
sales_channel='PARTNER'가 128행을 남깁니다
고정 fixture에서 128행이 predicate를 통과하고 130,944행이 제외됩니다. Filter iterator도 actual rows 128, loops 1을 기록했습니다.
예상 32,729행과 실제 128행의 차이는 32,601행입니다
오래된 25% mass가 현재 Filter output을 크게 과대 추정합니다. 실제 데이터와 결과는 같고 통계 상태와 estimate만 달라졌습니다.
128행이 filesort에 들어가고 100행이 반환됩니다
Filter output 128행이 Top-N Sort의 입력입니다. Sort와 LIMIT output은 100행이고 SORT_ROWS도 100입니다. cutoff는 id 116, 다음 제외 row는 id 112입니다.
세 variant가 같은 최신 100행을 반환합니다
결과는 id 512부터 116까지 4씩 감소하며 checksum은 16e6f9b5…9ecd입니다. histogram 상태는 결과 값이나 순서를 바꾸지 않았습니다.
변하지 않은 Stale CDF와 32,729행 estimate를 대조합니다
mutation 전후 동일한 raw histogram, EXPLAIN의 25.00% estimate, 실제 Filter 128행, 동일한 sort와 result를 한 화면에서 확인합니다.
캡처 결과 100행
MySQL 8.4.10 ·
16e6f9b59437241a7ed4ccff509bb6afb870a02f79d0dd45718f52812bec9ecd