DB 스캔 종류

FULL TABLE SCAN

  • 인덱스를 활용하지 않고 테이블을 처음부터 끝까지 전부 탐색하는 방식
  • 매우 비효율적이며 디스크 I/O의 비용이 많이 발생

 

INDEX FULL SCAN

  • 인덱스 테이블을 처음부터 끝까지 전부 탐색하는 방식
  • 실제 테이블보다 상대적으로 적은 양을 차지하므로 일반적으로 full table scan보다 효율적
  • 하지만 index full scan도 결국 인덱스 테이블 “전체”를 읽어야 함
  • B+ 트리에서 첫 번째 leaf 노드를 수직 탐색 후 나머지 leaf 노드에 대해 순차적으로 전체 탐색을 진행

 

INDEX UNIQUE SCAN

  • 조회하고자 하는 1건의 데이터를 단번에 찾아오는 경우
  • 즉 고유 인덱스(UNIQUE)나 PK를 사용해서 1건의 데이터만 조회한 경우에 해당
  • 인덱스를 사용하는 방식 중 가장 효율적인 스캔 방식

 

INDEX RANGE SCAN

  • 인덱스를 활용해 범위 형태의 데이터를 조회하는 경우
  • BETWEEN, 부등호(<, >, ≤, ≥), IN, LIKE이 이에 해당
  • 인덱스를 사용하기 때문에 기본적으로 효율적인 방식이나, 그 범위가 넓을 경우 성능이 저하됨

 

INDEX LOOSE SCAN

  • 인덱스에서 필요한 부분만 선택하여 스캔
  • index range scan처럼 넓은 범위에 전부 접근하지 않고, where 조건문을 기준으로 불필요한 데이터를 구분한 뒤 해당 인덱스 키는 무시

 

INDEX MERGE SCAN

  • 두 개 이상의 인덱스를 병합하여 검색
  • where 문의 조건 열이 서로 다른 인덱스에 존재할 때 사용됨
  • 물리적으로 존재하는 개별 인덱스를 각각 접근하므로 인덱스에 접근하는 시간이 오래 걸림
  • 따라서 보통 별개로 생성된 인덱스들을 하나의 인덱스로 통합하여 튜닝함


EXPLAIN Type 칼럼

스캔 방식과 맵핑

 


References

 

'Computer Science > 데이터베이스' 카테고리의 다른 글

DB Index란?  (0) 2025.04.24
Mysql EXPLAIN/ANALYZE 간단 실습  (0) 2025.02.16
Mysql 옵티마이저와 통계 정보  (0) 2025.02.15

SQL 성능 개선을 위해 가장 많이 활용되는 방법이 인덱스 활용이다. 하지만 단순히 인덱스만 적용한다고 해서 무조건 해결되는 게 아니다. 인덱스를 적절하게 활용해야만 DB 성능이 개선된다.


인덱스란?

개념

  • 추가적인 쓰기 작업과 저장 공간을 활용하여 DB 테이블에 대한 검색 성능의 속도를 높여주는 자료 구조
  • 데이터를 빨리 찾기 위해 특정 칼럼을 기준으로 미리 ‘정렬’해둔 표
  • 인덱스를 활용하면 UPDATE/DELETE 성능도 함께 향상됨. 해당 연산 수행을 위해서는 조회가 먼저 필요하기 때문

 

예시

users 테이블에 1만 개의 row가 있을 때, 23살인 사용자를 조회한다고 해보자. 인덱스가 없다면 만 개의 데이터를 전부 찾아봐야 할 것이다. 이를 Full Scan이라고 한다. 그런데 만약 사전에 나이를 기준으로 데이터를 정렬해둔다면 어떨까? (1)이미 정렬되어 있기 때문에 23살 데이터의 위치를 찾기도 빠르고, (2)24살 이후의 데이터는 볼 필요가 없으므로 모든 데이터를 스캔할 필요가 없다.

인덱스 관리

