My Books

My Slides

rss 아이콘 이미지

한 서버에 MySQL 5.6 버전과 5.7 버전을 설치하는 방법

MySQL 2017.04.28 22:12 Posted by 시연아카데미

5.7을 이용해서 MySQL 연구를 하던 중에 5.7의 옵티마이저 동작 원리가 5.6과 달라진 부분이 많아서 5.6을 설치할 필요가 생겼습니다. 그런데 한 서버에 MySQL 5.6과 5.7 버전을 설치하는 것이 약간 까다롭네요.


PostgreSQL은 RPM을 이용해서 설치하더라도 설치 디렉토리, 접속 포트 등을 지정할 수 있었는데, MySQL은 RPM을 이용해서는 지정된 디렉토리 (/var/lib/mysql)에만 설치가 가능한 것 같습니다.


일부 문서를 보면 --relocate 옵션을 이용해서 디렉토리 지정이 가능하다고 되어있는데, 5.6, 5.7 버전은 relocate가 불가능합니다.


그래서 이참에 소스 컴파일로 두 개의 버전을 설치해보기로 했습니다. 그런데 이 또한 사전 설치 작업, 라이브러리 설치, socket 지정 등의 작업으로 인한 시행착오가 일부 있었습니다. 따라서 관련 내용을 공유하고자 포스팅합니다.


사전 설치 작업



소스 컴파일을 위해선 cmake와 Bison을 설치해야 합니다. 그리고 5.7은  Boost 라이브러리를 추가로 설치해야 합니다.


1) cmake 설치


다운로드 사이트: https://cmake.org/
다운로드 파일: cmake-3.8.0-Linux-x86_64.tar.gz 


-- 설치 단계


배포용 파일이므로 컴파일은 필요없고 디렉토리에 압축만 풀면됩니다.


# gunzip cmake-3.8.0-Linux-x86_64.tar.gz
# tar xvf cmake-3.8.0-Linux-x86_64.tar
# cp -rp cmake-3.8.0-Linux-x86_64 /usr/local/cmake


-- .bash_profile PATH를 수정합니다.


PATH=$PATH:$HOME/bin:/usr/local/cmake/bin


# . .bash_profile


2) Bison 설치


다운로드 사이트: http://www.gnu.org/software/bison/
다운로드 파일: bison-3.0.tar.gz


-- 설치 단계 


# gunzip bison-3.0.tar.gz
# tar xvf bison-3.0.tar
# cd bison-3.0
# ./configure
# make
# make install


3) Boost 라이브러리 설치 (참고사이트: http://dslee1.blogspot.kr/2016/04/mysql-5711-cmake-error-at.html)


-- 설치 단계


# cd /usr/local/src/
# wget http://downloads.sourceforge.net/project/boost/boost/1.59.0/boost_1_59_0.tar.gz
# tar xvfz boost_1_59_0.tar.gz


4) 유저 및 그룹 생성


# groupadd mysql56
# useradd -r -g mysql56 -s /bin/false mysql56

# groupadd mysql57
# useradd -r -g mysql57 -s /bin/false mysql57


5) 소스 다운로드


https://github.com/mysql/mysql-server/tree/5.6
https://github.com/mysql/mysql-server/tree/5.7


MySQL 5.6 설치



설치 단계는 다음과 같습니다.


1) 설치 단계


# unzip mysql-server-5.6.zip
# cd mysql-server-5.6
# mkdir bld
# cd bld
# cmake ..


-- CmakeCache.txt 파일을 열어서 설치 디렉토리 위치를 변경합니다.


CMAKE_INSTALL_PREFIX:PATH=/usr/local/mysql56
MYSQL_DATADIR:PATH=/usr/local/mysql56/data
SYSCONFDIR:PATH=/usr/local/mysql56/etc


-- 내용을 수정한 후에 make 및 make install을 수행합니다.


# make
# make install


2) 후속 작업


# chown -R mysql56:mysql56 /usr/local/mysql56
# /usr/local/mysql56/scripts/mysql_install_db --user=mysql56


3) 비밀번호 변경 (참고사이트: http://egloos.zum.com/spongebody/v/3530932)


5.6을 소스로 설치할 때 root 비번을 잘 모르는 경우가 있습니다. 이때는 다음과 같이 조치합니다.


# /usr/local/mysql56/bin/mysqld_safe --user=mysql56 --skip-grant-tables &
# mysql -u root mysql
mysql> update user set password=password('rootpassword') where user = 'root';
mysql> flush privileges;


# killall mysqld
# /usr/local/mysql56/bin/mysqld_safe --user=mysql56 &


4) /usr/local/mysql56/my.cnf 파일 편집


저 같은 경우에는 5.6은 기본 포트인 3306으로, socket은 /tmp/mysql56.sock 으로 설정했습니다. 따라서 /usr/local/mysql56/my.cnf 파일을 다음과 같이 편집합니다.


[mysqld]
port = 3306
socket=/tmp/mysql56.sock
이하 생략


5) 접속 방법


소켓을 별도로 지정했으므로 -S 옵션을 이용해서 해당 소켓을 지정합니다.

# mysql -uroot -p -S /tmp/mysql56.sock


MySQL 5.7 설치



설치 단계는 다음과 같습니다.


1) 설치 단계


# unzip mysql-server-5.7.zip
# cd mysql-server-5.7
# mkdir bld
# cd bld
# cmake .. -DDOWNLOAD_BOOST=1 -DWITH_BOOST=/usr/local/src/boost_1_59_0


-- CmakeCache.txt 파일을 열어서 설치 디렉토리 위치를 변경합니다.


CMAKE_INSTALL_PREFIX:PATH=/usr/local/mysql57
MYSQL_DATADIR:PATH=/usr/local/mysql57/data
MYSQL_KEYRINGDIR:PATH=/usr/local/mysql57/keyring
SYSCONFDIR:PATH=/usr/local/mysql57/etc


-- 내용을 수정한 후에 make 및 make install을 수행합니다.


# make
# make install


2) 후속 작업


아래 작업 시에 화면에 root 임시 비번이 출력됩니다. 해당 비번을 복사해둡니다.


chown -R mysql57:mysql57 /usr/local/mysql57
/usr/local/mysql57/bin/mysqld --initialize --user=mysql57
/usr/local/mysql57/bin/mysql_ssl_rsa_setup


3) /usr/local/mysql57/my.cnf 파일 편집 


[mysqld]
socket=/tmp/mysql57.sock
port=3307
이하 생략


4) MySQL 시작
 
