본문 바로가기

카테고리 없음

[STEP15] 기본 쿼리와 인덱스 추가 시 성능 비교

0. 나의 시나리오에서 수행하는 쿼리들을 수집해보고, 필요하다고 판단되는 인덱스를 추가하고 쿼리의 성능개선 정도를 작성하여 제출

 

1. 나의 시나리오(e-commerce)에서 수행하는 쿼리들 수집

where절에 쿼리 추가 가능성 있는 쿼리 - 주황색

자주 쓰일 가능성이 높은 쿼리 - 초록색

 

0. 유저 서비스

1. 유저 등록

insert into user (balance,name)
values (?,?)

 

2.. 유저 조회 - 유저 존재 파악시 자주 조회

select u1_0.id,
       u1_0.balance,
       u1_0.name
from user u1_0
where u1_0.id=?

 

1. 쿠폰 서비스

 

1.쿠폰 등록

insert into coupon (coupon_state,create_date,expired_date,remain_quantity,type,value_of_type)
values (?,?,?,?,?,?);

 

2.쿠폰 조회(쿠폰 id) - 자주 조회하는 쿼리

select
    c1_0.id,
    c1_0.coupon_state,
    c1_0.create_date,
    c1_0.expired_date,
    c1_0.remain_quantity,
    c1_0.type,
    c1_0.value_of_type
from coupon c1_0
where c1_0.id=?

 

 

2. 유저 발급 쿠폰

 

1. 유저 발급 쿠폰 정보 생성

insert into user_coupon (coupon_id,issued_time,state,used_time,user_id)
values (?,?,?,?,?)

 

2.  유저 발급 쿠폰 리스트 조회 -> 쿼리 개선 state 추가 가능성 있음, 자주조회 가능성 있음  

select uc1_0.id,
       uc1_0.coupon_id,
       uc1_0.issued_time,
       uc1_0.state,
       uc1_0.used_time,
       uc1_0.user_id
from user_coupon uc1_0
where uc1_0.user_id=?

 

3. 상품 서비스

 

1. 상품 등록

insert into product (name,price)
values (?,?)

 

2. 삼품 조회 - 자주 조회하는 쿼리

select 
    p1_0.id,
    p1_0.name,
    p1_0.price 
from product p1_0 
where p1_0.id=?

 

4. 재고 서비스

 

1. 재고 조회 -> 쿼리에서 remain_quantity 가 0이 아닌 조건 자체를 넣어서 조회할 가능성 있음, 자주조회 가능성 있음

select s1_0.id,
       s1_0.product_id,
       s1_0.remain_quantity
from stock s1_0 
where s1_0.product_id=?

 

5. 주문 서비스

 

1. 주문 등록

insert into purchase_order (state,total_price,user_id) 
values (?,?,?)

 

2. 주문 검색 -> 쿼리에서 state 추가 가능성 있음, 자주 조회 가능성 있음

select po1_0.id,
       po1_0.state,
       po1_0.total_price,
       po1_0.user_id 
from purchase_order po1_0 
where po1_0.id=?

 

 

6. 결제 서비스

 

1. 결제 정보 저장

insert into payment (
                      id,
                      purchase_order_Id,
                      user_id,
                      state,vendor,
                      total_order_price,
                      payment_create_date_time,
                      payment_update_date_time
                    ) 
values (?,?,?,?,?,?,?,?)

 

7. 통계 (인기 상품 조회 - 외부 API로 가정)

1. 인기상품조회(3일간 상위 5개) -> 로직에 인덱스 추가 가능성 있음 , entity에  state 추가 가능성 있음.

SELECT s.product_id AS productId,
       s.product_name AS productName,
       SUM(s.sales_volume) AS totalVolume
FROM statics s
WHERE s.sales_date >= (날짜3일 전)
GROUP BY s.product_id, s.product_name
ORDER BY totalVolume
DESC LIMIT (5개)

 

 

2. 필요하다고 판단되는 인덱스

인덱스란 무엇인가?

1. 내 나름의 정의

 

데이터가 많거나 복잡한 조건의 검색을 빠르게 수행할 수 있도록 미리 정렬된 데이터셋을 만들어 놓은 것


2. 인덱스의 개념

 

  • 데이터베이스 테이블에서 특정 열(column)의 값을 기준으로 정렬한 자료구조
  • 데이터를 빠르게 검색할 수 있도록 도와주며, 테이블 전체를 탐색하는 Full Table Scan(FLS)을 방지
  • B-Tree, Hash, Bitmap 등의 구조로 저장

 