인덱스는 항상 최신의 정렬된 상태를 유지해야 한다. 이에 따라 INSERT/UPDATE/DELETE 연산 수행 시 다음과 같은 연산이 추가적으로 진행되어 오버헤드가 발생할 수 있다.

  • INSERT: 새로운 데이터에 대한 인덱스 추가
  • DELETE: 삭제하는 데이터의 인덱스를 ‘사용하지 않음’ 처리
  • UPDATE: 기존 인덱스를 ‘사용하지 않음’ 처리 후, 갱신된 데이터에 대한 인덱스를 추가

 

장단점

  • 장점: 조회 성능 향상
  • 단점: 쓰기 작업(데이터 삽입, 수정, 삭제) 성능 저하

⇒ 느려지는 이유? 인덱스를 생성한다는 것은 결국 인덱스용 테이블을 추가적으로 생성한다는 뜻이고, 인덱스 관리를 위해서는 DB의 약 10%에 해당하는 저장 공간이 필요하다고 한다. 쓰기 작업 시 원본 테이블 뿐 아니라 인덱스 테이블에도 그 값을 반영해야 한다. 즉 두 개 이상의 테이블에서 처리가 필요하기 때문에 더 느려질 수밖에 없다. 따라서 무분별한 인덱스 사용은 지양해야 한다.

 

인덱스 적용 케이스

  • INSERT, UPDATE, DELETE가 자주 발생하지 않는 칼럼
  • JOIN, WHERE, ORDER BY에 자주 사용되는 칼럼
  • 데이터의 중복도가 낮은 칼럼

 


인덱스의 종류

기본키(Primary Key)

  • PK도 인덱스의 일종이기 때문에, 테이블의 데이터가 이 PK를 기준으로 정렬되어 보관됨
  • 인덱스란 데이터를 빨리 찾기 위해 특정 칼럼을 기준으로 미리 ‘정렬’해둔 표라고 했는데, 그 칼럼이 PK일 경우에는 클러스터링 인덱스라고 부름
  • 클러스터링 인덱스란 “원본 데이터” 자체가 정렬되는 인덱스를 의미함
  • 클러스터링 인덱스에는 PK밖에 없기 때문에, PK = 클러스터링 인덱스 라고 봐도 무방

 

유니크(UNIQUE) 조건

  • MySQL은 UNIQUE 제약 조건 설정 시 자동으로 인덱스가 생성됨
  • 그 이유는 MySQL에서 유니크 조건을 구성하고 사용할 때 기본적으로 인덱스의 원리를 활용하기 때문
  • 즉, 유니크 옵션을 사용한다 = 인덱스를 사용한다 = 조회 성능이 향상된다
  • 유니크 특징으로 인해 생성되는 인덱스를 “고유 인덱스(Unique Index)”라고 부름

 

멀티 칼럼 인덱스(Multiple Column Index)

  • 2개 이상의 칼럼을 묶어서 하나의 인덱스로 설정하는 것
  • 즉 2개 이상의 칼럼에 대해서 미리 정렬해둔 표를 생성하는 것
  • 여러 칼럼을 인덱스로 생성할 때에는 칼럼의 순서도 중요함. 그게 곧 정렬의 우선순위이기 때문
  • (부서, 나이)로 인덱스를 생성했다면, 부서를 기준으로 먼저 정렬되고 동일 부서 내 데이터들 사이에서 나이를 기준으로 다시 정렬됨

 

커버링 인덱스(Covering Index)

  • SQL문을 실행시킬 때 필요한 모든 컬럼을 갖고 있는 인덱스

SELECT id, created_at FROM users는 created_at 칼럼이 인덱스에 없기 때문에 결국 원본 테이블에 접근해야 하지만, SELECT id, name FROM users는 인덱스에 사용자가 요청하는 모든 데이터가 담겨있기 때문에 원본 테이블에 접근할 필요가 없다.

 


인덱스 사용 시 주의점

적절한 활용 필요

앞서 말했듯, 인덱스를 많이 정의한다고 무조건 좋은 것은 아니다. 향상되는 것은 조회 시의 성능일 뿐, 그 외의 쓰기 작업에서는 데이터의 수가 많아질수록 부하가 심해지므로, 인덱스 선언은 꼭 필요한 경우에만 사용해야 한다.

 