# /usr/local/mysql57/bin/mysqld_safe --user=mysql57 &


5) 비밀번호 변경


# mysql -uroot -ptcDqMfDh:7h? -S /tmp/mysql57.sock
mysql> alter user 'root'@'localhost' identified by 'rootpassword';
mysql> flush privileges;


6) 접속


소켓을 별도로 지정했으므로 -S 옵션을 이용해서 해당 소켓을 지정합니다.


# mysql -uroot -p -S /tmp/mysql57.sock


이로써 한 서버에 2개의 MySQL 설치가 완료됐습니다.


저는 서버에서 테스트용으로 2개 버전을 설치한 것이라서 mysql 접속만 하면 되는데요.

만일 PHP를 이용하는 경우에는 기본적으로는 /tmp/mysql.sock을 이용하므로 socket 명을 변경한 경우에는 php.ini를 수정하는 등의 작업이 필요할 것 같습니다.


저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

PostgreSQL 1-Day 튜닝 교육을 시작합니다.

PostgreSQL 2017.04.27 11:43 Posted by 시연아카데미

어제 처음으로 외부에서 PostgreSQL 1-Day 튜닝 교육을 진행했습니다.
진행 후에 수강하신 분들의 반응을 들어보고, 저 나름대로 자체적인 평가를 해본 결과 정식으로 교육을 런칭해도 괜찮을 것같다는 생각이 들었습니다.

 

교육과 관련된 내용은 아래 링크를 참고해주세요.

 

https://siyeonacademy.wordpress.com/postgresql-1-day-tuning-workshop/

 

감사합니다.

 

김시연 올림.

저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

개발자를 위한 PostgreSQL 튜닝 워크샵 교육 자료

PostgreSQL 2017.04.24 02:55 Posted by 시연아카데미

우연한 기회에 개발자 대상으로 1일짜리 PostgreSQL 성능 교육을 진행하게 됐습니다.

이를 위해 제 책중에서 액기스만 뽑아서 아주 깔끔한(?) 교육 자료를 만들었습니다.

첫 교육 진행 후에 반응이 괜찮으면 정식 교육으로 론칭 해보려고 합니다.

온사이트 교육도 환영입니다. (제 e-mail로 연락주세요. ^^) 


파트-1: 아키텍처 (개요, Shared Buffer, MVCC, Vacuum, 파티션)







파트-2: 옵티마이저 & 쿼리 튜닝 (Explain, Access 방법, 조인, Query Rewrite, PG_HINT_PLAN, BRIN, Parallel)




저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

STRAIGHT_JOIN 힌트를 이용한 MySQL Explain 읽는 순서 확인

MySQL 2017.04.21 21:39 Posted by 시연아카데미

MySQL의 Explain 결과는 다른 DBMS와는 사뭇 다릅니다. 따라서 MySQL의 Explain 결과를 처음 접했을 때는 약간 당황스럽기도 합니다. 조금 더 익숙해지면 오히려 더 편리할 수도 있을지는 모르겠으나, 저로서는 아직은 생소합니다.


읽는 순서는 매우 단순합니다. "위에서부터 아래로 읽으면 됩니다."


과연 그럴까요? 이를 검증해보기 위해서 STRAIGHT_JOIN 힌트를 이용해서 몇 가지 경우를 확인해봤습니다. (STRAIGHT_JOIN 힌트는 FROM 절 순서대로 조인을 수행하도록 유도하는 힌트입니다.)


단순 조인인 경우는 위에서부터 읽어내려가면 됩니다. Derived 테이블이 있는 경우는 약간 헷갈린 데, 그 이유는 Derived 테이블의 원본 테이블이 맨 하단부에 위치하기 때문입니다. 이 역시 어느 정도 적응이 필요할 것 같습니다.


참고로 MySQL은 ORACLE의 In-Line View를 Derived 테이블 또는 From 절 내의 Subquery라고 합니다. 그리고 In-Line View와 같이 From 절에 View 쿼리를 직접 작성한 것을 unnamed derived 테이블이라고 하고, View를 named derived 테이블이라고도 합니다.


아래는 테스트 내용입니다.


-- 테이블 생성 

drop table t1;
drop table t2;
drop table t3;

create table t1 (c1 integer, c2 integer);
create table t2 (c1 integer, c2 integer);
create table t3 (c1 integer, c2 integer);

		 
-- STRAIGHT_JOIN 힌트를 적용했으므로 조인 순서는 T3->T2->T1이며 
-- Explain 결과도 이를 나타내고 있습니다.

explain
select STRAIGHT_JOIN * from t3, t2, t1
where t1.c1 = t2.c1
and   t1.c1 = t3.c1;
+----+-------------+-------+----------------------------------------------------+
| id | select_type | table | Extra                                              |
+----+-------------+-------+----------------------------------------------------+
|  1 | SIMPLE      | t3    | NULL                                               |
|  1 | SIMPLE      | t2    | Using where; Using join buffer (Block Nested Loop) |
|  1 | SIMPLE      | t1    | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+-------+----------------------------------------------------+

-- pt-visual-explain 결과는 아래에서 위로 읽습니다. 

[root@pgserver ~]# mysql -u root -p mydb01 < ./2.sql | pt-visual-explain
Enter password:
JOIN
+- Join buffer
|  +- Filter with WHERE
|     +- Table scan
|        rows           1
|        +- Table
|           table          t1
+- JOIN
   +- Join buffer
   |  +- Filter with WHERE
   |     +- Table scan
   |        rows           1
   |        +- Table
   |           table          t2
   +- Table scan
      rows           1
      +- Table
         table          t3

		 
-- Derived 테이블이 있는 경우를 살펴보겠습니다. 
-- 아래의 예제는 (t1 scan) -> derived 테이블 생성 -> t2 -> t3 순서로 수행되며,
-- Explain은 이를 다음과 같이 표현하고 있습니다.
-- ID가 1인 것들이 조인 대상 집합이며 ID가 2인 것이 derived 테이블 생성 작업을 나타냅니다.

explain
select STRAIGHT_JOIN * from (select c1, count(*) cnt from t1 group by c1) a, t2, t3
where  a.c1 = t2.c1
and    a.c1 = t3.c1;
+----+-------------+------------+----------------------------------------------------+
| id | select_type | table      | Extra                                              |
+----+-------------+------------+----------------------------------------------------+
|  1 | PRIMARY     |  | NULL                                               |
|  1 | PRIMARY     | t2         | Using where; Using join buffer (Block Nested Loop) |
|  1 | PRIMARY     | t3         | Using where; Using join buffer (Block Nested Loop) |
|  2 | DERIVED     | t1         | Using temporary; Using filesort                    |
+----+-------------+------------+----------------------------------------------------+