3. 인덱스의 장,단점

 

장점

  1. 검색 속도 향상: WHERE, JOIN, ORDER BY 같은 쿼리에서 성능이 크게 개선됨
  2. 정렬 성능 향상: ORDER BY나 GROUP BY를 사용할 때 추가적인 정렬 비용이 줄어듦
  3. 빠른 레코드 접근: 특정 행을 찾을 때 빠르게 탐색 가능

단점

  1. INSERT, UPDATE, DELETE 성능 저하: 인덱스가 많을수록 데이터 변경 시 추가적인 연산이 필요
  2. 추가적인 저장 공간 필요: 인덱스 자체가 별도의 데이터 구조이므로 디스크 공간을 차지함
  3. 잘못된 인덱스 사용 시 성능 저하: 너무 많은 인덱스를 사용하면 오히려 성능이 나빠질 수 있음

4. 인덱스 장점들의 이유

 

- where의 경우 - index 미사용 : full table scan 발생 (O(N))

                                      사용 : Index Range Scan (O(log N) + O(K))

  • O(log N) → 인덱스를 사용하여 검색할 시작 지점을 찾는 비용
  • O(K) → 검색된 이후, 원하는 데이터 K개를 가져오는 비용

- join의 경우 - index 미사용 : Nested Loop Join (O(M * N))

                                    사용 : Index Lookup (O(M log N))

  •   M, N은 두 테이블의 탐색 조건 key의 개수

- order by의 경우 - index 미사용 : Filesort (O(N log N)) 

                                            사용 : Index Scan (O(N))

  • 정렬되지 않은 데이터를 가져온 후 추가 정렬 O(N * log N)
  • 인덱스에서만 검색 O(N)

- group by의 경우 - index 미사용 : 테이블 풀 스캔 + 정렬 -> temp를 순회하며 정렬 O(N log N)

                                            사용 : 정렬 비용 제거 + 테이블 풀 스캔 제거 O(N) (인덱스 순차 접근)

 

※ 인덱스 탐색이 log(N)이 나오는 이유는 B-tree 방식을 사용했을 떄이다.

인덱스 유형     시간 복잡도       범위 검색 지원  ORDER BY 지원 MySQL 기본 사용
B+Tree 인덱스 O(log N) 가능 가능 InnoDB 기본
해시 인덱스 O(1) (단, 정확한 값 검색만) 불가능 불가능 직접 설정 필요

5. 단일 인덱스와 복합 인덱스

 

단일 인덱스 

  • 데이터는 B+Tree의 정렬된 형태로 저장됨
  • 리프 노드(Leaf Node)에 실제 데이터의 위치(ROW POINTER) 저장
  • 트리의 높이는 log N 수준이므로 검색이 빠름

- 데이터 탐색 : O(logN)  -> 포인터 저장 -> 데이터 읽기 O(1)

 

 

복합인덱스

 

  • 단일 인덱스와 다르게, 여러 개의 컬럼을 기준으로 정렬된 트리 구조 유지
  • LEFT-MOST RULE (왼쪽 우선 원칙) 적용됨

 


 

인덱스 추가 장점을 기준으로 이번 프로젝트의 인덱스 추가할 쿼리들의 조건을 생각해보았다.

 

- where 절에 조건이 2개 이상일 것.

- 조건이 entity id가 아닐 것.

- 정렬 조건(order by, group by)이 들어갈 것.

- join이 들어갈 것.

- 자주 조회하는 쿼리 혹은 복잡한 쿼리 일 것.

 

위 의 수집된 실행 쿼리들 중

 

현재 인덱스를 바로 추가하여 성능 테스트를 할 수 있는 조건의 쿼리는

7. 통계의 "인기상품 조회" 쿼리가 salesDate(특정 날짜) 기준, "group by"로 product_id, product_name, "order by"로 total   totalVolume(총 판매 개수) 기준으로 "정렬"하여 통계 개수가 늘어나면 범위탐색(날짜)과 정렬(group by, order by) 로인하여 조회 시간이 오래 걸릴 것으로 판단되어 인덱스를 바로 추가해서 성능 테스트를 할 것이다.

 