멀티 칼럼 인덱스 구성 순서

  1. 처음에 배치된 칼럼만 멀티 칼럼 인덱스에서 일반 인덱스처럼 활용 가능하다.
  2. 칼럼 순서는 소분류 > 중분류 > 대분류 순으로 구성하는 것이 좋다.

정의한 칼럼의 순서에 따라서 성능 차이가 난다. 만약 (부서, 이름) 순으로 인덱스를 정의했다면, ‘인사팀’에 해당하는 데이터를 찾은 후 ‘홍길동’을 찾을 것이다. 하지만 부서는 데이터 중복도가 높기 때문에 비효율적이며, (이름, 부서)순으로 정의하는 것이 더 높은 성능을 보일 수 있다. 즉 데이터 중복도가 낮은(≒ 카디널리티가 높은) 컬럼이 앞쪽으로 오는 게 좋은 경우가 많다. (항상 그런 것은 아니다)

 


References

쿼리 실행 계획 조회

테스트 환경 구축

이전까지의 포스팅은 모두 핵심만 정리하고자 간단한 형식으로 작성했지만, 여기서부터는 부가 설명이 필요할 것 같다. 우선 다양한 쿼리문을 테스트해보기 위해 로컬 MySQL 서버에 다음과 같이 테이블과 데이터를 생성해주었다. 귀찮아서 온라인 쿼리 사이트들도 사용해보려 했는데, 출력 결과 양식이 제대로 나오지 않아서(analyze 결과가..) 결국 MySQL Workbench를 사용했다.

스키마 구성은 제품 정보를 담는 product, 사용자 정보를 담는 customer, 그리고 주문 기록을 담는 orders 테이블로 간단히 구성했다.

더보기
-- product 테이블 생성
CREATE TABLE product (
  product_id INT AUTO_INCREMENT PRIMARY KEY,
  product_nm VARCHAR(100),
  description VARCHAR(255)
);

INSERT INTO product (product_nm, description) VALUES 
  ('Entity Framework Extensions', 'Use <a href="https://entityframework-extensions.net/" target="_blank">Entity Framework Extensions</a> to extend your DbContext with high-performance bulk operations.'),
  ('Dapper Plus', 'Use <a href="https://dapper-plus.net/" target="_blank">Dapper Plus</a> to extend your IDbConnection with high-performance bulk operations.'),
  ('C# Eval Expression', 'Use <a href="https://eval-expression.net/" target="_blank">C# Eval Expression</a> to compile and execute C# code at runtime.');

-- customer 테이블 생성
CREATE TABLE customer (
  customer_id INT AUTO_INCREMENT PRIMARY KEY,
  customer_nm VARCHAR(10)
);

INSERT INTO customer (customer_nm) VALUES 
  ('kate'),
  ('gia');

-- orders 테이블 생성
CREATE TABLE orders (
  order_id INT AUTO_INCREMENT PRIMARY KEY,
  customer_id INT,
  product_id INT,
  order_cnt INT,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  FOREIGN KEY (customer_id) REFERENCES customer(customer_id),
  FOREIGN KEY (product_id) REFERENCES product(product_id)
);

INSERT INTO orders (customer_id, product_id, order_cnt, created_at) VALUES 
  (1, 1, 2, '2025-01-01 09:00:00'),
  (1, 2, 4, '2025-01-15 11:30:00'),
  (2, 3, 1, '2025-02-01 15:45:00'),
  (2, 2, 5, '2025-02-01 17:30:00'),
  (2, 2, 5, '2025-02-11 14:00:00');

 

Workbench Visual Explain

workbench에서 쿼리를 실행하고 Result Grid 대신 Execution Plain 을 선택하면 아래와 같이 그림으로 실행 계획을 보여준다. 가장 직관적으로 확인할 수 있는 방법이다.

 

MySQL EXPLAIN 출력 형식