-- pt-visual-explain은 관련있는 것을 Tree 구조로 표현해줍니다. 
-- 아래의 결과를 보면, t1 scan -> derived 테이블 생성 작업을 연관해서 트리 구조로 표현합니다.

[root@pgserver ~]# mysql -u root -p mydb01 < ./3.sql | pt-visual-explain
Enter password:
JOIN
+- Join buffer
|  +- Filter with WHERE
|     +- Table scan
|        rows           1
|        +- Table
|           table          t3
+- JOIN
   +- Join buffer
   |  +- Filter with WHERE
   |     +- Table scan
   |        rows           1
   |        +- Table
   |           table          t2
   +- Table scan
      rows           2
      +- DERIVED
         table          derived(temporary(t1))
         +- Filesort
            +- TEMPORARY
               table          temporary(t1)
               +- Table scan
                  rows           1
                  +- Table
                     table          t1

-- 참고로 조인 순서를 변경하면 다음과 같이 Explain 결과가 변경됩니다. 
-- 이 예제 또한, ID가 2인 것이 맨 아래에 위치한다는 것을 알 수 있습니다.

explain
select STRAIGHT_JOIN * from t2, (select c1, count(*) cnt from t1 group by c1) a, t3
where  a.c1 = t2.c1
and    a.c1 = t3.c1;
+----+-------------+------------+----------------------------------------------------+
| id | select_type | table      | Extra                                              |
+----+-------------+------------+----------------------------------------------------+
|  1 | PRIMARY     | t2         | Using where                                        |
|  1 | PRIMARY     |  | NULL                                               |
|  1 | PRIMARY     | t3         | Using where; Using join buffer (Block Nested Loop) |
|  2 | DERIVED     | t1         | Using temporary; Using filesort                    |
+----+-------------+------------+----------------------------------------------------+

[root@pgserver ~]# mysql -u root -p mydb01 < ./4.sql | pt-visual-explain
Enter password:
JOIN
+- Join buffer
|  +- Filter with WHERE
|     +- Table scan
|        rows           1
|        +- Table
|           table          t3
+- JOIN
   +- Bookmark lookup
   |  +- DERIVED
   |  |  table          derived(temporary(t1))
   |  |  possible_keys  
   |  |  +- Filesort
   |  |     +- TEMPORARY
   |  |        table          temporary(t1)
   |  |        +- Table scan
   |  |           rows           1
   |  |           +- Table
   |  |              table          t1
   |  +- Index lookup
   |     key            derived(temporary(t1))->
   |     possible_keys  
   |     key_len        5
   |     ref            mydb01.t2.c1
   |     rows           2
   +- Filter with WHERE
      +- Table scan
         rows           1
         +- Table
            table          t2


저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
TAG EXPLAIN, MySQL

MySQL은 Query Cache 기능을 제공합니다. 이 기능은 쿼리 수행 결과를 메모리에 저장한 후에, 동일한 SQL이 수행되면 메모리에 저장된 수행 결과를 리턴하는 기능입니다.
쿼리 수행 대신 메모리에 있는 수행 결과를 리턴해주므로 수행 속도는 매우 빠릅니다. ORACLE 11g부터 제공하는 Result Cache의 Subset이라고 보면 될 것 같습니다.


이 기능을 잘 활용할 수 있는 업무는 주로 게시판 카운트 쿼리입니다. 특히, 입력 빈도는 높지 않으나 조회 빈도가 높은 게시판인 경우에는 Query Cache의 효과가 크겠죠.
또한, 편법이기는 하지만 임원들이 자주 보는 화면의 특정 조건 쿼리에 쿼리 캐시를 적용하면, 업무 담당자들이 스트레스가 조금은 줄어들 것 같습니다.


제가 MySQL의 쿼리 캐시에 대해서 궁금했던 점은 ORACLE과 같이 쿼리 블록 단위로도 캐싱이 가능한지 였습니다. 결론부터 말하면 안되네요. 사실, UI 화면은 매우 복잡하고 다양한 검색 조건이 존재합니다. 그리고 속도가 느린 쿼리의 대부분은 복잡한 쿼리겠죠. 하지만 간혹 이런 쿼리들의 공통 분모(쿼리 블록)가 존재하고, 이 부분이 항상 느리다면 쿼리 캐시 기능이 매우 좋은 해결책이 됩니다. 하지만 MySQL은 아쉽게도 이 부분은 지원되지 않고 있습니다. 아래의 테스트 내용을 참고하세요.


-- 파라미터 설정

query_cache_size=10485760
query_cache_type=2  -- 0:off 1: 항상 enable 2: on demand (SQL CACHE 힌트 사용시 사용) 

-- 확인 

mysql> show variables like 'query_cache%';
+------------------------------+----------+
| Variable_name                | Value    |
+------------------------------+----------+
| query_cache_limit            | 1048576  |
| query_cache_min_res_unit     | 4096     |
| query_cache_size             | 10485760 |
| query_cache_type             | DEMAND   |
| query_cache_wlock_invalidate | OFF      |
+------------------------------+----------+

-- 조회 속도가 느림 

mysql> select count(*) from t1;
+----------+
| count(*) |
+----------+
| 36500000 |
+----------+
1 row in set (23.66 sec)

-- 쿼리 캐시를 적용함 

mysql> select SQL_CACHE count(*) from t1;
+----------+
| count(*) |
+----------+
| 36500000 |
+----------+
1 row in set (23.42 sec)

-- 캐시된 결과를 리턴하므로 매우 빠름

mysql> select SQL_CACHE count(*) from t1;
+----------+
| count(*) |
+----------+
| 36500000 |
+----------+
1 row in set (0.00 sec)

-- 관련 Stat 수치가 증가하는 것을 확인할 수 있음

mysql> show status where Variable_name like 'Qcache_hits%';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| Qcache_hits   | 3     |
+---------------+-------+

-- 대소문자를 구분함

mysql> SELECT SQL_CACHE count(*) from t1;
+----------+
| count(*) |
+----------+
| 36500000 |
+----------+
1 row in set (21.67 sec)

-- 스페이스도 구분함 

mysql> SELECT  SQL_CACHE count(*) from t1;
+----------+
| count(*) |
+----------+
| 36500000 |
+----------+
1 row in set (22.73 sec)

