SlideShare a Scribd company logo
MYSQL - 실행계획
김천규
1
SQL
절차(How)가 아닌 대상(What)을 기술하는 언어
HOW를 의식하는 것이 SQL 튜닝의 첫 걸음
=> DB의 구성요소, 역할, 흐름을 알아야 한다.
2
DBMS 아키텍처
3
MYSQL 구조
4
Optimizer(옵티마이저)
5
규칙기반 옵티마이저
6
비용기반 옵티마이저
7
옵티마이저 입장에서는 전부 다른 쿼리 (파서)
SELECT * FROM 급여;
SELECt * FROM 급여;
SELECT * fROM 급여;
8
인덱스(Index)
9
인덱스를 타면 이렇게 되요
10
Index는 일반적으로 B+tree 구조
11
Table Scan
12
JOIN
13
JOIN
14
속도로는 R(A) JOIN R(B) != R(A) JOIN R(B)
15
실행계획 JOIN
1. Nested Loop
2. Sort Merge Join
3. Hash Join
16
Nested Loop
17
Sort Merge Join
18
Hash Join
19
SQL 튜닝을 한다는 거는
• 옵티마이저가 최적의 비용으로 수행하게 잘 유도 하는 것
• 테이블 스캔 횟수는 최소화
20
실무적인 SQL 튜닝
1. SQL문 실행결과 & 현황 파악 결과 및 소요 시간 확인
조인/서브쿼리 구조
동등/범위 조건
2. 가시적 & 비가시적 가시적 테이블의 데이터 건수
SELECT절 컬럼 분석
조건절 컬럼 분석
그루핑/정렬 컬럼
비가시적 실행계획
인덱스 현황
데이터 변경 추이
*업무적 특성*
3. 튜닝 방향 판단 & 개선/적용
21
실행 계획 판단 기준
일반적으로 그렇다는 의미이며
특이케이스가 있을 수 있음
22
기본키를 변형하는 나쁜 SQL – ASIS (4.2.1)
SELECT *
FROM 사원
WHERE SUBSTRING(사원번호,1,4) = 1100
AND LENGTH(사원번호) = 5;
* 결과
+----------+------------+-------------+-------------+------+------------+
| 사원번호 | 생년월일 | 이름 | 성 | 성별 | 입사일자 |
+----------+------------+-------------+-------------+------+------------+
| 11000 | 1960-09-12 | Alain | Bonifati | M | 1988-08-20 |
| 11001 | 1956-04-16 | Baziley | Buchter | F | 1987-02-23 |
| 11002 | 1952-02-26 | Bluma | Ulupinar | M | 1996-12-23 |
| 11003 | 1960-11-13 | Mariangiola | Gulla | M | 1987-05-24 |
| 11004 | 1954-08-05 | JoAnna | Decleir | F | 1992-01-19 |
| 11005 | 1958-03-12 | Byong | Douceur | F | 1986-07-27 |
| 11006 | 1962-12-26 | Christoper | Butterworth | F | 1989-08-02 |
| 11007 | 1962-03-16 | Olivera | Maccarone | M | 1991-04-11 |
| 11008 | 1962-07-11 | Gennady | Menhoudj | M | 1988-09-18 |
| 11009 | 1954-08-30 | Alper | Axelband | F | 1986-09-09 |
+----------+------------+-------------+-------------+------+------------+
10 rows in set (0.21 sec)
====================================================================================
23
사원번호가 1100으로 시작하면서 사원번호가 5자리인 사원 정보
기본키를 변형하는 나쁜 SQL – 실행계획 (4.2.1)
EXPLAIN
SELECT *
FROM 사원
WHERE SUBSTRING(사원번호,1,4) = 1100
AND LENGTH(사원번호) = 5;
* 결과
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+------
----+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered
| Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+------
----+-------------+
| 1 | SIMPLE | 사원 | NULL | ALL | NULL | NULL | NULL | NULL | 299157 | 100.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+------
----+-------------+
1 row in set, 1 warning (0.00 sec)
24
기본키를 변형하는 나쁜 SQL – TOBE (4.2.1)
SELECT *
FROM 사원
WHERE 사원번호 BETWEEN 11000 AND 11009;
* 결과
+----------+------------+-------------+-------------+------+------------+
| 사원번호 | 생년월일 | 이름 | 성 | 성별 | 입사일자 |
+----------+------------+-------------+-------------+------+------------+
| 11000 | 1960-09-12 | Alain | Bonifati | M | 1988-08-20 |
| 11001 | 1956-04-16 | Baziley | Buchter | F | 1987-02-23 |
| 11002 | 1952-02-26 | Bluma | Ulupinar | M | 1996-12-23 |
| 11003 | 1960-11-13 | Mariangiola | Gulla | M | 1987-05-24 |
| 11004 | 1954-08-05 | JoAnna | Decleir | F | 1992-01-19 |
| 11005 | 1958-03-12 | Byong | Douceur | F | 1986-07-27 |
| 11006 | 1962-12-26 | Christoper | Butterworth | F | 1989-08-02 |
| 11007 | 1962-03-16 | Olivera | Maccarone | M | 1991-04-11 |
| 11008 | 1962-07-11 | Gennady | Menhoudj | M | 1988-09-18 |
| 11009 | 1954-08-30 | Alper | Axelband | F | 1986-09-09 |
+----------+------------+-------------+-------------+------+------------+
10 rows in set (0.00 sec)
25
형변환으로 인덱스를 활용하지 못하는 나쁜 SQL – ASIS (4.2.3)
* SQL
SELECT COUNT(1)
FROM 급여
WHERE 사용여부 = 1;
* 결과
+----------+
| COUNT(1) |
+----------+
| 42842 |
+----------+
1 row in set (0.15 sec)
26
유효한 급여의 전체 개수 조회
형변환으로 인덱스를 활용하지 못하는 나쁜 SQL – ASIS (4.2.3)
desc 급여;
* 결과
+----------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------+---------+------+-----+---------+-------+
| 사원번호 | int | NO | PRI | NULL | |
| 연봉 | int | NO | | NULL | |
| 시작일자 | date | NO | PRI | NULL | |
| 종료일자 | date | NO | | NULL | |
| 사용여부 | char(1) | YES | MUL | | |
+----------+---------+------+-----+---------+-------+
5 rows in set (0.01 sec)
27
형변환으로 인덱스를 활용하지 못하는 나쁜 SQL – TOBE (4.2.3)
====================================================================================
* SQL
SELECT COUNT(1)
FROM 급여
WHERE 사용여부 = '1';
* 결과
+----------+
| COUNT(1) |
+----------+
| 42842 |
+----------+
1 row in set (0.01 sec)
28
열을 결합하여 사용하는 나쁜 SQL – ASIS, 실행계획 (4.2.4)
* SQL
EXPLAIN
SELECT *
FROM 사원
WHERE CONCAT(성별,' ',성) = 'M Radwan';
* 결과
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+------
----+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered
| Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+------
----+-------------+
| 1 | SIMPLE | 사원 | NULL | ALL | NULL | NULL | NULL | NULL | 299157 | 100.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+------
----+-------------+
1 row in set, 1 warning (0.00 sec)
29
열을 결합하여 사용하는 나쁜 SQL – TOBE, 실행계획 (4.2.4)
* SQL
EXPLAIN
SELECT *
FROM 사원
WHERE 성별 = 'M'
AND 성 = 'Radwan';
* 결과
+----+-------------+-------+------------+------+---------------+-----------+---------+-------------+------+----------+----
---+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+-----------+---------+-------------+------+----------+----
---+
| 1 | SIMPLE | 사원 | NULL | ref | I_성별_성 | I_성별_성 | 51 | const,const | 102 | 100.00 | NULL |
+----+-------------+-------+------------+------+---------------+-----------+---------+-------------+------+----------+----
---+
1 row in set, 1 warning (0.00 sec)
30
인덱스 고려 없이 열을 사용하는 SQL – ASIS (4.2.7)
* SQL
SELECT 성, 성별, COUNT(1) as 카운트
FROM 사원
GROUP BY 성, 성별;
* 결과
+------------------+------+--------+
| 성 | 성별 | 카운트 |
+------------------+------+--------+
| Aamodt | M | 120 |
| Acton | M | 108 |
| Adachi | M | 140 |
... 중략 ...
| Zwicker | F | 65 |
| Zyda | F | 72 |
| Zykh | F | 61 |
+------------------+------+--------+
3274 rows in set (0.43 sec)
31
인덱스 고려 없이 열을 사용하는 SQL – 인덱스 조회 (4.2.7)
* SQL
show index from 사원;
* 결과
+-------+------------+------------+--------------+-------------+-----------+-------------
+----------+--------+------+------------+---------+---------------+---------+------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality |
Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
+-------+------------+------------+--------------+-------------+-----------+-------------
+----------+--------+------+------------+---------+---------------+---------+------------+
| 사원 | 0 | PRIMARY | 1 | 사원번호 | A | 299157 | NULL | NULL | | BTREE | | | YES | NULL |
| 사원 | 1 | I_입사일자 | 1 | 입사일자 | A | 4612 | NULL | NULL | | BTREE | | | YES | NULL |
| 사원 | 1 | I_성별_성 | 1 | 성별 | A | 1 | NULL | NULL | | BTREE | | | YES | NULL |
| 사원 | 1 | I_성별_성 | 2 | 성 | A | 3257 | NULL | NULL | | BTREE | | | YES | NULL |
+-------+------------+------------+--------------+-------------+-----------+-------------
+----------+--------+------+------------+---------+---------------+---------+------------+
4 rows in set (0.00 sec)
32
인덱스 고려 없이 열을 사용하는 SQL – TOBE (4.2.7)
* SQL
SELECT 성, 성별, COUNT(1) as 카운트
FROM 사원
GROUP BY 성별, 성;
* 결과
+------------------+------+--------+
| 성 | 성별 | 카운트 |
+------------------+------+--------+
| Aamodt | M | 120 |
| Acton | M | 108 |
| Adachi | M | 140 |
... 중략 ...
| Zwicker | F | 65 |
| Zyda | F | 72 |
| Zykh | F | 61 |
+------------------+------+--------+
3274 rows in set (0.04 sec)
33
습관적으로 중복을 제거하는 나쁜 SQL – ASIS, 실행계획 (4.2.5)
EXPLAIN
SELECT DISTINCT 사원.사원번호, 이름, 성, 부서번호
FROM 사원
JOIN 부서관리자
ON (사원.사원번호 = 부서관리자. 사원번호);
* 결과
+----+-------------+------------+------------+--------+---------------+------------+---------+----------------------------
+------+----------+------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+--------+---------------+------------+---------+----------------------------
+------+----------+------------------------------+
| 1 | SIMPLE | 부서관리자 | NULL | index | PRIMARY | I_부서번호 | 12 | NULL | 24 | 100.00 | Using index; Using temporary |
| 1 | SIMPLE | 사원 | NULL | eq_ref | PRIMARY | PRIMARY | 4 | tuning.부서관리자.사원번호 | 1 | 100.00 | NULL |
+----+-------------+------------+------------+--------+---------------+------------+---------+----------------------------
+------+----------+------------------------------+
2 rows in set, 1 warning (0.00 sec)
34
습관적으로 중복을 제거하는 나쁜 SQL – TOBE, 실행계획 (4.2.5)
EXPLAIN
SELECT 사원.사원번호, 이름, 성, 부서번호
FROM 사원
JOIN 부서관리자
ON (사원.사원번호 = 부서관리자. 사원번호);
* 결과
+----+-------------+------------+------------+--------+---------------+------------+---------+----------------------------
+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+--------+---------------+------------+---------+----------------------------
+------+----------+-------------+
| 1 | SIMPLE | 부서관리자 | NULL | index | PRIMARY | I_부서번호 | 12 | NULL | 24 | 100.00 | Using index |
| 1 | SIMPLE | 사원 | NULL | eq_ref | PRIMARY | PRIMARY | 4 | tuning.부서관리자.사원번호 | 1 | 100.00 | NULL |
+----+-------------+------------+------------+--------+---------------+------------+---------+----------------------------
+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)
35
다수의 쿼리를 UNION 연산자로 합치는 나쁜 SQL – ASIS, 실행계획
(4.2.6)
EXPLAIN
SELECT 'M' AS 성별, 사원번호
FROM 사원
WHERE 성별 = 'M'
AND 성 ='Baba'
UNION
SELECT 'F', 사원번호
FROM 사원
WHERE 성별 = 'F'
AND 성 = 'Baba';
* 결과
+------+--------------+------------+------------+------+---------------+-----------+---------+-------------+------+----------+-----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+------+--------------+------------+------------+------+---------------+-----------+---------+-------------+------+----------+-----------------+
| 1 | PRIMARY | 사원 | NULL | ref | I_성별_성 | I_성별_성 | 51 | const,const | 135 | 100.00 | Using index |
| 2 | UNION | 사원 | NULL | ref | I_성별_성 | I_성별_성 | 51 | const,const | 91 | 100.00 | Using index |
| NULL | UNION RESULT | <union1,2> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary |
+------+--------------+------------+------------+------+---------------+-----------+---------+-------------+------+----------+-----------------+
3 rows in set, 1 warning (0.00 sec)
36
다수의 쿼리를 UNION 연산자로 합치는 나쁜 SQL – TOBE, 실행계획
(4.2.6)
EXPLAIN
SELECT 'M' as 성별, 사원번호
FROM 사원
WHERE 성별 = 'M'
AND 성 ='Baba'
UNION ALL
SELECT 'F' as 성별, 사원번호
FROM 사원
WHERE 성별 = 'F'
AND 성 ='Baba';
* 결과
+----+-------------+-------+------------+------+---------------+-----------+---------+-------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+-----------+---------+-------------+------+----------+-------------+
| 1 | PRIMARY | 사원 | NULL | ref | I_성별_성 | I_성별_성 | 51 | const,const | 135 | 100.00 | Using index |
| 2 | UNION | 사원 | NULL | ref | I_성별_성 | I_성별_성 | 51 | const,const | 91 | 100.00 | Using index |
+----+-------------+-------+------------+------+---------------+-----------+---------+-------------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)
37
불필요한 조인을 수행하는 나쁜 SQL – ASIS, 실행계획 (4.3.3)
* SQL
EXPLAIN
SELECT COUNT(DISTINCT 사원.사원번호) as 데이터건수
FROM 사원,
( SELECT 사원번호
FROM 사원출입기록 기록
WHERE 출입문 = 'A'
) 기록
WHERE 사원.사원번호 = 기록.사원번호;
* 결과
+----+-------------+-------+------------+--------+---------------+----------+---------+----------------------+--------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+--------+---------------+----------+---------+----------------------+--------+----------+-------------+
| 1 | SIMPLE | 기록 | NULL | ref | I_출입문 | I_출입문 | 4 | const | 329467 | 100.00 | Using index |
| 1 | SIMPLE | 사원 | NULL | eq_ref | PRIMARY | PRIMARY | 4 | tuning.기록.사원번호 | 1 | 100.00 | Using index |
+----+-------------+-------+------------+--------+---------------+----------+---------+----------------------+--------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)
38
불필요한 조인을 수행하는 나쁜 SQL – TOBE, 실행계획 (4.3.3)
• * SQL
• EXPLAIN
• SELECT COUNT(1) as 데이터건수
• FROM 사원
• WHERE EXISTS (SELECT 1
• FROM 사원출입기록 기록
• WHERE 출입문 = 'A'
• AND 기록.사원번호 = 사원.사원번호);
• * 결과
• +----+--------------+-------------+------------+--------+---------------------+---------------------+---------+----------------------+--------+-----
-----+--------------------------+
• | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
• +----+--------------+-------------+------------+--------+---------------------+---------------------+---------+----------------------+--------+-----
-----+--------------------------+
• | 1 | SIMPLE | 사원 | NULL | index | PRIMARY | I_입사일자 | 3 | NULL | 299157 | 100.00 | Using where; Using index |
• | 1 | SIMPLE | <subquery2> | NULL | eq_ref | <auto_distinct_key> | <auto_distinct_key> | 4 | tuning.사원.사원번호 | 1 | 100.00 | NULL |
• | 2 | MATERIALIZED | 기록 | NULL | ref | I_출입문 | I_출입문 | 4 | const | 329467 | 100.00 | Using index |
• +----+--------------+-------------+------------+--------+---------------------+---------------------+---------+----------------------+--------+-----
-----+--------------------------+
• 3 rows in set, 2 warnings (0.00 sec)
39
Reference
• 업무에 바로 쓰는 SQL 튜닝
• SQL 레벨업
• Effective SQL
• Real Mysql
40
감사합니다.
41

