1. 테이블 데이터 건수
- EMP 테이블의 건수를 10만 건으로 했을 때, LFET OUTER JOIN과 스칼라 서브쿼리의 소요 시간이 차이가 없어서 160만 건으로 진행한다.
MariaDB [DA_DB]> SELECT COUNT(*) FROM DEPT; +----------+ | COUNT(*) | +----------+ | 204 | +----------+ 1 row in set (0.01 sec) MariaDB [DA_DB]> SELECT COUNT(*) FROM EMP; +----------+ | COUNT(*) | +----------+ | 1600000 | +----------+ 1 row in set (0.31 sec)
2. NULL값이 존재하지 않을 때, LEFT OUTER JOIN과 SUBQUERY의 속도 차이 및 실행계획 분석
#1. FK 제약조건이 존재하지 않을 때
-- 스칼라 서브쿼리
MariaDB [DA_DB]> ANALYZE FORMAT=JSON
-> SELECT A.EMPNO
-> ,A.ENAME
-> ,(SELECT S1.DNAME
-> FROM DEPT S1
-> WHERE S1.DEPTNO = A.DEPTNO) AS DNAME
-> FROM EMP A;
+-------------------------------------------------------------------------------+
| ANALYZE |
+-------------------------------------------------------------------------------+
| {
"query_block": {
"select_id": 1,
"r_loops": 1,
"r_total_time_ms": 877.95,
"table": {
"table_name": "A",
"access_type": "ALL",
"r_loops": 1,
"rows": 100000,
"r_rows": 1.6e6,
"r_total_time_ms": 409.75,
"filtered": 100,
"r_filtered": 100
},
"subqueries": [
{
"expression_cache": {
"r_loops": 1600000,
"r_hit_ratio": 99.987,
"query_block": {
"select_id": 2,
"r_loops": 200,
"r_total_time_ms": 1.3305,
"table": {
"table_name": "S1",
"access_type": "eq_ref",
"possible_keys": ["PRIMARY"],
"key": "PRIMARY",
"key_length": "4",
"used_key_parts": ["DEPTNO"],
"ref": ["DA_DB.A.DEPTNO"],
"r_loops": 200,
"rows": 1,
"r_rows": 1,
"r_total_time_ms": 0.6481,
"filtered": 100,
"r_filtered": 100
}
}
}
}
]
}
} |
+-------------------------------------------------------------------------------+
1 row in set (0.88 sec)
-- LEFT OUTER JOIN
MariaDB [DA_DB]> ANALYZE FORMAT=JSON
-> SELECT A.EMPNO
-> ,A.ENAME
-> ,B.DNAME
-> FROM EMP A
-> LEFT OUTER JOIN DEPT B ON(B.DEPTNO = A.DEPTNO);
+-------------------------------------------------------------------------------+
| ANALYZE |
+-------------------------------------------------------------------------------+
| {
"query_block": {
"select_id": 1,
"r_loops": 1,
"r_total_time_ms": 1130.8,
"const_condition": "1",
"table": {
"table_name": "A",
"access_type": "ALL",
"r_loops": 1,
"rows": 100000,
"r_rows": 1.6e6,
"r_total_time_ms": 383.27,
"filtered": 100,
"r_filtered": 100
},
"table": {
"table_name": "B",
"access_type": "eq_ref",
"possible_keys": ["PRIMARY"],
"key": "PRIMARY",
"key_length": "4",
"used_key_parts": ["DEPTNO"],
"ref": ["DA_DB.A.DEPTNO"],
"r_loops": 1600000,
"rows": 1,
"r_rows": 1,
"r_total_time_ms": 405.35,
"filtered": 100,
"r_filtered": 100,
"attached_condition": "trigcond(trigcond((A.DEPTNO is not null)))"
}
}
} |
+-------------------------------------------------------------------------------+
1 row in set (1.13 sec)
-- FK 제약조건 생성
ALTER TABLE EMP ADD CONSTRAINT EMP_DEPTNO_FK FOREIGN KEY(DEPTNO)
REFERENCES DEPT(DEPTNO) ;
-- 스칼라 서브쿼리
MariaDB [DA_DB]> ANALYZE FORMAT=JSON
-> SELECT A.EMPNO
-> ,A.ENAME
-> ,(SELECT S1.DNAME
-> FROM DEPT S1
-> WHERE S1.DEPTNO = A.DEPTNO) AS DNAME
-> FROM EMP A;
+-------------------------------------------------------------------------------+
| ANALYZE |
+-------------------------------------------------------------------------------+
| {
"query_block": {
"select_id": 1,
"r_loops": 1,
"r_total_time_ms": 852.96,
"table": {
"table_name": "A",
"access_type": "ALL",
"r_loops": 1,
"rows": 100000,
"r_rows": 1.6e6,
"r_total_time_ms": 400.29,
"filtered": 100,
"r_filtered": 100
},
"subqueries": [
{
"expression_cache": {
"r_loops": 1600000,
"r_hit_ratio": 99.987,
"query_block": {
"select_id": 2,
"r_loops": 200,
"r_total_time_ms": 1.1185,
"table": {
"table_name": "S1",
"access_type": "eq_ref",
"possible_keys": ["PRIMARY"],
"key": "PRIMARY",
"key_length": "4",
"used_key_parts": ["DEPTNO"],
"ref": ["DA_DB.A.DEPTNO"],
"r_loops": 200,
"rows": 1,
"r_rows": 1,
"r_total_time_ms": 0.4271,
"filtered": 100,
"r_filtered": 100
}
}
}
}
]
}
} |
+-------------------------------------------------------------------------------+
1 row in set (0.85 sec)
-- LEFT OUTER JOIN
MariaDB [DA_DB]> ANALYZE FORMAT=JSON
-> SELECT A.EMPNO
-> ,A.ENAME
-> ,B.DNAME
-> FROM EMP A
-> LEFT OUTER JOIN DEPT B ON(B.DEPTNO = A.DEPTNO);
+-------------------------------------------------------------------------------+
| ANALYZE |
+-------------------------------------------------------------------------------+
| {
"query_block": {
"select_id": 1,
"r_loops": 1,
"r_total_time_ms": 1150.4,
"const_condition": "1",
"table": {
"table_name": "A",
"access_type": "ALL",
"r_loops": 1,
"rows": 100000,
"r_rows": 1.6e6,
"r_total_time_ms": 392.4,
"filtered": 100,
"r_filtered": 100
},
"table": {
"table_name": "B",
"access_type": "eq_ref",
"possible_keys": ["PRIMARY"],
"key": "PRIMARY",
"key_length": "4",
"used_key_parts": ["DEPTNO"],
"ref": ["DA_DB.A.DEPTNO"],
"r_loops": 1600000,
"rows": 1,
"r_rows": 1,
"r_total_time_ms": 410.68,
"filtered": 100,
"r_filtered": 100,
"attached_condition": "trigcond(trigcond((A.DEPTNO is not null)))"
}
}
} |
+-------------------------------------------------------------------------------+
1 row in set (1.15 sec)
ALTER TABLE EMP MODIFY COLUMN DEPTNO INT(4) NOT NULL;
-- [번외] NOT NULL 지정 시 DEFAULT의 유무에 관계없이 소요시간이 비슷하다.
MariaDB [DA_DB]> ALTER TABLE EMP MODIFY COLUMN DEPTNO INT(4) NOT NULL DEFAULT 10;
Query OK, 0 rows affected (1 min 10.22 sec)
Records: 0 Duplicates: 0 Warnings: 0
MariaDB [DA_DB]> ALTER TABLE EMP MODIFY COLUMN DEPTNO INT(4) NULL ;
Query OK, 0 rows affected (1 min 18.83 sec)
Records: 0 Duplicates: 0 Warnings: 0
MariaDB [DA_DB]> ALTER TABLE EMP MODIFY COLUMN DEPTNO INT(4) NOT NULL ;
Query OK, 0 rows affected (1 min 20.98 sec)
Records: 0 Duplicates: 0 Warnings: 0
-- 스칼라 서브쿼리
MariaDB [DA_DB]> ANALYZE FORMAT=JSON
-> SELECT A.EMPNO
-> ,A.ENAME
-> ,(SELECT S1.DNAME
-> FROM DEPT S1
-> WHERE S1.DEPTNO = A.DEPTNO) AS DNAME
-> FROM EMP A ;
+-------------------------------------------------------------------------------+
| ANALYZE |
+-------------------------------------------------------------------------------+
| {
"query_block": {
"select_id": 1,
"r_loops": 1,
"r_total_time_ms": 865.14,
"table": {
"table_name": "A",
"access_type": "ALL",
"r_loops": 1,
"rows": 100000,
"r_rows": 1.6e6,
"r_total_time_ms": 411.44,
"filtered": 100,
"r_filtered": 100
},
"subqueries": [
{
"expression_cache": {
"r_loops": 1600000,
"r_hit_ratio": 99.987,
"query_block": {
"select_id": 2,
"r_loops": 200,
"r_total_time_ms": 7.739,
"table": {
"table_name": "S1",
"access_type": "eq_ref",
"possible_keys": ["PRIMARY"],
"key": "PRIMARY",
"key_length": "4",
"used_key_parts": ["DEPTNO"],
"ref": ["DA_DB.A.DEPTNO"],
"r_loops": 200,
"rows": 1,
"r_rows": 1,
"r_total_time_ms": 7.1696,
"filtered": 100,
"r_filtered": 100
}
}
}
}
]
}
} |
+-------------------------------------------------------------------------------+
1 row in set (0.90 sec)
-- LEFT OUTER JOIN
MariaDB [DA_DB]> ANALYZE FORMAT=JSON
-> SELECT A.EMPNO
-> ,A.ENAME
-> ,B.DNAME
-> FROM EMP A
-> LEFT OUTER JOIN DEPT B ON(B.DEPTNO = A.DEPTNO);
+-------------------------------------------------------------------------------+
| ANALYZE |
+-------------------------------------------------------------------------------+
| {
"query_block": {
"select_id": 1,
"r_loops": 1,
"r_total_time_ms": 1132.6,
"const_condition": "1",
"table": {
"table_name": "A",
"access_type": "ALL",
"r_loops": 1,
"rows": 100000,
"r_rows": 1.6e6,
"r_total_time_ms": 404.58,
"filtered": 100,
"r_filtered": 100
},
"table": {
"table_name": "B",
"access_type": "eq_ref",
"possible_keys": ["PRIMARY"],
"key": "PRIMARY",
"key_length": "4",
"used_key_parts": ["DEPTNO"],
"ref": ["DA_DB.A.DEPTNO"],
"r_loops": 1600000,
"rows": 1,
"r_rows": 1,
"r_total_time_ms": 422.72,
"filtered": 100,
"r_filtered": 100
}
}
} |
+-------------------------------------------------------------------------------+
1 row in set (1.13 sec)
5. NULL값이 존재할 때, LEFT OUTER JOIN과 SUBQUERY의 속도 차이 및 실행계획 분석
- 총 160만 건 중, 40만 건이 DEPTNO가 NULL로 설정되었다. 그래서 LEFT OUTER JOIN에서 DEPT테이블을 액세스 하는데 r_rows가 0.75로 나타났다.(DEPT에 실제 액세스한 로우 수 = 전체 로우 수 - NULL 데이터 건 수 / 전체 데이터 건 수 =1 - 40만 건/160만 건)
- NULL의 비율을 증가시켜도 소요시간엔 큰 영향이 없다.
-- NOT NULL 제약조건 제거
MariaDB [DA_DB]> ALTER TABLE EMP MODIFY COLUMN DEPTNO INT(4) NULL ;
Query OK, 0 rows affected (1 min 4.42 sec)
Records: 0 Duplicates: 0 Warnings: 0
-- NULL의 비율을 25%로 업데이트
MariaDB [DA_DB]> UPDATE EMP
-> SET DEPTNO = NULL
-> WHERE MOD(EMPNO, 4) = 0;
Query OK, 400000 rows affected (17.77 sec)
Rows matched: 400000 Changed: 400000 Warnings: 0
MariaDB [DA_DB]> SELECT COUNT(*) FROM EMP WHERE DEPTNO IS NULL;
+----------+
| COUNT(*) |
+----------+
| 400000 |
+----------+
1 row in set (0.12 sec)
-- 스칼라 서브쿼리
MariaDB [DA_DB]> ANALYZE FORMAT=JSON
-> SELECT A.EMPNO
-> ,A.ENAME
-> ,(SELECT S1.DNAME
-> FROM DEPT S1
-> WHERE S1.DEPTNO = A.DEPTNO) AS DNAME
-> FROM EMP A;
+-------------------------------------------------------------------------------+
| ANALYZE |
+-------------------------------------------------------------------------------+
| {
"query_block": {
"select_id": 1,
"r_loops": 1,
"r_total_time_ms": 933.3,
"table": {
"table_name": "A",
"access_type": "ALL",
"r_loops": 1,
"rows": 100000,
"r_rows": 1.6e6,
"r_total_time_ms": 452.55,
"filtered": 100,
"r_filtered": 100
},
"subqueries": [
{
"expression_cache": {
"r_loops": 1600000,
"r_hit_ratio": 99.987,
"query_block": {
"select_id": 2,
"r_loops": 201,
"r_total_time_ms": 12.372,
"table": {
"table_name": "S1",
"access_type": "eq_ref",
"possible_keys": ["PRIMARY"],
"key": "PRIMARY",
"key_length": "4",
"used_key_parts": ["DEPTNO"],
"ref": ["DA_DB.A.DEPTNO"],
"r_loops": 200,
"rows": 1,
"r_rows": 1,
"r_total_time_ms": 11.933,
"filtered": 100,
"r_filtered": 100
}
}
}
}
]
}
} |
+-------------------------------------------------------------------------------+
1 row in set (1.04 sec)
-- LEFT OUTER JOIN
MariaDB [DA_DB]> ANALYZE FORMAT=JSON
-> SELECT A.EMPNO
-> ,A.ENAME
-> ,B.DNAME
-> FROM EMP A
-> LEFT OUTER JOIN DEPT B ON(B.DEPTNO = A.DEPTNO);
+-------------------------------------------------------------------------------+
| ANALYZE |
+-------------------------------------------------------------------------------+
| {
"query_block": {
"select_id": 1,
"r_loops": 1,
"r_total_time_ms": 1065.9,
"const_condition": "1",
"table": {
"table_name": "A",
"access_type": "ALL",
"r_loops": 1,
"rows": 100000,
"r_rows": 1.6e6,
"r_total_time_ms": 404.32,
"filtered": 100,
"r_filtered": 100
},
"table": {
"table_name": "B",
"access_type": "eq_ref",
"possible_keys": ["PRIMARY"],
"key": "PRIMARY",
"key_length": "4",
"used_key_parts": ["DEPTNO"],
"ref": ["DA_DB.A.DEPTNO"],
"r_loops": 1600000,
"rows": 1,
"r_rows": 0.75,
"r_total_time_ms": 341.92,
"filtered": 100,
"r_filtered": 100,
"attached_condition": "trigcond(trigcond((A.DEPTNO is not null)))"
}
}
} |
+-------------------------------------------------------------------------------+
1 row in set (1.06 sec)
-- NULL의 비율을 50%로 업데이트
MariaDB [DA_DB]> UPDATE EMP
-> SET DEPTNO = NULL
-> WHERE MOD(EMPNO, 2) = 0;
Query OK, 400000 rows affected (16.32 sec)
Rows matched: 800000 Changed: 400000 Warnings: 0
MariaDB [DA_DB]> SELECT COUNT(*) FROM EMP WHERE DEPTNO IS NULL;
+----------+
| COUNT(*) |
+----------+
| 800000 |
+----------+
1 row in set (0.21 sec)
-- 스칼라 서브쿼리
MariaDB [DA_DB]> ANALYZE FORMAT=JSON
-> SELECT A.EMPNO
-> ,A.ENAME
-> ,(SELECT S1.DNAME
-> FROM DEPT S1
-> WHERE S1.DEPTNO = A.DEPTNO) AS DNAME
-> FROM EMP A;
+-------------------------------------------------------------------------------+
| ANALYZE |
+-------------------------------------------------------------------------------+
| {
"query_block": {
"select_id": 1,
"r_loops": 1,
"r_total_time_ms": 889.29,
"table": {
"table_name": "A",
"access_type": "ALL",
"r_loops": 1,
"rows": 100000,
"r_rows": 1.6e6,
"r_total_time_ms": 413.73,
"filtered": 100,
"r_filtered": 100
},
"subqueries": [
{
"expression_cache": {
"r_loops": 1600000,
"r_hit_ratio": 99.987,
"query_block": {
"select_id": 2,
"r_loops": 201,
"r_total_time_ms": 1.2998,
"table": {
"table_name": "S1",
"access_type": "eq_ref",
"possible_keys": ["PRIMARY"],
"key": "PRIMARY",
"key_length": "4",
"used_key_parts": ["DEPTNO"],
"ref": ["DA_DB.A.DEPTNO"],
"r_loops": 200,
"rows": 1,
"r_rows": 1,
"r_total_time_ms": 0.514,
"filtered": 100,
"r_filtered": 100
}
}
}
}
]
}
} |
+-------------------------------------------------------------------------------+
1 row in set (0.89 sec)
-- LEFT OUTER JOIN
MariaDB [DA_DB]> ANALYZE FORMAT=JSON
-> SELECT A.EMPNO
-> ,A.ENAME
-> ,B.DNAME
-> FROM EMP A
-> LEFT OUTER JOIN DEPT B ON(B.DEPTNO = A.DEPTNO);
+-------------------------------------------------------------------------------+
| ANALYZE |
+-------------------------------------------------------------------------------+
| {
"query_block": {
"select_id": 1,
"r_loops": 1,
"r_total_time_ms": 1065.5,
"const_condition": "1",
"table": {
"table_name": "A",
"access_type": "ALL",
"r_loops": 1,
"rows": 100000,
"r_rows": 1.6e6,
"r_total_time_ms": 392.75,
"filtered": 100,
"r_filtered": 100
},
"table": {
"table_name": "B",
"access_type": "eq_ref",
"possible_keys": ["PRIMARY"],
"key": "PRIMARY",
"key_length": "4",
"used_key_parts": ["DEPTNO"],
"ref": ["DA_DB.A.DEPTNO"],
"r_loops": 1600000,
"rows": 1,
"r_rows": 0.5,
"r_total_time_ms": 368.07,
"filtered": 100,
"r_filtered": 100,
"attached_condition": "trigcond(trigcond((A.DEPTNO is not null)))"
}
}
} |
+-------------------------------------------------------------------------------+
1 row in set (1.06 sec)
6. 스칼라 서브쿼리 테이블/LEFT OUTER JOIN에서 DRIVEN 테이블의 데이터 건수가 많을 때
- 스칼라 서브쿼리 테이블/LEFT OUTER JOIN에서 DRIVEN 테이블의 데이터 건수가 많을 때는 캐싱효과(r_hit_ratio/r_loops)와 상관없이 소요시간이 거의 동일하다.
MariaDB [DA_DB]> ANALYZE FORMAT=JSON
-> SELECT A.EMPNO
-> ,A.ENAME
-> ,(SELECT S1.ENAME
-> FROM EMP S1
-> WHERE S1.EMPNO = A.MGR) AS MANAGER_NAME
-> FROM EMP A;
+-------------------------------------------------------------------------------+
| ANALYZE |
+-------------------------------------------------------------------------------+
| {
"query_block": {
"select_id": 1,
"r_loops": 1,
"r_total_time_ms": 718.15,
"table": {
"table_name": "A",
"access_type": "ALL",
"r_loops": 1,
"rows": 100000,
"r_rows": 1.6e6,
"r_total_time_ms": 397.81,
"filtered": 100,
"r_filtered": 100
},
"subqueries": [
{
"expression_cache": {
"r_loops": 1600000,
"r_hit_ratio": 99.999,
"query_block": {
"select_id": 2,
"r_loops": 11,
"r_total_time_ms": 0.1686,
"table": {
"table_name": "S1",
"access_type": "eq_ref",
"possible_keys": ["PRIMARY"],
"key": "PRIMARY",
"key_length": "4",
"used_key_parts": ["EMPNO"],
"ref": ["DA_DB.A.MGR"],
"r_loops": 10,
"rows": 1,
"r_rows": 1,
"r_total_time_ms": 0.0594,
"filtered": 100,
"r_filtered": 100
}
}
}
}
]
}
} |
+-------------------------------------------------------------------------------+
1 row in set (0.75 sec)
MariaDB [DA_DB]> ANALYZE FORMAT=JSON
-> SELECT A.EMPNO
-> ,A.ENAME
-> ,B.ENAME AS MANAGER_NAME
-> FROM EMP A
-> LEFT OUTER JOIN EMP B ON(B.EMPNO = A.MGR);
+-------------------------------------------------------------------------------+
| ANALYZE |
+-------------------------------------------------------------------------------+
| {
"query_block": {
"select_id": 1,
"r_loops": 1,
"r_total_time_ms": 714.52,
"const_condition": "1",
"table": {
"table_name": "A",
"access_type": "ALL",
"r_loops": 1,
"rows": 100000,
"r_rows": 1.6e6,
"r_total_time_ms": 404.83,
"filtered": 100,
"r_filtered": 100
},
"table": {
"table_name": "B",
"access_type": "eq_ref",
"possible_keys": ["PRIMARY"],
"key": "PRIMARY",
"key_length": "4",
"used_key_parts": ["EMPNO"],
"ref": ["DA_DB.A.MGR"],
"r_loops": 1600000,
"rows": 1,
"r_rows": 1,
"r_total_time_ms": 0.7399,
"filtered": 100,
"r_filtered": 100,
"attached_condition": "trigcond(trigcond((A.MGR is not null)))"
}
}
} |
+-------------------------------------------------------------------------------+
1 row in set (0.71 sec)
[결론]
- 스칼라 서브쿼리 테이블/ LFTER OUTER JOIN에서 DRIVEN 테이블의 건수가 적을 경우에 LEFT OUTER JOIN 보다 서브쿼리 캐싱효과로 인해 스칼라 서브 쿼리가 성능이 더 좋다. 테이블의 건수가 많을 때는 소요시간이 거의 동일하다.
- JOIN 조건 컬럼에 NOT NULL이 있을 경우 MariaDB 내부적으로 INNER JOIN으로 변경한다.("attached_condition": "trigcond(trigcond((A.DEPTNO is not null)))" 조건이 사라진다.)
'MariaDB > SQL Tuning' 카테고리의 다른 글
| [MariaDB][성능] 서브쿼리 최적화 (0) | 2016.09.25 |
|---|---|
| [MariaDB][성능] LEFT OUTER JOIN과 스칼라 서브쿼리 실행계획 비교2 (0) | 2016.08.28 |
| [MariaDB][성능] 스칼라 서브쿼리 실행계획2 (0) | 2016.08.17 |
| [MariaDB][성능]인라인뷰 처리 방식 분석 (0) | 2016.08.17 |
| [MariaDB][성능] 스칼라 서브쿼리 실행계획 (0) | 2016.08.15 |