-- 쿼리 블록 단위로는 사용이 불가능함.

mysql> select max(c1) from (select SQL_CACHE count(*) c1 from t1) a;
ERROR 1234 (42000): Incorrect usage/placement of 'SQL_CACHE'

-- 테이블에 변경이 생기면 Query Cache는 Invalid 상태가 됨

mysql> delete from t1 limit 1;

mysql> select SQL_CACHE count(*) from t1;
+----------+
| count(*) |
+----------+
| 36499999 |
+----------+
1 row in set (23.03 sec)


저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

MySQL PK 칼럼 순서 설정에 따른 성능 비교

MySQL 2017.04.20 21:26 Posted by 시연아카데미

이전 포스팅에서 언급한 것과 같이 MySQL InnoDB에 테이블을 생성하면 항상 Clustered Index로 생성됩니다. MySQL 개발자들이 이런 선택을 한 합리적인 이유도 있을 것이나 이로 인한 성능상의 문제점도 존재합니다. PK를 지정하지 않아도 내부적인 Hidden Clustered Index를 생성하므로, 이왕 생성할 거면 상황에 맞는 적절한 칼럼으로 PK를 지정함으로써, 레코드를 PK 칼럼으로 정렬하는 것이 좋습니다.


이번 포스팅에서 살펴볼 내용은 PK를 결합 인덱스로 생성할 때, 칼럼 순서에 따른 Insert 속도와 조회 속도 비교입니다.


상식적으로 생각해보면 Index Split이 적게 발생하는 순서로 PK를 설정하는 것이 Insert 성능에는 유리할 것이고, 조회 패턴에서 Equal 조건이 선두 칼럼으로 구성하는 것이 조회 속도에는 유리할 것입니다. 이 2가지를 모두 만족하면 좋겠지만, 이 2가지 사항이 서로 배치될 때는 상황에 맞는, 그리고 손익 계산을 통해 적절한 선택을 해야 합니다. 물론, 이때 손해 보는 부분에 대한 다른 대안이 있는지도 고려 대상이겠죠. 이렇듯 튜닝 관점에서 보면, 단순해 보이는 하나의 결정도 여러 가지 경우의 수를 고려해서 선택해야 합니다.


그럼 본론으로 들어가겠습니다.


테스트 시나리오


10만 명의 고객이 매일 1건씩 상품을 구매한 내역을 저장한 테이블이 있다고 가정하겠습니다. 조회 조건은 고객 번호가 Equal 조건으로 입력되고 날자 조건은 대략 세 달 정도의 기간으로 들어옵니다. 이런 경우, INSERT 성능을 빠르게 하려면 log_date + cust_no로 PK를 생성하고, 조회 속도를 빠르게 하려면 cust_no + log_date로 PK를 생성하는 것이 좋습니다.(당연하죠?)

그럼 입력 성능과 조회 성능의 차이가 얼마나 나는지 비교해보겠습니다.


-- 테이블 생성 
1) T1 테이블은 CUST_NO  + LOG_DATE로 PK 생성
2) T2 테이블은 LOG_DATE + CUST_NO로  PK 생성

drop table t1;
drop table t2;

create table t1 (cust_no integer, log_date date, dummy char(100), primary key(cust_no, log_date));
create table t2 (cust_no integer, log_date date, dummy char(100), primary key(log_date, cust_no));

-- T1 테이블 용 프로시저 생성 

delimiter $$
drop procedure if exists insert_t1_proc$$
create procedure insert_t1_proc()
begin
   declare i_log_date int default 1;
   declare i_cust_no  int default 1;
   
   while (i_log_date <= 365) do  -- 1년 기간
       while (i_cust_no <= 100000) do -- 고객 10만명 
	       insert into t1 values(i_cust_no, date_add(curdate(), interval i_log_date day) , 'dummy');
		   set i_cust_no=i_cust_no+1;
	   end while;
	   set i_log_date=i_log_date+1;
	   set i_cust_no=1;
   end while;
end $$


-- T2 테이블 용 프로시저 생성 
delimiter $$
drop procedure if exists insert_t2_proc$$
create procedure insert_t2_proc()
begin
   declare i_log_date int default 1;
   declare i_cust_no  int default 1;
   
   while (i_log_date <= 365) do  -- 1년 기간
       while (i_cust_no <= 100000) do -- 고객 10만명 
	       insert into t2 values(i_cust_no, date_add(curdate(), interval i_log_date day) , 'dummy');
		   set i_cust_no=i_cust_no+1;
	   end while;
	   set i_log_date=i_log_date+1;
	   set i_cust_no=1;
   end while;
end $$

delimiter ;

-- 입력 성능 비교 결과
-- T1 테이블에 3650만 건 입력 시에는 3시간 3분이 소요되고 T2 테이블 입력 시에는 47분이 소요됐습니다.
-- T1 테이블에 대한 입력이 3배가량 늦다는 것을 알 수 있습니다. 

mysql> start transaction;
mysql> call insert_t1_proc();
Query OK, 1 row affected (3 hours 3 min 22.15 sec)

mysql> commit;

mysql> start transaction;
mysql> call insert_t2_proc();
Query OK, 1 row affected (47 min 10.63 sec)

mysql> commit;


-- PK 인덱스 크기 비교
-- T1 테이블의 PK가 50%가량 큽니다. 이는 잦은 Index Block Split 때문인 것 같습니다.
 
select database_name, table_name, index_name, round(stat_value*@@innodb_page_size/1024/1024,0) as "size(mb)"
from   mysql.innodb_index_stats
where stat_name='size' and table_name in ('t1','t2');
+---------------+------------+------------+----------+
| database_name | table_name | index_name | size(mb) |
+---------------+------------+------------+----------+
| mydb01        | t1         | PRIMARY    |     7559 |
| mydb01        | t2         | PRIMARY    |     4759 |
+---------------+------------+------------+----------+

-- Explain 결과 확인 
-- 실제 Run 테스트 전에 Explain 결과를 확인해보면, 2개의 쿼리 모두 PK 인덱스를 이용한다는 것을 알 수 있습니다.

explain 
select * 
from   t1 
where  cust_no=99 
and    log_date between str_to_date('20170420','%Y%m%d') 
                    and str_to_date('20170731','%Y%m%d')\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: t1
   partitions: NULL
         type: range
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 7
          ref: NULL
         rows: 103
     filtered: 100.00
        Extra: Using where