More Related Content

PPTX
효율적인Sql작성방법 4주차
PDF
#6.SQL초보에서 Schema Objects까지_구로IT학원/오라클교육/자바교육/국비지원/재직자환급교육/내일배움카드/사업주위탁
PDF
#25.SQL초보에서 Schema Objects까지_구로IT학원/국비지원IT학원추천/구로디지털단지IT학원/재직자환급교육추천
PPTX
효율적인Sql작성방법 3주차
PPTX
효율적인 SQL 작성방법 1주차
PPTX
(SQL초보자를 위한, 쿼리최적화 for SQL튜닝)SQL쿼리작성Tip,최적화팁,최적화된SQL작성방법교육
PPTX
181215 MS SQL로 알아보는 데이터베이스
PPTX
효율적인Sql작성방법 2주차
효율적인Sql작성방법 4주차
#6.SQL초보에서 Schema Objects까지_구로IT학원/오라클교육/자바교육/국비지원/재직자환급교육/내일배움카드/사업주위탁
#25.SQL초보에서 Schema Objects까지_구로IT학원/국비지원IT학원추천/구로디지털단지IT학원/재직자환급교육추천
효율적인Sql작성방법 3주차
효율적인 SQL 작성방법 1주차
(SQL초보자를 위한, 쿼리최적화 for SQL튜닝)SQL쿼리작성Tip,최적화팁,최적화된SQL작성방법교육
181215 MS SQL로 알아보는 데이터베이스
효율적인Sql작성방법 2주차