MySQL의 실행 계획을 보기 위해서는 쿼리문 앞에 EXPLAIN | DESCRIBE | DESC 명령어를 붙이면 된다. 3가지 모두 동일한 결과 출력한다. 추가로, MySQL 8부터 출력 형식을 단순 Table, JSON, Tree 형태로 지정 가능하다. json 형식은 기존의 표 형식보다 다양한 정보를 제공한다.

아래는 단순 출력 포맷을 보여주기 위한 예시로, 자세한 내용은 아직 몰라도 된다. 그리고, 정확히 무슨 기준인지는 모르겠지만 쿼리와 스키마가 모두 동일함에도 불구하고 explain/analyze 결과가 종종 달라지는 것 같다.. 그렇기 때문에 포스팅의 내용은 참고만 해주길! 이 부분에 대해서도 나중에 다룰 수 있다면 좋겠다.

-- 일반 테이블 형태
EXPLAIN 
SELECT * FROM customer;

-- JSON 형태
EXPLAIN FORMAT = JSON
SELECT * FROM customer;

-- TREE 형태
EXPLAIN FORMAT = TREE 
SELECT c.customer_nm, p.product_nm
FROM orders o, customer c, product p
WHERE o.customer_id = c.customer_id
	AND o.product_id = p.product_id;
더보기
{
  "query_block": {
    "select_id": 1,
    "cost_info": {
      "query_cost": "0.45"
    },
    "table": {
      "table_name": "customer",
      "access_type": "ALL",
      "rows_examined_per_scan": 2,
      "rows_produced_per_join": 2,
      "filtered": "100.00",
      "cost_info": {
        "read_cost": "0.25",
        "eval_cost": "0.20",
        "prefix_cost": "0.45",
        "data_read_per_join": "80"
      },
      "used_columns": [
        "customer_id",
        "customer_nm"
      ]
    }
  }
}

 

EXPLAIN ANALYZE 출력 형식

위에서 본 단순 EXPLAIN 결과는 MySQL 서버가 가지고 있는 통계 정보들을 활용한 예측 결과이다. 만약 실제 실행된 소요 시간과 실행 계획 정보를 출력하고 싶다면 ANALYZE 키워드를 사용해야 한다. 이 경우 Tree 형식으로 출력되며, 들여쓰기가 같은 레벨에서는 상단에 위치한 라인이 먼저 실행되고, 들여쓰기가 다른 레벨에서는 가장 안쪽에 위치한 라인이 먼저 실행된다.

EXPLAIN ANALYZE 
SELECT c.customer_nm, p.product_nm
FROM orders o, customer c, product p
WHERE o.customer_id = c.customer_id
	AND o.product_id = p.product_id;

 


EXPLAIN

필드 설명

이제 EXPLAIN 실행 결과 나오는 속성들에 대해 간단히 알아보겠다. 각 필드별 상세 옵션을 정리하기 보다는 간단한 정의를 알아보고, 실제 쿼리와 대조해보며 결과를 분석하는 방식을 익히는 데 초점을 맞추려고 한다.

사진은 아래 EXPLAIN 명령을 실행 시킨 결과이다. 이 중 rows와 filtered 속성은 MySQL의 통계 정보를 기반으로 한 것이며, 정확하지는 않은 값이다. 쿼리는 일부러 다양한 결과를 포함하고자 만든 것이므로 무의미하며 이해할 필요는 없다. 단순히 로직을 결과와 대조하며 이해하는 데 참고하길 바란다.