explain 
select * 
from   t2
where  cust_no=99 
and    log_date between str_to_date('20170420','%Y%m%d') 
                    and str_to_date('20170731','%Y%m%d')\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: t2
   partitions: NULL
         type: range
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 7
          ref: NULL
         rows: 1
     filtered: 100.00
        Extra: Using where

		
		
-- DB 재기동, 캐시 클리어 후에 T1 테이블 조회용 쿼리 Run 테스트 
-- 아래의 수행 결과를 보면, 1번째 쿼리 속도는 0.00초, 2번째 쿼리 속도는 7.8초 정도입니다. 
-- 그리고 Disk IO 블록 수도 큰 차이를 보입니다.
stop
sync;  echo 1 > /proc/sys/vm/drop_caches;
start

set profiling=1;

select * 
from   t1 
where  cust_no=99 
and    log_date between str_to_date('20170420','%Y%m%d') 
                    and str_to_date('20170731','%Y%m%d');
103 rows in set (0.00 sec)

show profiles;
+----------+------------+----------------------------------------+
| Query_ID | Duration   | Query                                  |
+----------+------------+----------------------------------------+
|        1 | 0.00281925 | select * from t1 where cust_no=99 ...  |
+----------+------------+----------------------------------------+

show profile cpu, block io for query 1;
+----------------------+----------+----------+------------+------------+---------------+
| Status               | Duration | CPU_user | CPU_system | Block_ops_in | Block_ops_out |
+----------------------+----------+----------+------------+--------------+---------------+
| starting             | 0.000126 | 0.000000 |   0.000000 |            0 |             0 |
| checking permissions | 0.000013 | 0.000000 |   0.000000 |            0 |             0 |
| Opening tables       | 0.000016 | 0.000000 |   0.000000 |            0 |             0 |
| init                 | 0.000032 | 0.000000 |   0.000000 |            0 |             0 |
| System lock          | 0.000008 | 0.000000 |   0.000000 |            0 |             0 |
| optimizing           | 0.000014 | 0.000000 |   0.000000 |            0 |             0 |
| statistics           | 0.002424 | 0.000000 |   0.000000 |          120 |             0 |
| preparing            | 0.000026 | 0.000000 |   0.000000 |            0 |             0 |
| executing            | 0.000003 | 0.000000 |   0.000000 |            0 |             0 |
| Sending data         | 0.000104 | 0.000000 |   0.001000 |            0 |             0 |
| end                  | 0.000004 | 0.000000 |   0.000000 |            0 |             0 |
| query end            | 0.000007 | 0.000000 |   0.000000 |            0 |             0 |
| closing tables       | 0.000006 | 0.000000 |   0.000000 |            0 |             0 |
| freeing items        | 0.000025 | 0.000000 |   0.000000 |            0 |             0 |
| cleaning up          | 0.000012 | 0.000000 |   0.000000 |            0 |             0 |
+----------------------+----------+----------+------------+--------------+---------------+

-- DB 재기동, 캐시 클리어 후에 T2 테이블 조회용 쿼리 Run 테스트 

stop
sync;  echo 1 > /proc/sys/vm/drop_caches;
start


set profiling=1;

select * 
from   t2 
where  cust_no=99 
and    log_date between str_to_date('20170420','%Y%m%d') 
                    and str_to_date('20170731','%Y%m%d');

103 rows in set (7.83 sec)

show profiles;
+----------+------------+----------------------------------------+
| Query_ID | Duration   | Query                                  |
+----------+------------+----------------------------------------+
|        1 | 7.82868050 | select * from t2 where cust_no=99 ...  |
+----------+------------+----------------------------------------+

show profile cpu, block io for query 1;
+----------------------+----------+----------+------------+--------------+---------------+
| Status               | Duration | CPU_user | CPU_system | Block_ops_in | Block_ops_out |
+----------------------+----------+----------+------------+--------------+---------------+
| starting             | 0.000153 | 0.000000 |   0.000000 |            0 |             0 |
| checking permissions | 0.000018 | 0.000000 |   0.000000 |            0 |             0 |
| Opening tables       | 0.000023 | 0.000000 |   0.000000 |            0 |             0 |
| init                 | 0.000045 | 0.000000 |   0.000000 |            0 |             0 |
| System lock          | 0.000009 | 0.000000 |   0.000000 |            0 |             0 |
| optimizing           | 0.000016 | 0.000000 |   0.000000 |            0 |             0 |
| statistics           | 0.011644 | 0.000000 |   0.001000 |         1848 |             0 |
| preparing            | 0.000050 | 0.000000 |   0.000000 |            0 |             0 |
| executing            | 0.000003 | 0.000000 |   0.000000 |            0 |             0 |
| Sending data         | 7.816537 | 1.690000 |   3.313000 |      2723232 |             0 |
| end                  | 0.000015 | 0.000000 |   0.000000 |            0 |             0 |
| query end            | 0.000010 | 0.000000 |   0.000000 |            0 |             0 |
| closing tables       | 0.000007 | 0.000000 |   0.000000 |            0 |             0 |
| freeing items        | 0.000036 | 0.000000 |   0.000000 |            0 |             0 |
| logging slow query   | 0.000099 | 0.001000 |   0.000000 |            0 |             0 |
| cleaning up          | 0.000017 | 0.000000 |   0.000000 |            0 |             0 |
+----------------------+----------+----------+------------+--------------+---------------+


그렇다면 2번째 쿼리는 어떤 식으로 성능 개선을 할 수 있을까요? 이는 일반적인 쿼리 튜닝과 동일합니다. 즉, log_date를 IN 조건으로 입력함으로써 Between 범위 대신 Equal 조건 효과를 내는 것입니다. 아래와 같이 수행하면, 0.07초 정도에 수행되며 Disk IO 블록 수도 대폭 감소한 것을 알 수 있습니다. 그런데 이 방법은 log_date 조건이 "날자"인 경우에만 가능한 방법입니다. 만일, "날자:시:분:초" 형식이라면 이 방법도 적용할 수 없습니다.


create table log_date_master(log_date date);

delimiter $$
drop procedure if exists insert_log_date_master_proc$$
create procedure insert_log_date_master_proc()
begin
   declare i_log_date int default 1;
   declare i_cust_no  int default 1;
   
   while (i_log_date <= 365) do  -- 1년 기간
	  insert into log_date_master values(date_add(curdate(), interval i_log_date day));
	  set i_log_date=i_log_date+1;
   end while;
end $$

delimiter ; 

call insert_log_date_master_proc();


stop
sync;  echo 1 > /proc/sys/vm/drop_caches;
start