Similar to MySQL 실행계획 (20)

PDF
(재직자환급교육/사업주위탁/IT실무교육/구로IT학원/오라클교육/SQL기초강좌/IT강좌)#10.SQL초보에서 Schema Objects까지
PPTX
3.4 실행계획 SQL 연산 (Hash Anti-Join)
PDF
#1.SQL초보에서 Schema Objects까지(SQL학원/오라클학원/IT실무교육학원/재직자/실업자교육학원추천)
PDF
SQL 튜닝에 Dictionary View 활용하기 Part2_Wh oracle
PDF
Database 튜닝 교육 110124
PPTX
03주차 ddl- table을 만들자
PDF
TABLE ACCESS 패턴을 이용한 SQL 튜닝_Wh oracle
PPT
ecdevday8 웹개발자의 약한고리 SQL 뛰어넘기
DOCX
MySQL_SQL_Tunning_v0.1.3.docx
PPT
활용예시를 통한 Sql server 2012의 향상된 프로그래밍 기능 엿보기
PPTX
3.3 실행계획 SQL 연산 (Count,Count Stopkey/Filter)
PDF
오라클강의/자바강의/닷넷강의/자마린교육/아두이노교육학원추천_#13.SQL초보에서 Schema Objects까지
PDF
SQL Server Access Patterns
PPTX
Sql기초강좌2_SET AUTOTRACE_SQL교육
PDF
성능 좋은 SQL 작성법
PDF
DB Basic _ 성능개선
DOC
제1회 Tech Net Sql Server 2005 T Sql Enhancements
PPTX
개발자들이 흔히 실수하는 SQL 7가지
PPTX
3.2 실행계획 sql 연산 (concatenation)
(재직자환급교육/사업주위탁/IT실무교육/구로IT학원/오라클교육/SQL기초강좌/IT강좌)#10.SQL초보에서 Schema Objects까지
3.4 실행계획 SQL 연산 (Hash Anti-Join)
#1.SQL초보에서 Schema Objects까지(SQL학원/오라클학원/IT실무교육학원/재직자/실업자교육학원추천)
SQL 튜닝에 Dictionary View 활용하기 Part2_Wh oracle
Database 튜닝 교육 110124
03주차 ddl- table을 만들자
TABLE ACCESS 패턴을 이용한 SQL 튜닝_Wh oracle
ecdevday8 웹개발자의 약한고리 SQL 뛰어넘기
MySQL_SQL_Tunning_v0.1.3.docx
활용예시를 통한 Sql server 2012의 향상된 프로그래밍 기능 엿보기
3.3 실행계획 SQL 연산 (Count,Count Stopkey/Filter)
오라클강의/자바강의/닷넷강의/자마린교육/아두이노교육학원추천_#13.SQL초보에서 Schema Objects까지
SQL Server Access Patterns
Sql기초강좌2_SET AUTOTRACE_SQL교육
성능 좋은 SQL 작성법
DB Basic _ 성능개선
제1회 Tech Net Sql Server 2005 T Sql Enhancements
개발자들이 흔히 실수하는 SQL 7가지
3.2 실행계획 sql 연산 (concatenation)
Ad