EXPLAIN 
(
  SELECT *  -- id = 1(row1~3)
  FROM orders o
  JOIN customer c ON o.customer_id = c.customer_id -- row2
  JOIN product p ON o.product_id = p.product_id -- row3
  WHERE c.customer_nm = 'gia' -- row1
  
  UNION ALL

  SELECT *  -- id = 2(row4~6)
  FROM orders o -- row 4
  JOIN customer c ON o.customer_id = c.customer_id -- row 5
  JOIN product p ON o.product_id = p.product_id -- row 6
  WHERE o.order_cnt > (
	SELECT COUNT(*)  -- id = 3(row 7)
    FROM orders
    WHERE created_at BETWEEN '2025-01-01' AND '2025-02-01'
  )
);

  • id: select를 기준으로 쿼리가 실행되는 순서를 나타낸다. MySQL에서는 조인을 하나의 단위로 실행하기 때문에 조인만 사용하는 쿼리라면 항상 1 값으로 나올 것이다. 위 예시에서는 union을 사용했고, 서브 쿼리까지 있기 때문에 id가 1~3까지 나온 것을 볼 수 있다.
  • select_type: select문의 유형을 나타낸다. 서브 쿼리나 union이 없으면 SIMPLE로 출력된다. union이 포함된 SQL 중 첫 번째 select의 타입은 PRIMARY, 아래 select는 UNION이다. SUBQUERY는 말 그대로 서브 쿼리에 해당한다.
  • table: 사용되는 테이블의 이름이나 alias를 출력한다. 서브쿼리나 임시 테이블을 만들 때에는 <subquery#> 나 <derived#>라고 출력된다. 만약 <derived2> 라면 id=2가 먼저 실행되고 그 결과인 임시 테이블을 참조하는 것이다.
더보기

리턴하는 값이 COUNT, 즉 스칼라 값이기 때문이다. MySQL이 이를 상수로 취급하여 단순 테이블 필터링으로 변환된 것이다. (by chatGPT) 이렇게 다양한 상황이 존재할 수 있어서 옵티마이저의 결정을 이해하기가 어려운 것 같다.

  • partitions: 해당 쿼리가 사용하고 있는 파티션을 나타낸다. 파티션이란 큰 테이블을 여러 개의 작은 논리적 단위로 나눈 것을 뜻한다. 지금은 사용하고 있지 않기 때문에, 파티션을 활용한다면 더 빨리 처리할 수 있다는 정도만 알고 넘어가자.
  • type: 테이블에서 데이터를 찾는 접근 방식을 나타낸다. 쿼리의 성능에 많은 영향을 미치는 매우 중요한 항목이다. ALL은 풀 스캔, 즉 테이블 전체를 스캔한 것이다. ref와 eq_ref는 비슷하지만, eq_ref는 기본 키나 유니크 키를 사용해서 항상 하나의 행만 반환한 것이고, ref는 일반 인덱스를 사용하기 때문에 여러 개의 행이 반환될 수 있다. 즉, eq_ref가 하나의 행만 찾으면 되기 때문에 더 효율적이다.
더보기

customer_id와 product_id는 둘 다 테이블의 기본 키인데 왜 row2는 ref이고 row3은 eq_ref일까? 이는 기준이 되는 table이 다르기 때문이다. row2의 table은 o로, orders 테이블에서의 customer_id(key)는 기본키가 아니기 때문에 일반 인덱스로 취급되어 ref인 것이다. row3의 table은 p이고, product_id(key)가 product 테이블의 키가 맞기 때문에 eq_ref이다. 그래서 key 필드에 PRIMARY라고 표시된 것이기도 하다.

  • possible_keys: 옵티마이저가 사용할 수 있는 인덱스 목록을 표시한다. 실제 사용된 것이 아니라 사용 가능한 것이다.
  • key: 위 목록 중에서 실제로 사용한 인덱스다. PRIMARY는 해당 테이블의 기본 키가 사용된 것이며, NULL인 경우 인덱스를 사용하지 않은 것이다. 이 경우 key, key_len, ref가 전부 NULL인 것을 확인할 수 있다.
  • key_len: 사용한 인덱스의 바이트 수이다. 복합 인덱스라면 인덱스를 모두 더한 값이 나온다. 인덱스가 길면 비효율적이기 때문에 참고할 수 있다.
  • ref: 조인 시 액세스된 테이블 정보가 표시된다. row2는 o 테이블(table)의 customer_id(key)를 c 테이블의 customer_id(ref)와 비교하여 조인했다는 의미이다.
  • rows: 접근 방식을 통해 접근한 행의 수를 나타낸다.
  • filtered: 스토리지 엔진에서 가져온 데이터를 MySQL 엔진이 필터링하여 제거된 비율을 나타낸다. row1의 경우, 사용자가 2명밖에 없기 때문에 where 조건으로 50% 필터링되었다는 의미이다. row7도 총 5개의 데이터 중 하나만 해당 범위를 벗어나기 때문에 20%가 필터링되었다.
  • Extra: 옵티마이저의 동작에 대한 추가 정보를 나타낸다. 여기서는 WHERE 절을 이용해서 데이터를 필터링 했음을 알 수 있다.

 