set profiling=1;

select * 
from  t2 
where cust_no=99 
and   log_date in (select log_date 
                   from   log_date_master 
				   where  log_date between str_to_date('20170420','%Y%m%d') and str_to_date('20170731','%Y%m%d'));
102 rows in set (0.07 sec)

show profiles;
+----------+------------+----------------------------------------+
| Query_ID | Duration   | Query                                  |
+----------+------------+----------------------------------------+
|        1 | 0.06814325 | select * from t2 where cust_no=99 ...  |
+----------+------------+----------------------------------------+

show profile cpu, block io for query 1;
+----------------------+----------+----------+------------+--------------+---------------+
| Status               | Duration | CPU_user | CPU_system | Block_ops_in | Block_ops_out |
+----------------------+----------+----------+------------+--------------+---------------+
| starting             | 0.000890 | 0.000000 |   0.000000 |           72 |             0 |
| checking permissions | 0.000014 | 0.000000 |   0.000000 |            0 |             0 |
| checking permissions | 0.000063 | 0.000000 |   0.000000 |            0 |             0 |
| Opening tables       | 0.000028 | 0.000000 |   0.000000 |            0 |             0 |
| init                 | 0.000061 | 0.000000 |   0.000000 |            0 |             0 |
| System lock          | 0.000010 | 0.000000 |   0.000000 |            0 |             0 |
| optimizing           | 0.000017 | 0.000000 |   0.000000 |            0 |             0 |
| statistics           | 0.002837 | 0.001000 |   0.000000 |          472 |             0 |
| preparing            | 0.000035 | 0.000000 |   0.000000 |            0 |             0 |
| executing            | 0.000005 | 0.000000 |   0.000000 |            0 |             0 |
| Sending data         | 0.064098 | 0.001000 |   0.009000 |         6368 |             0 |
| end                  | 0.000015 | 0.000000 |   0.000000 |            0 |             0 |
| query end            | 0.000009 | 0.000000 |   0.000000 |            0 |             0 |
| removing tmp table   | 0.000006 | 0.000000 |   0.000000 |            0 |             0 |
| query end            | 0.000003 | 0.000000 |   0.000000 |            0 |             0 |
| closing tables       | 0.000008 | 0.000000 |   0.000000 |            0 |             0 |
| freeing items        | 0.000034 | 0.000000 |   0.000000 |            0 |             0 |
| cleaning up          | 0.000013 | 0.000000 |   0.000000 |            0 |             0 |
+----------------------+----------+----------+------------+--------------+---------------+


저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
TAG index, MySQL

MySQL의 Secondary Index에는 PK 칼럼이 포함될까?

MySQL 2017.04.20 16:27 Posted by 시연아카데미

MySQL의 InnoDB의 특징 중 하나는 모든 테이블을 Clustered Index로 생성한다는 것입니다.


즉, 모든 테이블이 인덱스라는 것이죠. ORACLE로 치면 IOT입니다.


따라서 튜닝 시에는 이러한 속성을 잘 이용해야 합니다. 다시말해, Clustered Index는 특정 칼럼으로 정렬을 유지한다는 속성이 있으므로 이 특성을 잘 이용해야 합니다. (이 부분은 별도 포스팅 예정입니다)


MySQL은 PK 칼럼을 이용해서 데이터를 정렬합니다. 만일 PK가 없다면 첫 번째 Unique Key 칼럼을 이용합니다. 만일 이마저도 없다면 내부적으로 6바이트짜리 키를 생성한 후에 데이터를 정렬하는 방식을 사용합니다.


그런데 Clustered Index (또는 IOT)의 문제점 중의 하나는 Secondary Index를 처리하는 것이 조금 힘들다는 점입니다. 왜냐면 Clustered Index는 빈번하게 Split이 발생하므로 레코드가 저장된 물리적인 블록의 위치가 쉽게 바뀌기 때문인데요.


이를 위해 ORACLE은 Direct Access, Guessing, PK 이용의 기법을 사용합니다. 그런데 MySQL 매뉴얼에 보면 MySQL은 Secondary Index 생성 시에 PK 칼럼을 추가한다는 내용이 있습니다. (헉!)


정말 그럴까요? 정말 그렇습니다. 테스트 내용은 아래를 참고하세요.




-- 테이블 생성(t1 테이블 PK 칼럼을 Integer, t1 테이블 PK 칼럼은 CHAR(100))

drop table t1;
drop table t2;

create table t1 (c1 integer,   c2 integer, dummy char(100), primary key(c1));
create table t2 (c1 char(100), c2 integer, dummy char(100), primary key(c1));

-- T1 테이블에 100만 건 입력용 프로시저 생성 

delimiter $$
drop procedure if exists insert_t1_proc$$
create procedure insert_t1_proc()
begin
   declare i int default 1;
   
   while (i <= 1000000) do
       insert into t1 values(i, i, 'dummy');
	   set i=i+1;
   end while;
end $$

-- T2 테이블에 100만 건 입력용 프로시저 생성

drop procedure if exists insert_t2_proc$$
create procedure insert_t2_proc()
begin
   declare i int default 1;
   
   while (i <= 1000000) do
       insert into t2 values(lpad(i,100,'0'), i, 'dummy');
	   set i=i+1;
   end while;
end $$

delimiter ;

-- 프로시저 수행  

start transaction;
call insert_t1_proc();
commit;

start transaction;
call insert_t2_proc();
commit;

-- 현재 인덱스 크기 비교 (현재 T2 테이블에 생성된 PK의 크기가 2배 가량 큽니다)

analyze table t1;
analyze table t2;

select database_name, table_name, index_name, round(stat_value*@@innodb_page_size/1024/1024,0) as "size(mb)"
from   mysql.innodb_index_stats
where stat_name='size' and table_name in ('t1','t2');

+---------------+------------+------------+----------+
| database_name | table_name | index_name | size(mb) |
+---------------+------------+------------+----------+
| mydb01        | t1         | PRIMARY    |      132 |
| mydb01        | t2         | PRIMARY    |      233 |
+---------------+------------+------------+----------+

-- secondary index 생성 및 크기 비교 (T2의 PK의 크기가 큰 탓에 T2의 Secondary Index의 크기가 T1에 비해서 무려 7배 가량 큽니다)

create index t1_idx01 on t1(c2);
create index t2_idx01 on t2(c2);

analyze table t1;
analyze table t2;