More from Wonjun Hwang (20)

PDF
20250802 _ TOSS MAKERS CONFERENCE 25.pdf
PDF
20250725_Kit-Works Team Study_GOOGLE I_O 2025.pdf
PPTX
20250725_Kit-Works Team Study_Spring AI.pptx
PPTX
20250718_Next.js를 떠나는 개발자들: 비판과 대안 프레임워크 분석.pptx
PDF
Kit-Works Team Study_20250718_자바의-enum.pdf
PDF
Kit-Works Team Study_Vibe Coding 도전해보기.pdf
PPTX
Kit-Works Team Study_브라우저 검색 과정_20250704_손문수.pptx
PDF
Kit-Works Team Study_20250627_한달만에만든사내서비스키링(양다윗).pdf
PDF
Kit-Works Team Study_20250627_기술 부채_김경수.pdf
PPTX
20250530_Kit-Works Team Study_결제, 너 믿어도 될까.pptx
PPTX
20250620_Kit-Works Team Study_jspecify.pptx
PDF
20250523_Kit-Works Team Study_윤정빈_놓치고 있던 웹 접근성.pdf
PPTX
20250523_Kit-Works Team Study_Exception.pptx
PPTX
Kit-Works Team Study-20250517_uuid_김한나.pptx
PDF
Kit-Works Team Study_20240517_장현정_Claude에서MCP사용해보기.pdf
PDF
Kit-Works Team Study_아직도 Dockefile.pdf_김성호
PDF
Kit-Works Team Study_팀스터디_김한솔_nuqs_20250509.pdf
PDF
Kit-Works Team Study_공허참,부존재증명,트러블슈팅.pdf
PPTX
Kit-Works Team Study_20250502_Code_Formatting_유현주.pptx
PDF
20250425_ Kit-Works Team Study_Java의 문자열 클래스.pdf
20250802 _ TOSS MAKERS CONFERENCE 25.pdf
20250725_Kit-Works Team Study_GOOGLE I_O 2025.pdf
20250725_Kit-Works Team Study_Spring AI.pptx
20250718_Next.js를 떠나는 개발자들: 비판과 대안 프레임워크 분석.pptx
Kit-Works Team Study_20250718_자바의-enum.pdf
Kit-Works Team Study_Vibe Coding 도전해보기.pdf
Kit-Works Team Study_브라우저 검색 과정_20250704_손문수.pptx
Kit-Works Team Study_20250627_한달만에만든사내서비스키링(양다윗).pdf
Kit-Works Team Study_20250627_기술 부채_김경수.pdf
20250530_Kit-Works Team Study_결제, 너 믿어도 될까.pptx
20250620_Kit-Works Team Study_jspecify.pptx
20250523_Kit-Works Team Study_윤정빈_놓치고 있던 웹 접근성.pdf
20250523_Kit-Works Team Study_Exception.pptx
Kit-Works Team Study-20250517_uuid_김한나.pptx
Kit-Works Team Study_20240517_장현정_Claude에서MCP사용해보기.pdf
Kit-Works Team Study_아직도 Dockefile.pdf_김성호
Kit-Works Team Study_팀스터디_김한솔_nuqs_20250509.pdf
Kit-Works Team Study_공허참,부존재증명,트러블슈팅.pdf
Kit-Works Team Study_20250502_Code_Formatting_유현주.pptx
20250425_ Kit-Works Team Study_Java의 문자열 클래스.pdf
Ad

