일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- 가장 긴 바이토닉 부분 수열 파이썬
- SQL SERVER MIGRATION
- 백준 11054.가장 긴 바이토닉 부분 수열
- 트리의 지름 파이썬
- 백준 2146 다리 만들기
- 백준 1238 파티 파이썬
- 반도체 설계 파이썬
- 백준 1034 램프 파이썬
- 프로그래머스 여행경로
- SWEA
- 가장 긴 팰린드롬 파이썬
- SQL SERVER 장비교체
- 램프 파이썬
- 프로그래머스 베스트앨범
- 프로그래머스 순위 파이썬
- 백준 2352 반도체 설계 파이썬
- 게임 개발 파이썬
- 다중 컬럼 NOT IN
- 등굣길 파이썬
- 다리 만들기 파이썬
- 순위 파이썬
- 역사 파이썬
- 백준 1516 게임 개발
- 프로그래머스 등굣길
- 백준 1167 트리의 지름 파이썬
- 백준 1043 거짓말 파이썬
- 백준 1613 역사
- 프로그래머스 순위
- 베스트앨범 파이썬
- 프로그래머스 가장 긴 팰린드롬
- Today
- Total
공부, 기록
[MySQL] MySQL group by 개선 본문
개요
서비스 관련 쿼리를 개선하면서 MySQL에만 사용되는 기능인 index for group by를 적용하게 되어 작성합니다.
사용되는 쿼리와 테이블은 다음과 같은 형식입니다
테이블
CREATE TABLE `tableA` (
`id` int NOT NULL AUTO_INCREMENT,
`strDepth1Category` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL,
`strDepth2Category` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL,
`strDepth3Category` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL,
`strServiceName` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL,
`dtCreatedAt` datetime NOT NULL,
`dtUpdatedAt` datetime NOT NULL,
PRIMARY KEY (`id`),
KEY `table_index_01` (`strServiceName`,`strDepth1Category`,`strDepth2Category`,`strDepth3Category`)
) ENGINE=InnoDB AUTO_INCREMENT=62145281 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
쿼리
SELECT `strDepth2Category` AS `strDepth2Category`
FROM `tableA`
WHERE `strServiceName` = 'abc' AND `strDepth1Category` IN ('abc', 'def')
GROUP BY `strDepth2Category`;
가장 깊은 depth 의 category 유니크 값을 조회하기 위한 내용이며 상단의 조건들은 필수 값으로 입력을 받도록 설계하였습니다.
table_index_01 (`strServiceName`,`strDepth1Category`,`strDepth2Category`,`strDepth3Category`) 형태의 인덱스로 where 구문과 group by 구문에서 인덱스 사용 용도를 충족한다고 판단하였고 위 쿼리는 다음과 같은 실행계획으로 풀이됩니다.
쿼리 개선 이전
IN안에 하나의 조건만 들어올 경우
-> Group (no aggregates) (cost=47.12 rows=193) (actual time=0.170..0.171 rows=1 loops=1)
-> Covering index lookup on tableA using table_index_01(strServiceName='abc', strDepth1Category='abc') (cost=27.82 rows=193) (actual time=0.031..0.147 rows=193 loops=1)
-> Group (no aggregates) (cost=155307.21 rows=639014) (actual time=144.157..282.805 rows=5 loops=1)
-> Covering index lookup on tableA using table_index_01 (strServiceName='abc', strDepth1Category='def')
(cost=91405.81 rows=639014) (actual time=0.028..240.382 rows=443627 loops=1)
IN안에 여러 조건이 들어올 경우
-> Table scan on <temporary> (cost=0.01..7987.96 rows=638837) (actual time=0.003..0.004 rows=5 loops=1)
-> Temporary table with deduplication (cost=219154.33..227142.28 rows=638837) (actual time=574.515..574.516 rows=5 loops=1)
-> Filter: ((tableA.strServiceName = 'abc') and (tableA.strDepth1Category in ('abc','def'))) (cost=155270.61 rows=638837) (actual time=0.033..419.354 rows=443810 loops=1)
-> Covering index range scan on tableA using table_index_01over (strServiceName = 'abc' AND strDepth1Category = 'def') OR (strServiceName = 'abc' AND strDepth1Category = 'abc') (cost=155270.61 rows=638837) (actual time=0.031..269.346 rows=443810 loops=1)
개선 방안
커버링 인덱스를 사용하여 조회가 되지만 의도했던 부분은 인덱스의 정렬된 데이터에서 group by에서 사용되어야 하는 하나의 데이터를 읽으면 그 다음 데이터로 바로 넘어가는 상황을 기대했습니다. 그런 것에 비하여 rows 조회 양이 많았고 지연 시간도 늘어납니다.
관련하여 group by index 사용에 대하여 최적화를 좀 더 찾아보았을 때 관련 내용을 확인하였습니다.
https://dev.mysql.com/doc/refman/8.0/en/group-by-optimization.html 해당 페이지를 확인하면 group by 를 성능 개선하는데 2가지 방안이 있습니다.
1 Loose Index scan : 특정 조건을 만족하면 데이터의 일부만 읽고 스킵하여 집계를 합니다.
2 Tight Index Scan : 쿼리 조건에 따라 인덱스 전체 스캔 또는 범위 스캔이 발생합니다. 임시 테이블 생성을 피할 수 있으며 범위 조건을 충족하는 모든 키를 읽은 후 집계를 합니다.
t1 테이블에 (c1,c2,c3) 의 복합 인덱스가 있다는 가정하에
1 Loose Index scan은 다음과 같은 쿼리들에서 적용이 가능합니다.
SELECT c1, c2 FROM t1 GROUP BY c1, c2;
SELECT DISTINCT c1, c2 FROM t1;
SELECT c1, MIN(c2) FROM t1 GROUP BY c1;
SELECT c1, c2 FROM t1 WHERE c1 < const GROUP BY c1, c2;
SELECT MAX(c3), MIN(c3), c1, c2 FROM t1 WHERE c2 > const GROUP BY c1, c2;
SELECT c2 FROM t1 WHERE c1 < const GROUP BY c1, c2;
SELECT c1, c2 FROM t1 WHERE c3 = const GROUP BY c1, c2;
SELECT c1, c3 FROM t1 WHERE c3 = cosnt GROUP BY c1, c2;
SELECT COUNT(DISTINCT c1), SUM(DISTINCT c1) FROM t1;
SELECT COUNT(DISTINCT c1, c2), COUNT(DISTINCT c2, c1) FROM t1;
GROUP BY에 적용이 인덱스 순서에 맞게 컬럼이 작성되어 있습니다.
AVG(DISTINCT COLUMN), SUM(DISTINCT COLUMN), COUNT(DISTINCT COLUMN1, COLUMN2 ...) 도 사용이 가능하며 count는 두개 이상의 컬럼을 사용할 수 있지만 AVG, SUM은 하나의 컬럼을 사용해야 합니다.
2 Tight Index scan은 다음과 같은 쿼리들에서 적용이 가능합니다.
SELECT c1, c2, c3 FROM t1 WHERE c2 = 'a' GROUP BY c1, c3;
SELECT c1, c2, c3 FROM t1 WHERE c1 = 'a' GROUP BY c2, c3;
WHERE, GROUP BY 를 합쳐서 순서에 맞게 작성이 되어 있습니다.
개선
Loose Index Scan을 적용할 수 있는 상황으로 판단되어 쿼리를 수정하였습니다.
SELECT distinct `strDepth2Category` AS `strDepth2Category`
FROM `tableA`
WHERE `strServiceName` = 'abc' AND `strDepth1Category`
IN ('def')
GROUP BY strServiceName, strDepth1Category, `strDepth2Category`
위와 같은 쿼리 형태로 수정이 되었으며 개선 이후 성능이 많이 개선되는 것을 확인하였습니다. actual 기준 574.516 → 0.145 (ms)
개선된 실행 계획에는 다음과 같은 항목이 표시됩니다. using index for group by , index skip scan
쿼리 개선 이후
IN안에 하나의 조건만 들어올 경우
-> Sort with duplicate removal: tableA.strDepth2Category (actual time=0.040..0.040 rows=0 loops=1)
-> Table scan on <temporary> (cost=2.51..2.51 rows=1) (actual time=0.002..0.002 rows=0 loops=1)
-> Temporary table with deduplication (cost=2.97..2.97 rows=1) (actual time=0.035..0.035 rows=0 loops=1)
-> Covering index lookup on tableA using table_index_01(strServiceName='abc', strDepth1Category='abc'\'') (cost=0.36 rows=1) (actual time=0.020..0.020 rows=0 loops=1)
-> Sort with duplicate removal: tableA .strDepth2Category (actual time=0.131..0.131 rows=5 loops=1)
-> Table scan on <temporary> (cost=0.01..32.70 rows=2416) (actual time=0.002..0.003 rows=5 loops=1)
-> Temporary table with deduplication (cost=2433.91..2466.59 rows=2416) (actual time=0.113..0.114 rows=5 loops=1)
-> Filter: (( tableA .strServiceName = 'abc') and ( tableA .strDepth1Category = 'def')) (cost=2192.29 rows=2416) (actual time=0.041..0.086 rows=5 loops=1)
-> Covering index skip scan for deduplication on tableA using table_index_01over (strServiceName = 'abc' AND strDepth1Category = 'def') (cost=2192.29 rows=2416) (actual time=0.038..0.082 rows=5 loops=1)
IN안에 여러 조건이 들어올 경우
-> Sort with duplicate removal: tableA .strDepth2Category (actual time=0.145..0.145 rows=5 loops=1)
-> Table scan on <temporary> (cost=0.01..32.54 rows=2403) (actual time=0.002..0.003 rows=6 loops=1)
-> Temporary table with deduplication (cost=2422.69..2455.22 rows=2403) (actual time=0.129..0.131 rows=6 loops=1)
-> Filter: ((tableA.strServiceName = 'abc') and ( tableA.strDepth1Category in ('abc','def'))) (cost=2182.38 rows=2403) (actual time=0.041..0.101 rows=6 loops=1)
-> Covering index skip scan for deduplication on tableA using table_index_01 over (strServiceName = 'abc' AND strDepth1Category = 'def') OR (strServiceName = 'abc' AND strDepth1Category = 'abc') (cost=2182.38 rows=2403) (actual time=0.039..0.097 rows=6 loops=1)
'공부 > DATABASE' 카테고리의 다른 글
PostgreSQL Any 연산자 (0) | 2025.03.23 |
---|---|
[Aurora] Commit 지연 지표 (0) | 2024.12.06 |
[AWS] Aurora, ElastiCache 타입 변경 스크립트 (0) | 2024.11.21 |
SQL Server와 MySQL만 하다가 PostgreSQL을 공부하니.. (0) | 2024.10.15 |
Valkey의 멀티스레드 아키텍처 (0) | 2024.10.12 |