select database_name, table_name, index_name, round(stat_value*@@innodb_page_size/1024/1024,0) as "size(mb)"
from   mysql.innodb_index_stats
where stat_name='size' and table_name in ('t1','t2');
+---------------+------------+------------+----------+
| database_name | table_name | index_name | size(mb) |
+---------------+------------+------------+----------+
| mydb01        | t1         | PRIMARY    |      132 |
| mydb01        | t1         | t1_idx01   |       17 |
| mydb01        | t2         | PRIMARY    |      233 |
| mydb01        | t2         | t2_idx01   |      123 |
+---------------+------------+------------+----------+

저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
TAG index, MySQL

제목이 너무 자극적이죠?


MySQL 성능 테스트를 위해 Loop Insert 하는 Procedure를 생성했는데 처리 속도가 너무 느렸습니다. 100만 건 입력하는데 10분 이상 소요됐으니까요.

같은 유형의 프로시저를 PostgreSQL에서 수행하면 10초 이내에 끝나는 것에 비하면 극심한 성능 차이가 발생하는 것인데요.


이 문제를 처음 접했을 때는 MySQL의 InnoDB 엔진의 특성상 모든 테이블을 클러스터 인덱스로 생성하는 이유 때문인 것으로 추정했었습니다. 즉, Index Split 현상 떄문에 입력 시의 지연 현상이 발생한다고 생각했으나 결과적으로 이 문제는 아니었습니다. (물론, 이로 인한 약간의 성능 손실은 있습니다)


문제의 원인은 MySQL이 Loop를 처리하는 방식 때문입니다. PostgreSQL은 프로시저 내의 Loop를 1-Transaction으로 처리합니다. 그런데 MySQL은 매건 COMMIT을 하는 방식으로 수행합니다.


따라서 이 문제를 해결하는 법은 간단합니다. Loop 수행 전에 SET TRANSACTION을 지정하거나 SET AUTOCOMMIT=OFF로 설정하면 됩니다. 간단하죠?


아래는 테스트를 위한 스크립트입니다.


-- 테이블 및 프로시저 생성 

drop table t1;
create table t1 (c1 integer, c2 integer, dummy char(100));

delimiter $$
drop procedure if exists insert_t1_proc$$
create procedure insert_t1_proc()
begin
   declare i int default 1;
   declare j int default 0;
   
   while (i <= 10000) do
       while (j <= 99) do
	       insert into t1 values(i+(j*10000), i+(j*10000), 'dummy');
		   set j=j+1;
	   end while;
	   set i=i+1;
	   set j=0;
   end while;
end $$

delimiter ;

-- 프로시저 수행 결과 (747초나 소요됨)

call insert_t1_proc();
Query OK, 1 row affected (12 min 27.08 sec) (747초)


-- START TRANSACTION 적용 후의 수행 결과 (19초로 단축됨)

start transaction;
call insert_t1_proc(); 
Query OK, 1 row affected (19.20 sec)
commit;


저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

MySQL은 튜닝 대상 SQL을 수집하기 위해 Slow Query Log 기능을 제공합니다. (설정 방법은 여기 참조)

 

Slow Query Log는 파일 및 테이블에 기록이 가능합니다만, 아마 운영 환경에서는 관리 문제로 인해서 주로 파일에 기록할 것으로 보입니다. 이렇게 파일에 기록된 Slow Query Log를 눈으로 확인하는 것은 매우 힘든데요.

 

percona 툴킷에서 제공하는 pt-query-digest 명령어를 이용하면 이러한 작업을 매우 효율적으로 수행할 수 있습니다. 뿐만 아니라 Literal SQL (띄어쓰기가 다르거나 심지어 대소문자가 달라도)을 하나의 SQL로 묶어서 리포트를 해주므로 분석 시에 매우 유용할 것 같습니다.

 

아래와 같이 Literal SQL, 띄어쓰기, 대소문자가 다른 SQL을 각각 수행 (총 4번 수행)

select * from  (select c1 from t1 where c1=1 group by c1) a, t2 b where a.c1=b.c1;
select * from  (select c1 from t1 where c1=2 group by c1) a, t2 b where a.c1=b.c1;
SELECT * FROM (SELECT c1 FROM t1 WHERE c1=2 GROUP BY c1) a, t2 b WHERE a.c1=b.c1;
SELECT * FROM        (SELECT c1 FROM t1 WHERE c1=2 GROUP BY c1) a, t2 b WHERE a.c1=b.c1;

 

 

pt-query_digest로 분석 수행

$ pt-query-digest /var/log/mysql/mysql-slow.log

 

아래의 결과에서 보는것과 같이, Overall:4, Count:4 입니다. 즉, 위의 4개의 SQL이 1개로 Grouping되서 리포트되는 것을 알 수 있습니다.

# Overall: 4 total, 1 unique, 0.04 QPS, 0.06x concurrency ________________
# Time range: 2017-04-12T21:32:24 to 2017-04-12T21:34:08
# Attribute          total     min     max     avg     95%  stddev  median
# ============     ======= ======= ======= ======= ======= ======= =======
# Exec time             6s      1s      2s      2s      2s    31ms      2s
# Lock time          481us   108us   149us   120us   144us    16us   108us
# Rows sent            385       1     128   96.25  124.25   53.37  124.25
# Rows examine      16.76M   4.19M   4.19M   4.19M   4.06M       0   4.06M
# Query size           330      80      88   82.50   84.10    2.76   80.10



# Attribute    pct   total     min     max     avg     95%  stddev  median
# ============ === ======= ======= ======= ======= ======= ======= =======
# Count        100       4
# Exec time    100      6s      1s      2s      2s      2s    31ms      2s
# Lock time    100   481us   108us   149us   120us   144us    16us   108us
# Rows sent    100     385       1     128   96.25  124.25   53.37  124.25
# Rows examine 100  16.76M   4.19M   4.19M   4.19M   4.06M       0   4.06M
# Query size   100     330      80      88   82.50   84.10    2.76   80.10
# String:
# Hosts        localhost
# Users        root
# Query_time distribution
#   1us
#  10us
# 100us
#   1ms
#  10ms
# 100ms
#    1s  ################################################################
#  10s+
# Tables
#    SHOW TABLE STATUS LIKE 't1'\G
#    SHOW CREATE TABLE `t1`\G
# EXPLAIN /*!50100 PARTITIONS*/
select * from  (select c1 from t1 where c1=2 group by c1) a, t2 b where a.c1=b.c1\G

 

저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

MySQL에서 PostgreSQL의 generate_series() 흉내내기