MySQL 실행계획

  • 2. SQL 절차(How)가 아닌 대상(What)을 기술하는 언어 HOW를 의식하는 것이 SQL 튜닝의 첫 걸음 => DB의 구성요소, 역할, 흐름을 알아야 한다. 2
  • 8. 옵티마이저 입장에서는 전부 다른 쿼리 (파서) SELECT * FROM 급여; SELECt * FROM 급여; SELECT * fROM 급여; 8
  • 15. 속도로는 R(A) JOIN R(B) != R(A) JOIN R(B) 15
  • 16. 실행계획 JOIN 1. Nested Loop 2. Sort Merge Join 3. Hash Join 16
  • 20. SQL 튜닝을 한다는 거는 • 옵티마이저가 최적의 비용으로 수행하게 잘 유도 하는 것 • 테이블 스캔 횟수는 최소화 20
  • 21. 실무적인 SQL 튜닝 1. SQL문 실행결과 & 현황 파악 결과 및 소요 시간 확인 조인/서브쿼리 구조 동등/범위 조건 2. 가시적 & 비가시적 가시적 테이블의 데이터 건수 SELECT절 컬럼 분석 조건절 컬럼 분석 그루핑/정렬 컬럼 비가시적 실행계획 인덱스 현황 데이터 변경 추이 *업무적 특성* 3. 튜닝 방향 판단 & 개선/적용 21
  • 22. 실행 계획 판단 기준 일반적으로 그렇다는 의미이며 특이케이스가 있을 수 있음 22
  • 23. 기본키를 변형하는 나쁜 SQL – ASIS (4.2.1) SELECT * FROM 사원 WHERE SUBSTRING(사원번호,1,4) = 1100 AND LENGTH(사원번호) = 5; * 결과 +----------+------------+-------------+-------------+------+------------+ | 사원번호 | 생년월일 | 이름 | 성 | 성별 | 입사일자 | +----------+------------+-------------+-------------+------+------------+ | 11000 | 1960-09-12 | Alain | Bonifati | M | 1988-08-20 | | 11001 | 1956-04-16 | Baziley | Buchter | F | 1987-02-23 | | 11002 | 1952-02-26 | Bluma | Ulupinar | M | 1996-12-23 | | 11003 | 1960-11-13 | Mariangiola | Gulla | M | 1987-05-24 | | 11004 | 1954-08-05 | JoAnna | Decleir | F | 1992-01-19 | | 11005 | 1958-03-12 | Byong | Douceur | F | 1986-07-27 | | 11006 | 1962-12-26 | Christoper | Butterworth | F | 1989-08-02 | | 11007 | 1962-03-16 | Olivera | Maccarone | M | 1991-04-11 | | 11008 | 1962-07-11 | Gennady | Menhoudj | M | 1988-09-18 | | 11009 | 1954-08-30 | Alper | Axelband | F | 1986-09-09 | +----------+------------+-------------+-------------+------+------------+ 10 rows in set (0.21 sec) ==================================================================================== 23 사원번호가 1100으로 시작하면서 사원번호가 5자리인 사원 정보
  • 24. 기본키를 변형하는 나쁜 SQL – 실행계획 (4.2.1) EXPLAIN SELECT * FROM 사원 WHERE SUBSTRING(사원번호,1,4) = 1100 AND LENGTH(사원번호) = 5; * 결과 +----+-------------+-------+------------+------+---------------+------+---------+------+--------+------ ----+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+---------------+------+---------+------+--------+------ ----+-------------+ | 1 | SIMPLE | 사원 | NULL | ALL | NULL | NULL | NULL | NULL | 299157 | 100.00 | Using where | +----+-------------+-------+------------+------+---------------+------+---------+------+--------+------ ----+-------------+ 1 row in set, 1 warning (0.00 sec) 24
  • 25. 기본키를 변형하는 나쁜 SQL – TOBE (4.2.1) SELECT * FROM 사원 WHERE 사원번호 BETWEEN 11000 AND 11009; * 결과 +----------+------------+-------------+-------------+------+------------+ | 사원번호 | 생년월일 | 이름 | 성 | 성별 | 입사일자 | +----------+------------+-------------+-------------+------+------------+ | 11000 | 1960-09-12 | Alain | Bonifati | M | 1988-08-20 | | 11001 | 1956-04-16 | Baziley | Buchter | F | 1987-02-23 | | 11002 | 1952-02-26 | Bluma | Ulupinar | M | 1996-12-23 | | 11003 | 1960-11-13 | Mariangiola | Gulla | M | 1987-05-24 | | 11004 | 1954-08-05 | JoAnna | Decleir | F | 1992-01-19 | | 11005 | 1958-03-12 | Byong | Douceur | F | 1986-07-27 | | 11006 | 1962-12-26 | Christoper | Butterworth | F | 1989-08-02 | | 11007 | 1962-03-16 | Olivera | Maccarone | M | 1991-04-11 | | 11008 | 1962-07-11 | Gennady | Menhoudj | M | 1988-09-18 | | 11009 | 1954-08-30 | Alper | Axelband | F | 1986-09-09 | +----------+------------+-------------+-------------+------+------------+ 10 rows in set (0.00 sec) 25
  • 26. 형변환으로 인덱스를 활용하지 못하는 나쁜 SQL – ASIS (4.2.3) * SQL SELECT COUNT(1) FROM 급여 WHERE 사용여부 = 1; * 결과 +----------+ | COUNT(1) | +----------+ | 42842 | +----------+ 1 row in set (0.15 sec) 26 유효한 급여의 전체 개수 조회
  • 27. 형변환으로 인덱스를 활용하지 못하는 나쁜 SQL – ASIS (4.2.3) desc 급여; * 결과 +----------+---------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +----------+---------+------+-----+---------+-------+ | 사원번호 | int | NO | PRI | NULL | | | 연봉 | int | NO | | NULL | | | 시작일자 | date | NO | PRI | NULL | | | 종료일자 | date | NO | | NULL | | | 사용여부 | char(1) | YES | MUL | | | +----------+---------+------+-----+---------+-------+ 5 rows in set (0.01 sec) 27
  • 28. 형변환으로 인덱스를 활용하지 못하는 나쁜 SQL – TOBE (4.2.3) ==================================================================================== * SQL SELECT COUNT(1) FROM 급여 WHERE 사용여부 = '1'; * 결과 +----------+ | COUNT(1) | +----------+ | 42842 | +----------+ 1 row in set (0.01 sec) 28
  • 29. 열을 결합하여 사용하는 나쁜 SQL – ASIS, 실행계획 (4.2.4) * SQL EXPLAIN SELECT * FROM 사원 WHERE CONCAT(성별,' ',성) = 'M Radwan'; * 결과 +----+-------------+-------+------------+------+---------------+------+---------+------+--------+------ ----+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+---------------+------+---------+------+--------+------ ----+-------------+ | 1 | SIMPLE | 사원 | NULL | ALL | NULL | NULL | NULL | NULL | 299157 | 100.00 | Using where | +----+-------------+-------+------------+------+---------------+------+---------+------+--------+------ ----+-------------+ 1 row in set, 1 warning (0.00 sec) 29
  • 30. 열을 결합하여 사용하는 나쁜 SQL – TOBE, 실행계획 (4.2.4) * SQL EXPLAIN SELECT * FROM 사원 WHERE 성별 = 'M' AND 성 = 'Radwan'; * 결과 +----+-------------+-------+------------+------+---------------+-----------+---------+-------------+------+----------+---- ---+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+---------------+-----------+---------+-------------+------+----------+---- ---+ | 1 | SIMPLE | 사원 | NULL | ref | I_성별_성 | I_성별_성 | 51 | const,const | 102 | 100.00 | NULL | +----+-------------+-------+------------+------+---------------+-----------+---------+-------------+------+----------+---- ---+ 1 row in set, 1 warning (0.00 sec) 30
  • 31. 인덱스 고려 없이 열을 사용하는 SQL – ASIS (4.2.7) * SQL SELECT 성, 성별, COUNT(1) as 카운트 FROM 사원 GROUP BY 성, 성별; * 결과 +------------------+------+--------+ | 성 | 성별 | 카운트 | +------------------+------+--------+ | Aamodt | M | 120 | | Acton | M | 108 | | Adachi | M | 140 | ... 중략 ... | Zwicker | F | 65 | | Zyda | F | 72 | | Zykh | F | 61 | +------------------+------+--------+ 3274 rows in set (0.43 sec) 31
  • 32. 인덱스 고려 없이 열을 사용하는 SQL – 인덱스 조회 (4.2.7) * SQL show index from 사원; * 결과 +-------+------------+------------+--------------+-------------+-----------+------------- +----------+--------+------+------------+---------+---------------+---------+------------+ | Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression | +-------+------------+------------+--------------+-------------+-----------+------------- +----------+--------+------+------------+---------+---------------+---------+------------+ | 사원 | 0 | PRIMARY | 1 | 사원번호 | A | 299157 | NULL | NULL | | BTREE | | | YES | NULL | | 사원 | 1 | I_입사일자 | 1 | 입사일자 | A | 4612 | NULL | NULL | | BTREE | | | YES | NULL | | 사원 | 1 | I_성별_성 | 1 | 성별 | A | 1 | NULL | NULL | | BTREE | | | YES | NULL | | 사원 | 1 | I_성별_성 | 2 | 성 | A | 3257 | NULL | NULL | | BTREE | | | YES | NULL | +-------+------------+------------+--------------+-------------+-----------+------------- +----------+--------+------+------------+---------+---------------+---------+------------+ 4 rows in set (0.00 sec) 32
  • 33. 인덱스 고려 없이 열을 사용하는 SQL – TOBE (4.2.7) * SQL SELECT 성, 성별, COUNT(1) as 카운트 FROM 사원 GROUP BY 성별, 성; * 결과 +------------------+------+--------+ | 성 | 성별 | 카운트 | +------------------+------+--------+ | Aamodt | M | 120 | | Acton | M | 108 | | Adachi | M | 140 | ... 중략 ... | Zwicker | F | 65 | | Zyda | F | 72 | | Zykh | F | 61 | +------------------+------+--------+ 3274 rows in set (0.04 sec) 33
  • 34. 습관적으로 중복을 제거하는 나쁜 SQL – ASIS, 실행계획 (4.2.5) EXPLAIN SELECT DISTINCT 사원.사원번호, 이름, 성, 부서번호 FROM 사원 JOIN 부서관리자 ON (사원.사원번호 = 부서관리자. 사원번호); * 결과 +----+-------------+------------+------------+--------+---------------+------------+---------+---------------------------- +------+----------+------------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+------------+------------+--------+---------------+------------+---------+---------------------------- +------+----------+------------------------------+ | 1 | SIMPLE | 부서관리자 | NULL | index | PRIMARY | I_부서번호 | 12 | NULL | 24 | 100.00 | Using index; Using temporary | | 1 | SIMPLE | 사원 | NULL | eq_ref | PRIMARY | PRIMARY | 4 | tuning.부서관리자.사원번호 | 1 | 100.00 | NULL | +----+-------------+------------+------------+--------+---------------+------------+---------+---------------------------- +------+----------+------------------------------+ 2 rows in set, 1 warning (0.00 sec) 34
  • 35. 습관적으로 중복을 제거하는 나쁜 SQL – TOBE, 실행계획 (4.2.5) EXPLAIN SELECT 사원.사원번호, 이름, 성, 부서번호 FROM 사원 JOIN 부서관리자 ON (사원.사원번호 = 부서관리자. 사원번호); * 결과 +----+-------------+------------+------------+--------+---------------+------------+---------+---------------------------- +------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+------------+------------+--------+---------------+------------+---------+---------------------------- +------+----------+-------------+ | 1 | SIMPLE | 부서관리자 | NULL | index | PRIMARY | I_부서번호 | 12 | NULL | 24 | 100.00 | Using index | | 1 | SIMPLE | 사원 | NULL | eq_ref | PRIMARY | PRIMARY | 4 | tuning.부서관리자.사원번호 | 1 | 100.00 | NULL | +----+-------------+------------+------------+--------+---------------+------------+---------+---------------------------- +------+----------+-------------+ 2 rows in set, 1 warning (0.00 sec) 35
  • 36. 다수의 쿼리를 UNION 연산자로 합치는 나쁜 SQL – ASIS, 실행계획 (4.2.6) EXPLAIN SELECT 'M' AS 성별, 사원번호 FROM 사원 WHERE 성별 = 'M' AND 성 ='Baba' UNION SELECT 'F', 사원번호 FROM 사원 WHERE 성별 = 'F' AND 성 = 'Baba'; * 결과 +------+--------------+------------+------------+------+---------------+-----------+---------+-------------+------+----------+-----------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +------+--------------+------------+------------+------+---------------+-----------+---------+-------------+------+----------+-----------------+ | 1 | PRIMARY | 사원 | NULL | ref | I_성별_성 | I_성별_성 | 51 | const,const | 135 | 100.00 | Using index | | 2 | UNION | 사원 | NULL | ref | I_성별_성 | I_성별_성 | 51 | const,const | 91 | 100.00 | Using index | | NULL | UNION RESULT | <union1,2> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary | +------+--------------+------------+------------+------+---------------+-----------+---------+-------------+------+----------+-----------------+ 3 rows in set, 1 warning (0.00 sec) 36
  • 37. 다수의 쿼리를 UNION 연산자로 합치는 나쁜 SQL – TOBE, 실행계획 (4.2.6) EXPLAIN SELECT 'M' as 성별, 사원번호 FROM 사원 WHERE 성별 = 'M' AND 성 ='Baba' UNION ALL SELECT 'F' as 성별, 사원번호 FROM 사원 WHERE 성별 = 'F' AND 성 ='Baba'; * 결과 +----+-------------+-------+------------+------+---------------+-----------+---------+-------------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+---------------+-----------+---------+-------------+------+----------+-------------+ | 1 | PRIMARY | 사원 | NULL | ref | I_성별_성 | I_성별_성 | 51 | const,const | 135 | 100.00 | Using index | | 2 | UNION | 사원 | NULL | ref | I_성별_성 | I_성별_성 | 51 | const,const | 91 | 100.00 | Using index | +----+-------------+-------+------------+------+---------------+-----------+---------+-------------+------+----------+-------------+ 2 rows in set, 1 warning (0.00 sec) 37
  • 38. 불필요한 조인을 수행하는 나쁜 SQL – ASIS, 실행계획 (4.3.3) * SQL EXPLAIN SELECT COUNT(DISTINCT 사원.사원번호) as 데이터건수 FROM 사원, ( SELECT 사원번호 FROM 사원출입기록 기록 WHERE 출입문 = 'A' ) 기록 WHERE 사원.사원번호 = 기록.사원번호; * 결과 +----+-------------+-------+------------+--------+---------------+----------+---------+----------------------+--------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+--------+---------------+----------+---------+----------------------+--------+----------+-------------+ | 1 | SIMPLE | 기록 | NULL | ref | I_출입문 | I_출입문 | 4 | const | 329467 | 100.00 | Using index | | 1 | SIMPLE | 사원 | NULL | eq_ref | PRIMARY | PRIMARY | 4 | tuning.기록.사원번호 | 1 | 100.00 | Using index | +----+-------------+-------+------------+--------+---------------+----------+---------+----------------------+--------+----------+-------------+ 2 rows in set, 1 warning (0.00 sec) 38
  • 39. 불필요한 조인을 수행하는 나쁜 SQL – TOBE, 실행계획 (4.3.3) • * SQL • EXPLAIN • SELECT COUNT(1) as 데이터건수 • FROM 사원 • WHERE EXISTS (SELECT 1 • FROM 사원출입기록 기록 • WHERE 출입문 = 'A' • AND 기록.사원번호 = 사원.사원번호); • * 결과 • +----+--------------+-------------+------------+--------+---------------------+---------------------+---------+----------------------+--------+----- -----+--------------------------+ • | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | • +----+--------------+-------------+------------+--------+---------------------+---------------------+---------+----------------------+--------+----- -----+--------------------------+ • | 1 | SIMPLE | 사원 | NULL | index | PRIMARY | I_입사일자 | 3 | NULL | 299157 | 100.00 | Using where; Using index | • | 1 | SIMPLE | <subquery2> | NULL | eq_ref | <auto_distinct_key> | <auto_distinct_key> | 4 | tuning.사원.사원번호 | 1 | 100.00 | NULL | • | 2 | MATERIALIZED | 기록 | NULL | ref | I_출입문 | I_출입문 | 4 | const | 329467 | 100.00 | Using index | • +----+--------------+-------------+------------+--------+---------------------+---------------------+---------+----------------------+--------+----- -----+--------------------------+ • 3 rows in set, 2 warnings (0.00 sec) 39
  • 40. Reference • 업무에 바로 쓰는 SQL 튜닝 • SQL 레벨업 • Effective SQL • Real Mysql 40