나머지 자주 쓰이거나 where 절에 조건이 추가될 가능성이 있는 쿼리들은

  • 유저 발급 쿠폰 리스트 조회 - where절에 쿠폰 사용여부의 state가 추가 개선 가능성 있음.
  • 쿠폰 조회 - 쿠폰이 존재하는지 확인시, 유저 발급 쿠폰 리스트 조회시, 주문, 결재시 자주 호출 가능성. 
  • 상품 조회 - 주문, 결재 등 다른 서비스에서 상품 존재 유무 파악시 조회를 하기 때문에 자주 쓰인다고 판단.
  • 재고 조회 - 상품의 재고가 남아있는지 여부 등이 주문, 결제에서 호출하기 때문에 자주 쓰인다고 판단.
  • 주문 검색 - 주문 쿼리는 history를 남기는 기능과 주문 자체를 호출하여 검색하는 기능이 많이 쓰이기 떄문에 자주 쓰인다고 판단

등이 존재하였으나 당장 단일 인덱스를 추가하기는 시간이 부족하므로 인기 상품 조회 쿼리에 인덱스를 바로 추가하여 비교 분석해본 후 추후 인기 상품 조회 외의 쿼리들을 인덱스를 추가하여 테스트 해볼 예정이다.

 

2. 더미데이터 추가하기

 

1. local에서 mysql에 400만개 정도의 랜덤 statics 쿼리를 생성 - 3번 반복 -> 1258만 2912개 데이터 입력

- src/main/resources 폴더 안에 data.sql 파일을 생성 해주고 application.yml 파일 설정을 해주면 spring boot 시작시 랜덤 쿼리 생성(1회당 419만4304개)된다.

 

data.sql 파일

INSERT INTO statics (product_id, product_name, sales_volume, sales_date)
SELECT FLOOR(1 + (RAND() * 1000)),
       CONCAT('Product ', FLOOR(1 + (RAND() * 1000))),
       FLOOR(RAND() * 500),
       CURDATE() - INTERVAL FLOOR(RAND() * 365) DAY
FROM (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) t1,
     (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) t2,
     (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) t3,
     (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) t4,
     (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) t5,
     (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) t6,
     (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) t7,
     (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) t8,
     (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) t9,
     (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) t10,
     (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) t11;

 

 application.yml 파일

spring:
  application:
    name: hhplus
  profiles:
    active: local
  datasource:
    name: HangHaePlusDataSource
    type: com.zaxxer.hikari.HikariDataSource
    hikari:
      maximum-pool-size: 3
      connection-timeout: 10000
      max-lifetime: 60000
    driver-class-name: com.mysql.cj.jdbc.Driver
  jpa:
    defer-datasource-initialization: true #data.sql 설정
    open-in-view: false
    generate-ddl: false
    show-sql: true
    hibernate:
      ddl-auto: none
    properties:
      hibernate.timezone.default_storage: NORMALIZE_UTC
      hibernate.jdbc.time_zone: UTC
  sql.init.mode: always # data.sql 설정

---
spring.config.activate.on-profile: local, test

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/hhplus?characterEncoding=UTF-8&serverTimezone=UTC
    username: 
    password:

 

 

3. 쿼리의 성능개선 정도

 

1.성능 개선 파악  계획

1. 예측

 

인덱스 없을 경우 내가 예상하는 시간 공식

 

where절에 시간 조건 1개  * group by * order by

 

O(N)  * O(N log N) * O(N log N) 시간  = 12582912 * (12582912 * log(12582912))^2 = 1.0042305e+23 ms 는 너무 긴시간으로 보여서 chat  gpt에 검색해 보았다.

 

*가 아니라 + 여야 시간이 어느 정도 맞는다. 또한 1row를 읽는 시간과 디스크 i/o 시간도 변수가 된다. (1row를 읽는 시간과 디스크i/o 시간 중 더 느린 시간을 적용해주면 됨)

char gpt가 뱉은 답변의 경우에 table 풀 스갠과 group by , order by 하는 시간을 독립 변수로 생각하기 때문에 덧셈을 해주는 것으로 추정되었다.

 

보통 한행(row)을 읽는 시간  (0.01ms ~ 0.1ms) - 0.01ms.의 경우 redis와 같은 메모리 기반 db, 0.1ms의 경우 디스크 기반 

ms=1/1000 초라고 한다. 따라서 0.00001 초 ~ 0.0001초

디스크 I/O 속도 (초당 100,000 ~ 1,000,000 row)

 

( O(N)  + O(N log N) + O(N log N) ) / 초당 수행 row 시간

따라서 