MySQL 2017.04.13 05:49 Posted by 시연아카데미

테스트 데이터 Set을 만들때, 일련 번호를 생성하는 PostgreSQL의 generate_series() 함수는 매우 유용합니다. 그런데 MySQL은 이런 함수를 내장하고 있지 않습니다. (조금 불편하네요)

 

이를 보완하기 위해서 Function을 만들어서 공유해준 분이 있습니다.

 

 

Function은 많은 기능들을 제공하나, 일련 번호 생성을 위해서는 이 중에서 generate_series_base(), generate_series_n_base(), generate_series() 함수만 생성하면 됩니다.

 

generate_series() 함수 수행 후에는 해당 결과가 series_tmp 테이블에 저장됩니다. 따라서 다음과 같은 순서로 일련 번호를 테이블에 입력하면 됩니다.

 

  1. CALL generate_series(1,100000);
  2. insert into t1 (c1) select * from series_tmp;

 

저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

MySQL의 Explain 결과를 보는 몇 가지 방법들

MySQL 2017.04.13 04:32 Posted by 시연아카데미

드디어 MySQL 성능 이야기를 시작합니다.

 

성능 이야기의 첫 주제는 Explain입니다. 튜닝의 시작 단계에서는 Explain을 잘 활용해야 하기 떄문입니다.

 

그런데 MySQL의 Explain은 조금 당황스럽네요. 지금 MySQL 필드를 뛰시는 튜너 분들은 Explain 결과를 이용해서 어떤식으로 튜닝을 진행하는지가 궁금해졌습니다.

 

왜냐면 MySQL의 Explain은 Run-Time 결과를 제공하지 않기 때문입니다. 다시 말해, Explain 결과 중에서 속도가 느리거나, Disk IO, 또는 Buffer IO가 과다한 Step을 찾을 수 없습니다.

 

물론, profiling을 이용해서 SQL 레벨의 수행 시간, CPU Time, Disk IO Block 수는 확인할 수 있지만, 쿼리 내의 문제 부분을 특정할 수 없다는 것은 튜닝 작업의 걸림돌이 되는 것만은 확실할 것 같습니다.

 

그리고 기본적으로 제공되는 Explain의 결과는 테이블 Format입니다. 즉, 트리구조로 제공되지 않습니다. 이 역시 가독성을 떨어뜨리는 요소입니다.

 

이를 조금이나마 보완하는 방법은 3가지입니다.

 

1. JSON Format (이 역시 JSON에 익숙하지 않으면 보기 어려움)

2. pt-visual-explain (percona toolkit 설치 필요)

   - 해당 Toolkit은 PERL을 사용하므로 PERL 관련 모듈 몇 개(DBI, DBD::mysql)를 설치해야 합니다.   

3. MySQL Workbench의 Visual Explain (JSON을 그래픽으로 변환해서 보여주는 기능)

 

출력 결과는 다음과 같습니다.

 

1. JSON Format

 

| {
  "query_block": {
    "select_id": 1,
    "cost_info": {
      "query_cost": "3.60"
    },
    "nested_loop": [
      {
        "table": {
          "table_name": "a",
          "access_type": "ALL",
          "rows_examined_per_scan": 1,
          "rows_produced_per_join": 1,
          "filtered": "100.00",
          "cost_info": {
            "read_cost": "1.00",
            "eval_cost": "0.20",
            "prefix_cost": "1.20",
            "data_read_per_join": "8"
          },
          "used_columns": [
            "c1"
          ],
          "attached_condition": "(`mydb01`.`a`.`c1` is not null)"
        }
      },
      {
        "table": {
          "table_name": "c",
          "access_type": "ALL",
          "rows_examined_per_scan": 1,
          "rows_produced_per_join": 1,
          "filtered": "100.00",
          "using_join_buffer": "Block Nested Loop",
          "cost_info": {
            "read_cost": "1.00",
            "eval_cost": "0.20",
            "prefix_cost": "2.40",
            "data_read_per_join": "8"
          },
          "used_columns": [
            "c1"
          ],
          "attached_condition": "(`mydb01`.`c`.`c1` = `mydb01`.`a`.`c1`)"
        }
      },
      {
        "table": {
          "table_name": "b",
          "access_type": "ref",
          "possible_keys": [
            "t2_idx01"
          ],
          "key": "t2_idx01",
          "used_key_parts": [
            "c1"
          ],
          "key_length": "5",
          "ref": [
            "mydb01.a.c1"
          ],
          "rows_examined_per_scan": 1,
          "rows_produced_per_join": 1,
          "filtered": "100.00",
          "using_index": true,
          "cost_info": {
            "read_cost": "1.00",
            "eval_cost": "0.20",
            "prefix_cost": "3.60",
            "data_read_per_join": "8"
          },
          "used_columns": [
            "c1"
          ]
        }
      }
    ]
  }
} |

2. pt-visual-explain

[root@pgserver ~]# mysql -u root -p mydb01 -e "explain select * from t1 a, t2 b, t3 c where a.c1=b.c1 and a.c1=c.c1" | pt-visual-explain
Enter password:
JOIN
+- Index lookup
|  key            b->t2_idx01
|  possible_keys  t2_idx01
|  key_len        5
|  ref            mydb01.a.c1
|  rows           1
+- JOIN
   +- Join buffer
   |  +- Filter with WHERE
   |     +- Table scan
   |        rows           1
   |        +- Table
   |           table          c
   +- Filter with WHERE
      +- Table scan
         rows           1
         +- Table
            table          a

 

3. MySQL Workbench의 Visual Explain

 

 

 

 

참고사이트

 

http://stackoverflow.com/questions/36305809/is-there-a-way-to-show-mysql-explain-plan-like-oracle-as-a-tree

 

 

사족

 

새로운 블로그에만 기술 컨텐츠를 작성하려고 했으나, 워드프레스가 구글 검색이 잘 안 되는 문제가 있습니다. SEO가 잘된다고 해서 워드프레스를 선택한 이유도 있는데, 무료라서 그런 것인지? 아니면 설치형이 아닌 가입형이라서 그런 것인지? 동일한 내용을 구글 웹마스터에서 크롤링을 시켜봐도 워드프레스가 티스토리에 비해서 검색이 잘 안됩니다. 따라서 약간의 공수가 더 들겠지만, 동일한 내용은 양쪽 블로그에 모두 포스팅하도록 하겠습니다.

저작자 표시 비영리 변경 금지
신고
크리에이티브 커먼즈 라이선스
Creative Commons License


 

티스토리 툴바