ANALYZE

출력 양식 설명

analyze를 사용하면 쿼리의 실행 시간과 각 단계의 실행 계획을 구체적으로 알 수 있다. EXPLAIN이 통계 수치를 기반으로 예상한 값을 알려줬다면, ANALYZE는 실제 데이터를 기반으로 한다. 출력은 트리 구조로 표시되며, 가장 안쪽에 들여쓰기된 작업이 먼저 실행된다. 각 줄은 조인이나 스캔 등의 작업 정보와 실행 시간에 대한 비용 정보로 구성된다.

 

 

비용 정보 형식

(cost=X rows=Y) (actual time=A...B rows=Z loops=D)

  • X: 옵티마이저가 예측한 비용
  • Y: 옵티마이저가 예측한 처리 결과 행의 수
  • A: 첫 번째 행을 반환하는데 걸린 시간(ms)
  • B: 모든 행을 반환하는데 걸린 총 시간(ms)
  • C: 실제 처리된 결과 행의 수
  • D: 해당 작업이 반복된 횟수

 


References

'Computer Science > 데이터베이스' 카테고리의 다른 글

DB 스캔 종류와 mysql explain type 칼럼  (0) 2025.04.28
DB Index란?  (0) 2025.04.24
Mysql 옵티마이저와 통계 정보  (0) 2025.02.15

MySQL 구조와 실행 순서

MySQL 구조

  • MySQL 엔진
    • Connection Handler: Client와의 연결을 관리하고 쿼리 요청을 처리
    • SQL 인터페이스: DML, DDL, Procedure, View 등 SQL 인터페이스 제공
    • SQL 파서: SQL 문법 오류 탐지 및 서버에서 처리하기 위한 트리 형태로 파싱
    • SQL 옵티마이저: 쿼리 최적화 및 실행 계획 수립
    • 캐시와 버퍼: 성능 향상을 위한 보조 저장소 기능
  • Storage 엔진
    • 핸들러 API를 통해 MySQL 엔진이 스토리지 엔진에 읽기/쓰기 요청
    • 스토리지 엔진은 플러그인 형태로 제공되기 때문에 테이블마다 선택 가능(InnoDB가 기본값)

 

쿼리 실행 순서

  1. SQL 파싱
    • MYSQL 서버의 SQL 파서라는 모듈에서 처리하며, 토큰 단위의 SQL 파싱 트리를 생성
  2. 전처리
    • 파싱 트리를 기반으로 쿼리 문장의 구조적 문제점 검사
    • 테이블과 칼럼의 존재 여부, 접근 권한 등도 체크
  3. 최적화 및 실행 계획 수립
    • 옵티마이저가 파싱 트리를 통해 최적의 실행 계획을 수립
    • 불필요한 조건 제거 및 복잡한 연산의 단순화, 레코드의 임시 테이블 저장 등을 처리
  4. 데이터 로드 및 처리
    • 위 실행 계획을 기반으로 Storage 엔진으로부터 데이터 로드
    • MySQL 엔진에서 받은 레코드를 조인하거나 정렬하는 작업을 수행

 


MySQL 옵티마이저

옵티마이저란

  • DBMS의 목적은 많은 데이터를 안전하고 빠르게 저장 및 관리하는 것
  • 이를 위해 옵티마이저가 쿼리를 최적화하며, 2가지 종류가 있음
  • 비용 기반 최적화(Cost-based optimizer, CBO)
    • 현재 대부분의 DBMS는 CBO 방식을 사용
    • 각 단위 작업의 비용 정보와 통계 정보를 활용하여 비용을 산출
  • 규칙 기반 최적화(Rule-based optimizer, RBO)
    • 단순히 옵티마이저에 내장된 우선 순위에 따라 실행 계획을 수립
    • 같은 쿼리에 대해서는 항상 같은 계획을 수립한다는 특징을 가짐

 