((12582912  + 12582912 *(log( 12582912 )) + 12582912 *(log( 12582912 )) )* 0.0000001 을 해주면 예상 시간으로 19.1254755094 초가 걸린다. 

 

... 실제 쿼리를 날렸을때는 3초가 걸렸으므로 예상이 틀렸는데 왜 그럴까?

 

+ 를 해줄때 filter된 조건 계산을 생각해야하는데 그냥 모든 테이블을 계속 풀스캔하는 조건으로 공식을 적용해서 이런 시간이 나온 것으로 보인다.

 

처음 테이블 풀스캔 O(N) + group by진행(O(N*log(그룹으로 필터링된 개수)) + order by 진행 (그룹으로 필터링된 개수 * log(그룹으로 필터링 된 개수)의 시간 순으로 계산한다.

 

statics 테이블에 삽입된 총 개수 :  12582912(1000만건)

select count(*) from statics
    -> ;
+----------+
| count(*) |
+----------+
| 12582912 |
+----------+
1 row in set (0.53 sec)

 

statics 테이블에 삽입된 오늘 기준 3일간 데이터 개수 :  103632(10만건)

select count(*) from statics
    -> where sales_date >= (NOW() - INTERVAL 3 DAY)
    -> ;
+----------+
| count(*) |
+----------+
|   103632 |
+----------+
1 row in set (2.41 sec)

 

총 쿼리 실행 시간 3.71 초

mysql> SELECT s.product_id AS productId,
    -> s.product_name AS productName,
    -> SUM(s.sales_volume) AS totalVolume
    -> FROM statics s
    -> WHERE s.sales_date >= (NOW() - INTERVAL 3 DAY)
    -> GROUP BY s.product_id, s.product_name
    -> ORDER BY totalVolume DESC
    -> LIMIT 5;
+-----------+-------------+-------------+
| productId | productName | totalVolume |
+-----------+-------------+-------------+
|       809 | Product 279 |        5800 |
|       919 | Product 945 |        5798 |
|       649 | Product 170 |        5410 |
|       647 | Product 169 |        5403 |
|       782 | Product 261 |        5258 |
+-----------+-------------+-------------+
5 rows in set (3.71 sec)

 

(12582912 + 12582912 *(log( 103632 )) + 103632  *(log( 103632 ))) * 0.0000001 = 7.62121957867 초가 걸리는 것으로 예상 되었다.

 

...로 진행하려고 했으나 쿼리 생성시 productId 1개당 productName은 동일하게 생성해야되는걸 깜빡하고 더미데이터를 넣었다. 복합 그룹으로 productId * productName 를 각각 랜덤으로 돌려 데이터를 생성했기 때문에 productId * productName 만큼  카디널리티가 감소하여 쿼리 자체가 잘못되었다는 걸 깨달았다.

 

 

그래서 productId 나 productName 중 하나만 group by로 그룹핑 하는 식으로 쿼리를 바꿔서 인덱스 적용하기로 하였다.

 

 

다음은 , 바꾼 쿼리와 실행 계획(product_name 1000개만 group by 조건으로 생각하기로 함)이다.

 

(12582912 + 12582912 *(log(1000)) + 1000*(log( 1000 ))) * 0.0000001 = 5.0334648 초가 걸리는 것으로 예상 되었다

 

SELECT s.product_name as productName,
    -> SUM(s.sales_volume) AS totalVolume
    -> FROM statics s
    -> WHERE s.sales_date >= (NOW() - INTERVAL 3 DAY)
    -> GROUP BY s.product_name
    -> ORDER BY totalVolume DESC
    -> LIMIT 5;
+-------------+-------------+
| productName | totalVolume |
+-------------+-------------+
| Product 660 |       40412 |
| Product 629 |       39182 |
| Product 997 |       37670 |
| Product 650 |       37580 |
| Product 316 |       37039 |
+-------------+-------------+
5 rows in set (3.41 sec)

 

실제로는 3.41초가 걸렸는데 cpu 성능이나 초당 데이터 row 읽는 속도에 따라 실행시간이 변할 가능성이 있을 것으로 보인다.

 

아래는 EXPLAIN 결과이다. 

mysql> EXPLAIN SELECT s.product_name as productName,
    -> SUM(s.sales_volume) AS totalVolume
    -> FROM statics s
    -> WHERE s.sales_date >= (NOW() - INTERVAL 3 DAY)
    -> GROUP BY s.product_name
    -> ORDER BY totalVolume DESC
    -> LIMIT 5;
+----+-------------+-------+------------+------+---------------+------+---------+------+----------+----------+----------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows     | filtered | Extra                                        |
+----+-------------+-------+------------+------+---------------+------+---------+------+----------+----------+----------------------------------------------+
|  1 | SIMPLE      | s     | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 12539241 |    33.33 | Using where; Using temporary; Using filesort |
+----+-------------+-------+------------+------+---------------+------+---------+------+----------+----------+----------------------------------------------+
1 row in set, 1 warning (0.00 sec)

 

EXPLAIN 컬럼의 의미는 다음과 같다. 

id 실행 순서 (서브쿼리, join 개수에 따라 값이 증가)
select_type 쿼리 유형 (단순, 복잡 등)
table 대상 테이블 ( 여기서는 statics )
partitions 사용된 파티션
type 데이터 접근 방식(all - 풀테이블스캔)
possible_keys 사용 가능한 인덱스(잠재적 인덱스 목록)
key 실제 사용된 인덱스
key_len 인덱스 길이(byte 단위 인덱스 길이)
ref 인덱스 비교 대상
rows 처리할 행 개수
filtered 필터링된 비율 (%) (WHERE 조건을 적용한 후 남아있는 행의 비율) 뭔가 이상함.
 Extra 추가 정보( Using temporary- 임시테이블 생성, Using filesort 정렬 별도 작업, Using where - where 필터링 사용)  index 적용으로 개선 가능한 정보들

 

2.성능 개선 정도 (인덱스 추가 후)

mysql에서 인덱스 추가 방법   CREATE INDEX 인덱스이름 ON 테이블명(컬럼영);

ex) CREATE INDEX idx_sales_date ON statics(sales_date);

 

mysql에서 인덱스 삭제 방법 ALTER TABLE 테이블명 DROP INDEX 인덱스명;

ex) ALTER TABLE statics DROP INDEX idx_sales_date_product_name;

 

1. 단일 인덱스만 적용시

 

1. salesDate 단일 인덱스 추가

mysql> CREATE INDEX idx_sales_date ON statics(sales_date);
Query OK, 0 rows affected (20.58 sec)
Records: 0  Duplicates: 0  Warnings: 0

 

인덱스 추가 후 오히려 시간이 늘어남

mysql> SELECT s.product_name as productName,
    -> SUM(s.sales_volume) AS totalVolume
    -> FROM statics s
    -> WHERE s.sales_date >= (NOW() - INTERVAL 3 DAY)
    -> GROUP BY s.product_name
    -> ORDER BY totalVolume DESC
    -> LIMIT 5;
+-------------+-------------+
| productName | totalVolume |
+-------------+-------------+
| Product 660 |       40412 |
| Product 629 |       39182 |
| Product 997 |       37670 |
| Product 650 |       37580 |
| Product 316 |       37039 |
+-------------+-------------+
5 rows in set (6.11 sec)

 

3.41 -> 6.11 sec

 

아래는 explain인데

rows가 19만개로 줄어들고 filtered가 100%로 늘어난 것이 성능 저하의 원인으로 보였다. 아니면 날짜를 기준(range)으로 365일 랜덤 생성한 날짜데이터를 인덱스로 만들어서  이럴지도 모르겠다.

 

+----+-------------+-------+------------+-------+----------------+----------------+---------+------+--------+----------+-------------------------------------------------------------------+
| id | select_type | table | partitions | type  | possible_keys  | key            | key_len | ref  | rows   | filtered | Extra                                                             |
+----+-------------+-------+------------+-------+----------------+----------------+---------+------+--------+----------+-------------------------------------------------------------------+
|  1 | SIMPLE      | s     | NULL       | range | idx_sales_date | idx_sales_date | 4       | NULL | 199244 |   100.00 | Using index condition; Using MRR; Using temporary; Using filesort |
+----+-------------+-------+------------+-------+----------------+----------------+---------+------+--------+----------+-------------------------------------------------------------------+

 

2. group by에만 index 적용

mysql> create index idx_sales_product_name on statics(product_name);
Query OK, 0 rows affected (35.01 sec)
Records: 0  Duplicates: 0  Warnings: 0

 

인덱스 생성 시간 35초

 

mysql> EXPLAIN SELECT s.product_name as productName,
    -> SUM(s.sales_volume) AS totalVolume
    -> FROM statics s
    -> WHERE s.sales_date >= (NOW() - INTERVAL 3 DAY)
    -> GROUP BY s.product_name
    -> ORDER BY totalVolume DESC
    -> LIMIT 5;
+----+-------------+-------+------------+-------+------------------------+------------------------+---------+------+----------+----------+----------------------------------------------+
| id | select_type | table | partitions | type  | possible_keys          | key                    | key_len | ref  | rows     | filtered | Extra                                        |
+----+-------------+-------+------------+-------+------------------------+------------------------+---------+------+----------+----------+----------------------------------------------+
|  1 | SIMPLE      | s     | NULL       | index | idx_sales_product_name | idx_sales_product_name | 768     | NULL | 12539241 |    33.33 | Using where; Using temporary; Using filesort |
+----+-------------+-------+------------+-------+------------------------+------------------------+---------+------+----------+----------+----------------------------------------------+
1 row in set, 1 warning (0.00 sec)

 

group by에만 index 적용시 원래 3일간 데이터는 103632건인데 3일간 데이터와 1000만건 중 prouduct_id 를 풀테이블스캔한 결과를 join 하는 것으로 보여 시간이 5분 이상 걸려 중간에 쿼리를 취소하였다. 아무 생각없이 단일인덱스를 걸면 안되는 이유를 알게 되었다.

 

마찬가지로 order by 또한 group index와 같은 O(N log N) 시간이 예상되므로 totalVolume에 대한 단일인덱스 적용은 넘어가도록 하겠다. 또한 totalVolume은 값을 index에서 알 수 없는 컬럼이므로 인덱스 적용을 안하는게 더 좋다는 판단을 하였다. 

 

2. 복합 인덱스 적용시 

앞서 단일 인덱스 적용시 where 절에 있는 날짜를 기준으로 했을때 3일치로 범위가 축소되어 그나마 6초라는 시간에 쿼리 결과가 나옴을 알 수 있었다. 따라서 복합 인덱스 적용시 where 절의 date부터 group by의 product_name 순으로 복합 인덱스를 적용하며 인덱스 적용 시간을 테스트 해보겠다.

 

mysql> create index idx_sales_date_product_name on statics(sales_date, product_name);
Query OK, 0 rows affected (37.54 sec)
Records: 0  Duplicates: 0  Warnings: 0

 

mysql> EXPLAIN SELECT s.product_name as productName,
    -> SUM(s.sales_volume) AS totalVolume
    -> FROM statics s
    -> WHERE s.sales_date >= (NOW() - INTERVAL 3 DAY)
    -> GROUP BY s.product_name
    -> ORDER BY totalVolume DESC
    -> LIMIT 5;
+----+-------------+-------+------------+-------+-----------------------------+-----------------------------+---------+------+--------+----------+-------------------------------------------------------------------+
| id | select_type | table | partitions | type  | possible_keys               | key                         | key_len | ref  | rows   | filtered | Extra                                                             |
+----+-------------+-------+------------+-------+-----------------------------+-----------------------------+---------+------+--------+----------+-------------------------------------------------------------------+
|  1 | SIMPLE      | s     | NULL       | range | idx_sales_date_product_name | idx_sales_date_product_name | 4       | NULL | 141408 |   100.00 | Using index condition; Using MRR; Using temporary; Using filesort |
+----+-------------+-------+------------+-------+-----------------------------+-----------------------------+---------+------+--------+----------+-------------------------------------------------------------------+
1 row in set, 1 warning (0.00 sec)
mysql> SELECT s.product_name as productName,
    -> SUM(s.sales_volume) AS totalVolume
    -> FROM statics s
    -> WHERE s.sales_date >= (NOW() - INTERVAL 3 DAY)
    -> GROUP BY s.product_name
    -> ORDER BY totalVolume DESC
    -> LIMIT 5;
+-------------+-------------+
| productName | totalVolume |
+-------------+-------------+
| Product 660 |       29684 |
| Product 629 |       28200 |
| Product 316 |       26824 |
| Product 283 |       26817 |
| Product 230 |       26491 |
+-------------+-------------+
5 rows in set (4.31 sec)

 

단일 인덱스 적용시보다 조금 빨라졌고 풀스캔과 비슷하거나 느린 결과가 나와서 아리송하다...(결과가 달라진 이유는 12시가 넘었기 때문..)

 

4. 결론

성능 최적화를 위해 기본적으로 알아야한다는 index에 대해 실습과 비교를 해봤다. 생각보다 쿼리 튜닝이라는 행위는 만만치 않은 일이었다. 아마 index에 대해 더 깊게 공부하고 mysql optimazer에 대해서도 알아보고.. sql이 쿼리를 어떻게 처리하는지까지 공부하면 쿼리 튜닝을 좀 할 수 있지 않을까 싶다. 

 

 

 

참조 : mysql index 생성/수정/삭제

https://dbaant.tistory.com/69#google_vignette