MySQL의 Optimizer

  • MySQL의 옵티마이저는 비용 기반으로 쿼리를 최적화함
  • 어디까지나 추정 값이며 항상 최적의 실행 계획을 만들어낼 수 있는 것은 아님
  • 사용자가 보완할 수 있도록 EXPLAIN 기능을 제공함 → 쿼리 튜닝에 활용

 

EXPLAIN/ANALYZE

자세한 포스팅은 다음 글을 참고!

  • EXPLAIN
    • 옵티마이저가 주어진 쿼리에 대해서 수립한 실행 계획
    • 실제 수행 순서가 아닌 MySQL의 통계 정보를 기반으로 계산한 예측 값
  • EXPLAIN ANALYZE(MySQL 8부터 지원)
    • 실제 실행된 쿼리의 계획과 단계 별 소요 시간을 TREE 형식으로 출력
    • 들여쓰기가 같은 레벨에서는 상단에 위치한 라인이 먼저 실행됨
    • 들여쓰기가 다른 레벨에서는 가장 안쪽에 위치한 라인이 먼저 실행됨

 


통계 정보

통계 저장 범위의 확장

  • MySQL 5.5: 각 테이블의 통계 정보가 메모리에 관리되어 서버 재시작 시 모두 사라짐
  • MySQL 5.6: InnoDB 스토리지 엔진에 통계 정보를 영구적으로 관리
  • MySQL 8: 데이터 분포도를 수집해 저장하는 히스토그램 정보가 도입

 

자동/수동 수집

  • 기본적으로 자동 수집되며, 필요에 따라 수집 방식 조정 가능
  • ANALYZE TABLE 명령어를 통해 특정 테이블의 최신 통계 정보 갱신 가능
  • SHOW INDEX 명령어를 통해 최신 통계 정보 확인 가능
  • 기본적으로 자동 수집되며, 필요에 따라 수집 방식 조정 가능
  • 히스토그램은 명령어를 통해 수동으로 수집되며 시스템 딕셔너리에 컬럼 단위로 저장되어 관리됨. MySQL 서버가 시작될 때 information_schema.column_statistics 테이블에 로드하여 활용

 

자동 갱신 케이스

  • 테이블이 새로 오픈되는 경우
  • 테이블의 레코드가 대량으로 변경되는 경우(테이블 전체 레코드 1/6 규모의 INSERT, UPDATE, DELETE가 실행되는 경우)
  • ANALYZE TABLE 명령이 실행되는 경우
  • SHOW TABLE STATUS 혹은 SHOW INDEX 명령이 실행되는 경우
  • InnoDB 모니터가 활성화되는 경우
  • Innodb_stats_on_metadata 시스템 설정이 ON인 상태에서 SHOW TABLE STATUS 명령이 실행되는 경우

 

히스토그램의 용도

  • MySQL의 일반적인 통계는 인덱스를 기반으로 카디널리티를 예측하지만, 인덱스가 없는 칼럼의 데이터 분포를 제대로 반영하지 못할 수 있음
  • MySQL 5.7까지는 실제 응용 프로그램의 데이터는 항상 균등한 분포를 가지지 않는다는 점을 간과, 정확도가 높지 않았음
  • 예를 들어, 아래 테이블에서 order_status 칼럼의 값이 complete 90%와 canceled 10%의 비중이라면 MySQL의 기본 통계는 이를 반영하지 못해 잘못된 실행 계획을 세울 수 있음
SELECT * FROM orders WHERE order_status = 'completed';

⇒ 히스토그램을 수집하면 데이터 분포를 정확히 이해하고 최적의 실행 계획을 수립할 수 있음

 


References

'Computer Science > 데이터베이스' 카테고리의 다른 글

DB 스캔 종류와 mysql explain type 칼럼  (0) 2025.04.28
DB Index란?  (0) 2025.04.24
Mysql EXPLAIN/ANALYZE 간단 실습  (0) 2025.02.16

+ Recent posts