percona에서 새로 소개한 binlog2sql에 대해 설명한다.
[출처] www.percona.com/blog/2020/07/09/binlog2sql-binlog-to-raw-sql-conversion-and-point-in-time-recovery/

 

1. 개요

mysqlbinlog 툴을 사용하면 binary log를 binary에서 텍스트 형식으로 변환할 수 있다.
"binlog_format & binlog_rows_query_log_events "매개 변수 에 따라 다르지만 mysqlbinlog 유틸리티를 사용하여 정확한 SQL 문을 생성할 수도 있다.
하지만 mysqlbinlog 툴에는 ROLLBACK 문을 생성하는 기능이 없다.
percona binlog2sql은 MySQL binary log를 디코딩하고 raw SQL을 추출하며, 플래시 백 기능을 사용하여 PITR (Point-Time Recovery)을 위한 ROLLBACK 문을 생성한다.

 

 

2. binlog2sql은 무엇인가?

  • binary log를 구문 분석하는 오픈 소스 도구다.
  • binary log에서 raw SQL 문을 추출하는 기능이 있다.
  • 특정 시점 복구를 위해 binary log에서 ROLLBACK SQL을 생성하는 기능이 있다.

 

 

3. Mysqlbinlog vs Binlog2sql

binlog2sql 도구로 이동하기 전에 Mysqlbinlog와 bilog2sql의 출력을 비교한다.
아래 쿼리를 사용하여 binlog 이벤트를 생성한다.

 

3.1. Mysqlbinlog ( binlog_format = STATEMENT )

> mysqlbinlog --base64-output=decode-rows -vv --start-position 1682 --stop-position 2009 /log/mysql-log/mysql-bin.000005 >> test1.log

SQL 형태로 결과물을 제공한다.

 

3.2 Mysqlbinlog ( binlog_format=ROW, binlog_rows_query_log_events=OFF )

> mysqlbinlog --base64-output=decode-rows -vv --start-position 1629 --stop-position 1903 /log/mysql-log/mysql-bin.000005 >> test2.log

텍스트 형식이긴 하지만 SQL 형태는 아니다.

 

3.3. Mysqlbinlog ( binlog_format=ROW and binlog_rows_query_log_events=ON )

> mysqlbinlog --base64-output=decode-rows -vv --start-position 2325 --stop-position 2669 /log/mysql-log/mysql-bin.000005 >> test3.log

SQL 형태로 결과물을 제공한다.

 

3.4. Binlog2sql

> ./binlog2sql-master/binlog2sql/binlog2sql.py --user root -p --start-file mysql-bin.000005

SQL 형태로 결과물을 제공한다.

 

 

4. MySQL 요구 사항

다음 파라미터를 활성화한다.

binlog_format = row
binlog_row_image = full

binlog2sql은 BINLOG_DUMP 프로토콜을 기반으로 binlog 컨텐츠를 얻는다.
binlog2sql은 서버에서 INFORMATION_SCHEMA.COLUMNS 테이블을 읽기 위해 메타 데이터 테이블이 필요하다.

 

 

5. 설치

5.1. 테스트 환경

파이선 2.7
MySQL 5.7.28
참고 : MySQL 8 이상을 지원하려면 “pymysql – 0.9.3”이 있어야 한다.

다음 명령어를 통해 설치 가능하다.

git clone https://github.com/danfengcao/binlog2sql.git && cd binlog2sql
pip install -r requirements.txt

 

5.2. Binlog2sql 옵션

 

6. Binary Log에서 Raw SQL 추출하는 방법

> create table binlog2sql(id int primary key auto_increment, name varchar(16), status enum('A','NA'), up_date datetime default current_timestamp);
Query OK, 0 rows affected (0.01 sec)

> insert into binlog2sql (name,status) values ('kani','A'),('ram','A'),('durai','A'),('asha','A'),('sakthi','A');
Query OK, 5 rows affected (0.00 sec)
Records: 5 Duplicates: 0 Warnings: 0

./binlog2sql-master/binlog2sql/binlog2sql.py -uroot -p --start-file mysql-bin.000005 --start-position 3717 --stop-position 4209 | cut -f1 -d"#" 

 

7. binlog2sql의 PITR 사용법

Binlog2sql 툴에 “–flashback” 옵션을 사용하여 ROLLBACK 문을 생성한다.
INSERT, DELETE, UPDATE 문에서 데이터를 복구할 수 있다.
DROP, TRUNCATE는 binary logs를 사용할 수 없으므로 지원하지 않는다.

 

7.1. Case 1 – DELETE PITR :

테스트 테이블 "binlog2sql" 에서 "id in (4,5)"의 두 레코드를 삭제한다.

 

참고를 위해 binlog2sql을 사용하여 원시 SQL을 생성한다.

./binlog2sql-master/binlog2sql/binlog2sql.py -uroot -p --start-file mysql-bin.000005 --start-position 4209 --stop-position 4571 | cut -f1 -d"#" 

 

위의 DELETE는 레코드를 삭제하기 위해 실행된 명령문이다.

이제 아래와 같이 “–flashback”옵션을 사용하여 ROLLBACK 문을 생성한다.

./binlog2sql-master/binlog2sql/binlog2sql.py -uroot -p --start-file mysql-bin.000005 --start-position 4209 --stop-position 4571 --flashback | cut -f1 -d"#" 

해당 DELETE 문이 INSERT 문으로 변환된 것을 볼 수 있다.

 

이제  롤백 명령문을 사용하여 복구를 시도한다.

./binlog2sql-master/binlog2sql/binlog2sql.py -uroot -p --start-file mysql-bin.000005 --start-position 4209 --stop-position 4571 --flashback | cut -f1 -d"#" >> rollback.sql

mysql -uroot -p -D test -vvv < rollback.sql

DELETE에서 데이터를 복구했다.

 

7.2. Case 2 – UPDATE PITR :

테이블 "binlog2sql" 에서 "id in (4,5)"의 두 레코드의 상태를 “A”에서 “NA”로 변경했다.

 

참조를 위해 binlog2sql을 사용하여 실행된 UPDATE를 생성한다.

./binlog2sql-master/binlog2sql/binlog2sql.py -uroot -p --start-file mysql-bin.000005 --start-position 5409 --stop-position 5817 | cut -f1 -d"#" 

 

ROLLBACK 문을 생성한다.

./binlog2sql-master/binlog2sql/binlog2sql.py -uroot -p --start-file mysql-bin.000005 --start-position 5409 --stop-position 5817 --flashback | cut -f1 -d"#" 

 

이제  롤백 명령문을 사용하여 복구를 시도한다.

./binlog2sql-master/binlog2sql/binlog2sql.py -uroot -p --start-file mysql-bin.000005 --start-position 5409 --stop-position 5817 --flashback | cut -f1 -d"#" >> rollback.sql

mysql -uroot -p -D test -vvv < rollback.sql

변경 사항이 rollback 되었다.

 

 

8. 결론

앞에서 언급했듯이이 binlog2sql 도구는 INSERT, DELETE, UPDATE에 대해서만 롤백을 지원한다.
DDL 문은 binary log에 실제 데이터를 기록하지 않으므로 DDL을 지원하지 않는다.
binlog2sql은 암호화, 압축 binary log에서 작동하지 않는다.
binlog2sql은 MySQL 5.6 및 MySQL 5.7 환경에서 테스트되었다.

'MySQL' 카테고리의 다른 글

MySQL 정적 및 동적 권한  (0) 2021.07.29
MySQL 8.0 strage engine  (0) 2021.07.21
mysql 벤치 마크 툴 sysbench  (0) 2020.04.16
MySQL benchmark tool ( MySQL 성능 테스트 툴 ) DBT2  (0) 2020.03.19
optimizer trace  (0) 2019.11.26

sysbench는 CPU, memory, file I/O, mutex performance, MySQL 등 다양한 테스트가 가능 하고, 많은 레퍼런스 보유하고 있어 성능 진단에 유용하게 사용 된다.
sysbench를 이용해서 mysql 성능을 측정해보자

설치는 yum으로 간단하게 할 수 있다.

> sudo yum -y install sysbench

더보기

mysql@bench-test-utf8:/data/mytmp 11:28:09> sudo yum -y install sysbench
Loaded plugins: fastestmirror, langpacks, versionlock
Determining fastest mirrors
EPEL7                                                                                                                                    | 4.7 kB  00:00:00
base                                                                                                                                     | 3.6 kB  00:00:00
centosplus                                                                                                                               | 2.9 kB  00:00:00
extras                                                                                                                                   | 2.9 kB  00:00:00
update                                                                                                                                   | 2.9 kB  00:00:00
(1/6): EPEL7/7/x86_64/group_gz                                                                                                           |  95 kB  00:00:00
(2/6): EPEL7/7/x86_64/updateinfo                                                                                                         | 1.0 MB  00:00:00
(3/6): extras/7/x86_64/primary_db                                                                                                        | 165 kB  00:00:00
(4/6): centosplus/7/x86_64/primary_db                                                                                                    | 3.0 MB  00:00:00
(5/6): EPEL7/7/x86_64/primary_db                                                                                                         | 6.8 MB  00:00:00
(6/6): update/7/x86_64/primary_db                                                                                                        | 7.6 MB  00:00:00
Resolving Dependencies
--> Running transaction check
---> Package sysbench.x86_64 0:1.0.17-2.el7 will be installed
--> Processing Dependency: libck.so.0()(64bit) for package: sysbench-1.0.17-2.el7.x86_64
--> Processing Dependency: libluajit-5.1.so.2()(64bit) for package: sysbench-1.0.17-2.el7.x86_64
--> Running transaction check
---> Package ck.x86_64 0:0.5.2-2.el7 will be installed
---> Package luajit.x86_64 0:2.0.4-3.el7 will be installed
--> Finished Dependency Resolution

Dependencies Resolved

================================================================================================================================================================
 Package                               Arch                                Version                                     Repository                          Size
================================================================================================================================================================
Installing:
 sysbench                              x86_64                              1.0.17-2.el7                                EPEL7                              152 k
Installing for dependencies:
 ck                                    x86_64                              0.5.2-2.el7                                 EPEL7                               26 k
 luajit                                x86_64                              2.0.4-3.el7                                 EPEL7                              343 k

Transaction Summary
================================================================================================================================================================
Install  1 Package (+2 Dependent packages)

Total download size: 521 k
Installed size: 1.7 M
Downloading packages:
(1/3): ck-0.5.2-2.el7.x86_64.rpm                                                                                                         |  26 kB  00:00:00
(2/3): luajit-2.0.4-3.el7.x86_64.rpm                                                                                                     | 343 kB  00:00:00
(3/3): sysbench-1.0.17-2.el7.x86_64.rpm                                                                                                  | 152 kB  00:00:00
----------------------------------------------------------------------------------------------------------------------------------------------------------------
Total                                                                                                                           2.4 MB/s | 521 kB  00:00:00
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  Installing : luajit-2.0.4-3.el7.x86_64                                                                                                                    1/3
  Installing : ck-0.5.2-2.el7.x86_64                                                                                                                        2/3
  Installing : sysbench-1.0.17-2.el7.x86_64                                                                                                                 3/3
  Verifying  : ck-0.5.2-2.el7.x86_64                                                                                                                        1/3
  Verifying  : sysbench-1.0.17-2.el7.x86_64                                                                                                                 2/3
  Verifying  : luajit-2.0.4-3.el7.x86_64                                                                                                                    3/3

Installed:
  sysbench.x86_64 0:1.0.17-2.el7

Dependency Installed:
  ck.x86_64 0:0.5.2-2.el7                                                      luajit.x86_64 0:2.0.4-3.el7

Complete!
mysql@bench-test-utf8:/data/mytmp 11:28:32>

설치가 완료 되면 /usr/share/sysbench 경로에 벤치 마크 예제들이 생성 된다.
oltp_read_write.lua 파일을 이용해서 mysql DB의 oltp 성능을 측정해보자.

진행은 다음 순서로 한다.
1. prepare : 초기 기반 테이블 및 데이터을 설치
2. run : 수행 후 분석 결과 확인
3. cleanup : 설치한 모든 내용 제거

옵션은 다음과 같다.
--max-requests=[쿼리 요청 개수] 
--max-time=[테스트 시간]
--mysql-host=[호스트명] 
--mysql-db=[테스트 데이터베이스명] 
--mysql-user=[DB USER] 
--mysql-password=[DB USER 비밀번호] 
--mysql-table-engine=[테이블 엔진 종류] 
--mysql-socket=[MySQL 소켓 파일 경로] 
--mysql-port=[MySQL-포트-번호] 
--oltp-test-mode=[simple|complex|nontrx] 
--oltp-read-only=[on|off] 
--num-threads=[동시 스레드 개수]
--table_size=[데이터 건수]
--tables=[테이블 개수]

다음과 같이 실행 시간 기반으로 요청 수에 관계 없이 240초 동안 실행하고 결과를 출력한다.

sysbench --time=240 --max-requests=0 /usr/share/sysbench/oltp_read_write.lua \
--table_size=10000000 --tables=30 --report-interval=1 \
--mysql-host=localhost --mysql-port=3306 \
--db-driver=mysql --mysql-user=sysbench --mysql-password=sysbench \
--mysql-db=sysbench --mysql-socket=/tmp/mysql.sock --num-threads=110 \
prepare

sysbench --time=240 --max-requests=0 /usr/share/sysbench/oltp_read_write.lua \
--table_size=10000000000 --tables=15 --report-interval=1 \
--mysql-host=localhost --mysql-port=3306 \
--db-driver=mysql --mysql-user=sysbench --mysql-password=sysbench \
--mysql-db=sysbench --mysql-socket=/tmp/mysql.sock --num-threads=110 \
run

더보기

SQL statistics:
    queries performed:
        read:                            8899310
        write:                           641866
        other:                           3172110
        total:                           12713286
    transactions:                        635658 (2647.55 per sec.)
    queries:                             12713286 (52951.61 per sec.)
    ignored errors:                      7      (0.03 per sec.)
    reconnects:                          0      (0.00 per sec.)

General statistics:
    total time:                          240.0904s
    total number of events:              635658

Latency (ms):
         min:                                    1.78
         avg:                                   41.53
         max:                                  379.03
         95th percentile:                      106.75
         sum:                             26396240.17

Threads fairness:
    events (avg/stddev):           5778.7091/210.35
    execution time (avg/stddev):   239.9658/0.03

본 테스트 시스템에서는 threads가 130이 넘으면 에러가 발생하고, table_size가 1000000000000 이상이면 동작하지 않으므로 적절한 값으로 조절하여 수행 하면 된다.

다음 테스트는 time 240으로 table_size 와 threads에 변화에 따른 tps 수치 변화를 표로 만들어 보았다.

  threads=50 threads=120
table_size=1000000 transactions:  466736 (1944.34 per sec.)
queries:      9358012 (38983.83 per sec.)
transactions: 402868 (1677.81 per sec.)
queries:    8057378 (33556.21 per sec.)
table_size=10000000000 transactions: 639694 (2664.91 per sec.)
queries:   12793880 (53298.20 per sec.)
transactions: 614487 (2559.47 per sec.)
queries:    12289812 (51189.78 per sec.)

threads 값 보다는 table_size가 tps에 더 많은 영향을 미치고 오히려 threads가 높으면 Latency가 높아서 성능이 더 떨어지는 상황이 발생 하였다.

테스트시 수행되는 SQL의 패턴은 다음과 같다.

BEGIN;
SELECT c FROM sbtest WHERE id=N;
SELECT c FROM sbtest WHERE id BETWEEN N AND M;
SELECT SUM(K) FROM sbtest WHERE id BETWEEN N and M;
SELECT c FROM sbtest WHERE id between N and M ORDER BY c;
SELECT DISTINCT c FROM sbtest WHERE id BETWEEN N and M ORDER BY c;
UPDATE sbtest SET k=k+1 WHERE id=N;
UPDATE sbtest SET c=N WHERE id=M;
DELETE FROM sbtest WHERE id=N;
INSERT INTO sbtest VALUES (...);
COMMIT;

 

'MySQL' 카테고리의 다른 글

MySQL 8.0 strage engine  (0) 2021.07.21
binlog2sql: Binlog를 이용한 Point In Time Recovery  (0) 2020.08.04
MySQL benchmark tool ( MySQL 성능 테스트 툴 ) DBT2  (0) 2020.03.19
optimizer trace  (0) 2019.11.26
MySQL EXPLAIN ANALYZE  (0) 2019.10.02

mysql 벤치 마크 툴인 DBT2에 대해서 설정하고, 서버의 성능에 대하여 측정 해본다.

테스트에 사용된 DB : MySQL 5.7.28

DBT2 : dbt2-0.37.50.15

DBT2의 다운로드 사이트 : https://dev.mysql.com/downloads/benchmarks.html

 

MySQL :: MySQL Benchmark Tool

DBT2 Benchmark Tool The DBT2 Benchmark Tool can be used to run automated benchmarks for MySQL and MySQL Cluster. It supports three types of benchmarks: DBT2 SysBench flexAsynch It has been primarily used on Linux x86_64 platforms, but occasional benchmarks

dev.mysql.com

해당 사이트에서 툴을 다운로드 받은 후 테스트 할 서버로 업로드를 한다.

해당 툴의 압축을 해제 한다.

> cd cd /data/mytmp
> tar xzvf dbt2-0.37.50.15.tar.gz

해제한 툴을 설치 한다.

> cd /data/mytmp/dbt2-0.37.50.15/
> ./configure --with-mysql=/db/mysql/

> make

데이터 생성 및 데이터 로딩

> mkdir -p /data/mytmp/dbt2-0.37.50.15/output
> ./src/datagen -w 30 -d /data/mytmp/dbt2-0.37.50.15/output --mysql

mysql 에서 테스트에 사용할 유저 생성

> create user test@'localhost';
> grant all privileges on *.* to test@'localhost';

> create user test2@'%' identified by 'test2';
> grant all privileges on *.* to test2@'%';
> create user test2@'localhost' identified by 'test2';
> grant all privileges on *.* to test2@'localhost';

mysql 에 테스트 데이터 생성

> ./scripts/mysql/mysql_load_db.sh --mysql-path /db/mysql/bin/mysql --database dbt2 --path /data/mytmp/dbt2-0.37.50.15/output --socket /tmp/mysql.sock --user test --verbose

위와 같이 에러가 발생하는데 스크립트 상에 오류와 데이터에 문제가 좀 있어 수정이 필요하다.

mysql_load_db.sh 파일을 열어 다음 부분을 찾아 수정한다.

변경 전     command_exec "$MYSQL $DB_NAME -e \"LOAD DATA $LOCAL INFILE \\\"$DB_PATH/$FN.data\\\" \
              INTO TABLE $TABLE FIELDS TERMINATED BY '\t' ${COLUMN_NAMES} \""
젼경 후     command_exec "$MYSQL $DB_NAME -e \"LOAD DATA LOCAL INFILE \\\"$DB_PATH/$FN.data\\\" \
              INTO TABLE $TABLE character set latin1 FIELDS TERMINATED BY '\t' ${COLUMN_NAMES} \""

그리고 다시 수행

> ./scripts/mysql/mysql_load_db.sh --mysql-path /db/mysql/bin/mysql --database dbt2 --path /data/mytmp/dbt2-0.37.50.15/output --socket /tmp/mysql.sock --user test --verbose

프로시저 생성

> ./scripts/mysql/mysql_load_sp.sh --database dbt2 --sp-path ./storedproc/mysql/ --client-path /db/mysql/bin/ --user test2 --password test2

벤치마크 툴 수행

> ./scripts/run_mysql.sh -u test2 --password test2 -d dbt2 -s /tmp/mysql.sock -w 3 -t 300 -c 50  --lib-client-path /db/mysql/bin/mysql --verbose

워크로드가 적어 성능 측정이 되지 않는다.

적절하게 값을 늘려본다.

> ./scripts/run_mysql.sh -u test2 --password test2 -d dbt2 -s /tmp/mysql.sock -w 50 -t 300 -c 50  --lib-client-path /db/mysql/bin/mysql --verbose

해당 서버는 사양이 좋지 않아 분당 약 40 의 주문 트랜잭션 처리가 가능하다.

'MySQL' 카테고리의 다른 글

binlog2sql: Binlog를 이용한 Point In Time Recovery  (0) 2020.08.04
mysql 벤치 마크 툴 sysbench  (0) 2020.04.16
optimizer trace  (0) 2019.11.26
MySQL EXPLAIN ANALYZE  (0) 2019.10.02
MySQL Hash Join Optimization  (0) 2019.10.02

옵티마이저의 판단을 알기 위한 방법으로 explain 명령어를 사용한다. 하지만 explain 옵티마이저가 최종적으로 선택한 실행 계획 뿐이고, 과정에 최적화나 비용 계산, 또는 실행 계획의 비교가 어떻게 일어 났는지는 없다. 옵티마이저 트레이스에는 이러한 내용이 설명 되어있다.

mysql> explain select * from t1 join t3 on (t1.c1=t3.pk and t3.pk < 113);
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra                                              |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+----------------------------------------------------+
|  1 | SIMPLE      | t3    | NULL       | range | PRIMARY       | PRIMARY | 4       | NULL |    2 |   100.00 | Using where                                        |
|  1 | SIMPLE      | t1    | NULL       | ALL   | NULL          | NULL    | NULL    | NULL |   10 |    10.00 | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+----------------------------------------------------+
2 rows in set, 1 warning (0.00 sec)


옵티마이저
트레이스의 사용법은 간단하다.

  1. 옵티마이저 트레이스를 활성화한다.
  2. 해석하고 싶은 쿼리를 실행한다.
  3. 트레이스를 출력한다.

 

SQL을 수행하면 아래 그림처럼 진행이 되는데, 옵티마이저 트레이스를 출력하면 옵티마이저가 하는 역할을 그대로 확인 할 수 있다.

 

실행 계획은 information_schema에서 json 형식으로 있다.

select * from inforamtion_schema.optimizer_trace\G;

 

옵티마이저 트레이스를 제어하는 시스템 변수를 확인 하자.

세션 변수이므로 그냥 변경하면 현재 세션에만 적용 된다.

- 옵티마이저 트레이스의 활성화/ 비활성화

set optimizer_trace='enabled=on';
set optimizer_trace='enabled=off';

- 옵티마이저 트레이스 최대 저장 갯수 변경

SET optimizer_trace_offset=-2, optimizer_trace_limit=5; << 마지막 2개 수생된거 부터 5개 저장
SET optimizer_trace_offset=0, optimizer_trace_limit=5; << 마지막 수행된거 부터 5개 저장 (추천, limit 값만 조절 할 것)

※ 최대 출력 갯수는 optimizer_trace_max_mem_size 의 영향을 받는다.

- 옵티마이저 트레이스 다중 출력

select * from information_schema.optimizer_trace ; << 순서대로 모두 출럭 (추천)
select * from information_schema.optimizer_trace limit 1 ; << 1번째 1개 출력
select * from information_schema.optimizer_trace limit 1 offset 1 ; << 2번째 1개 출력
select * from information_schema.optimizer_trace limit 2 offset 0 ; << 1번째 부터 2개 출력
select * from information_schema.optimizer_trace limit 2 offset 1 ; << 2번째 부터 2개 출력
select * from information_schema.optimizer_trace limit 2 offset 2 ; << 3번째 부터 2개 출럭

 

쿼리를 수행하고 트레이스를 확인 보았다.

출력되는 결과가 너무 길어서 잘라서 보자.

mysql> select * from information_schema.optimizer_trace\G;
*************************** 1. row ***************************
                            QUERY: select * from t1 join t3 on (t1.c1=t3.pk and t3.pk < 113)
                            TRACE: {
  "steps": [
    {
      "join_preparation": {
        "select#": 1,
        "steps": [
          {
            "expanded_query": "/* select#1 */ select `t1`.`c1` AS `c1`,`t1`.`c2` AS `c2`,`t3`.`pk` AS `pk`,`t3`.`i` AS `i` from (`t1` join `t3` on(((`t1`.`c1` = `t3`.`pk`) and (`t3`.`pk` < 113))))"
          },
          {
            "transformations_to_nested_joins": {
              "transformations": [
                "JOIN_condition_to_WHERE",
                "parenthesis_removal"
              ],
              "expanded_query": "/* select#1 */ select `t1`.`c1` AS `c1`,`t1`.`c2` AS `c2`,`t3`.`pk` AS `pk`,`t3`.`i` AS `i` from `t1` join `t3` where ((`t1`.`c1` = `t3`.`pk`) and (`t3`.`pk` < 113))"
            }
          }
        ]
      }
    },

QUERY 컬럼을 통해 실제 입력된 쿼리를 있다.

join_preparation select# explain id 동일한 값이다.

join_preparation transformations_to_nested_joins 에서 쿼리의 실행 계획을 찾기 전에 쿼리를 완전한 형태로 열어 조인문의 on 절에 있는 조건을 where 절로 이동 시킨다.

 

        {
      "join_optimization": {
        "select#": 1,
        "steps": [
          {
            "condition_processing": {
              "condition": "WHERE",
              "original_condition": "((`t1`.`c1` = `t3`.`pk`) and (`t3`.`pk` < 113))",
              "steps": [
                {
                  "transformation": "equality_propagation",
                  "resulting_condition": "((`t3`.`pk` < 113) and multiple equal(`t1`.`c1`, `t3`.`pk`))"
                },
                {
                  "transformation": "constant_propagation",
                  "resulting_condition": "((`t3`.`pk` < 113) and multiple equal(`t1`.`c1`, `t3`.`pk`))"
                },
                {
                  "transformation": "trivial_condition_removal",
                  "resulting_condition": "((`t3`.`pk` < 113) and multiple equal(`t1`.`c1`, `t3`.`pk`))"
                }
              ]
            }
          },

join_optimization 처음 단계는 where 절의 변환이다.원래 on 절에 지정되어 있던 것들 불필요한 조건을 제거한 옵티마이저가 해석하기 쉬운 형태로 변경 한다. 3개의 변환 알고리즘이 적용 되었다.

equality_propagation(등가 비교에 의한 추이율) 적용한 결과 c1, pk 컬럼이 같은 값이라고 판단하여 같은 그룹(multiple equal)으로 합쳐졌다.

constant_propagation(정수의 등가 비교에 의한 추이) trivial_condition_removal(자명한 조건의 삭제) 조건이 포함 되지 않아 변환은 일어나지 않았다. trivial_condition_removal에서는 만일 where...and 1=1 같은 불필요한 조건이 포함되어 있을 경우 삭제 된다.

위의 과정을 좀 자세히 보면 다음과 같다.

 

           {
            "substitute_generated_columns": {
            }
          },
          {
            "table_dependencies": [
              {
                "table": "`t1`",
                "row_may_be_null": false,
                "map_bit": 0,
                "depends_on_map_bits": [
                ]
              },
              {
                "table": "`t3`",
                "row_may_be_null": false,
                "map_bit": 1,
                "depends_on_map_bits": [
                ]
              }
            ]
          },
          {
            "ref_optimizer_key_uses": [
              {
                "table": "`t3`",
                "field": "pk",
                "equals": "`t1`.`c1`",
                "null_rejecting": true
              }
            ]
          },
          {
            "rows_estimation": [
              {
                "table": "`t1`",
                "table_scan": {
                  "rows": 10,
                  "cost": 0.25
                }
              },
              {
                "table": "`t3`",
                "range_analysis": {
                  "table_scan": {
                    "rows": 4,
                    "cost": 2.75
                  },
                  "potential_range_indexes": [
                    {
                      "index": "PRIMARY",
                      "usable": true,
                      "key_parts": [
                        "pk"
                      ]
                    }
                  ],
                  "setup_range_conditions": [
                  ],
                  "group_index_range": {
                    "chosen": false,
                    "cause": "not_single_table"
                  },
                  "skip_scan_range": {
                    "chosen": false,
                    "cause": "not_single_table"
                  },
                  "analyzing_range_alternatives": {
                    "range_scan_alternatives": [
                      {
                        "index": "PRIMARY",
                        "ranges": [
                          "pk < 113"
                        ],
                        "index_dives_for_eq_ranges": true,
                        "rowid_ordered": true,
                        "using_mrr": false,
                        "index_only": false,
                        "rows": 2,
                        "cost": 0.71,
                        "chosen": true
                      }
                    ],
                    "analyzing_roworder_intersect": {
                      "usable": false,
                      "cause": "too_few_roworder_scans"
                    }
                  },
                  "chosen_range_access_summary": {
                    "range_access_plan": {
                      "type": "range_scan",
                      "index": "PRIMARY",
                      "rows": 2,
                      "ranges": [
                        "pk < 113"
                      ]
                    },
                    "rows_for_plan": 2,
                    "cost_for_plan": 0.71,
                    "chosen": true
                  }
                }
              }
            ]
          },

substitute_generated_columns은 컬럼 처리 방식으로 table_dependencies에는 테이블의 의존 관계가 나타나 있다. 이는 outer join에서 의미가 있는 항목이다. outer join에는 depends_on_map_bits에 의존하는 테이블의 map_bit가 표시된다.

ref_optimizer_key_uses에는 이쿼리에서 이용 가능한 인덱스이 목록이 나열된다.

rows_estimation에는 접근한 테이블의 수가 출력된다. 또한 테이블 스캔을 실행할 때의 비용을 있다.

 

          {
            "considered_execution_plans": [
              {
                "plan_prefix": [
                ],
                "table": "`t1`",
                "best_access_path": {
                  "considered_access_paths": [
                    {
                      "rows_to_scan": 10,
                      "filtering_effect": [
                      ],
                      "final_filtering_effect": 1,
                      "access_type": "scan",
                      "resulting_rows": 10,
                      "cost": 1.25,
                      "chosen": true
                    }
                  ]
                },
                "condition_filtering_pct": 100,
                "rows_for_plan": 10,
                "cost_for_plan": 1.25,
                "rest_of_plan": [
                  {
                    "plan_prefix": [
                      "`t1`"
                    ],
                    "table": "`t3`",
                    "best_access_path": {
                      "considered_access_paths": [
                        {
                          "access_type": "eq_ref",
                          "index": "PRIMARY",
                          "rows": 1,
                          "cost": 3.5,
                          "chosen": true,
                          "cause": "clustered_pk_chosen_by_heuristics"
                        },
                        {
                          "access_type": "range",
                          "range_details": {
                            "used_index": "PRIMARY"
                          },
                          "chosen": false,
                          "cause": "heuristic_index_cheaper"
                        }
                      ]
                    },
                    "condition_filtering_pct": 100,
                    "rows_for_plan": 10,
                    "cost_for_plan": 4.75,
                    "chosen": true
                  }
                ]
              },
              {
                "plan_prefix": [
                ],
                "table": "`t3`",
                "best_access_path": {
                  "considered_access_paths": [
                    {
                      "access_type": "ref",
                      "index": "PRIMARY",
                      "usable": false,
                      "chosen": false
                    },
                    {
                      "rows_to_scan": 2,
                      "filtering_effect": [
                      ],
                      "final_filtering_effect": 1,
                      "access_type": "range",
                      "range_details": {
                        "used_index": "PRIMARY"
                      },
                      "resulting_rows": 2,
                      "cost": 0.91,
                      "chosen": true
                    }
                  ]
                },
                "condition_filtering_pct": 100,
                "rows_for_plan": 2,
                "cost_for_plan": 0.91,
                "rest_of_plan": [
                  {
                    "plan_prefix": [
                      "`t3`"
                    ],
                    "table": "`t1`",
                    "best_access_path": {
                      "considered_access_paths": [
                        {
                          "rows_to_scan": 10,
                          "filtering_effect": [
                          ],
                          "final_filtering_effect": 1,
                          "access_type": "scan",
                          "using_join_cache": true,
                          "buffers_needed": 1,
                          "resulting_rows": 10,
                          "cost": 2.25,
                          "chosen": true
                        }
                      ]
                    },
                    "condition_filtering_pct": 100,
                    "rows_for_plan": 20,
                    "cost_for_plan": 3.16,
                    "chosen": true
                  }
                ]
              }
            ]
          },

considered_execution_plans 옵티마이저가 검토한 실행 계획을 배열로 표시한다.

plan_prefix는 지금까지 접근한 테이블을 나타낸다. 처음에 접근하는 테이블의 plan_prefix는 당연히 비어있다.

table 맴버는 현재 검토하고 있는 테이블을 가리킨다.

best_access_path는 가장 효율적인 접근 방식을 착기 위해 어떤 접근 방식을 검토했는가를 표시하고, 검토한 접근 방식은 considered_access_paths에 정리 되어 있다. 선택된 것은 chosen 맴버가 true 것으로 있다.

condition_filtering_pct는 테이블에서 데이터를 패치한 이후에 적용된 where 절의 조건으로 어느 정도 행의 범위 축소가 일어났는지를 예상한다. 여기서는 100% 되어 있기 대문에 범위 축소가 전혀 일어나지 않았다.

rest_of_plan은 이후의 테이블 접근 정보가 저장된다. 여기서는 조인한 경우를 있다.

두 개의 실행계획을 색으로 분리 하였다. t1->t3 조인 방법은 cost 4.75, t3->t1 조인 방법은 cost 3.16 이 소모된다.

 

          {
            "attaching_conditions_to_tables": {
              "original_condition": "((`t1`.`c1` = `t3`.`pk`) and (`t3`.`pk` < 113))",
              "attached_conditions_computation": [
              ],
              "attached_conditions_summary": [
                {
                  "table": "`t3`",
                  "attached": "(`t3`.`pk` < 113)"
                },
                {
                  "table": "`t1`",
                  "attached": "(`t1`.`c1` = `t3`.`pk`)"
                }
              ]
            }
          },
          {
            "finalizing_table_conditions": [
              {
                "table": "`t3`",
                "original_table_condition": "(`t3`.`pk` < 113)",
                "final_table_condition   ": "(`t3`.`pk` < 113)"
              },
              {
                "table": "`t1`",
                "original_table_condition": "(`t1`.`c1` = `t3`.`pk`)",
                "final_table_condition   ": "(`t1`.`c1` = `t3`.`pk`)"
              }
            ]
          },

attaching_conditions_to_tables는 추가로 적용될 where 절의 검색 조건을 정리한다.

 

            "refine_plan": [
              {
                "table": "`t3`"
              },
              {
                "table": "`t1`"
              }
            ]
          }
        ]
      }
    },
    {
      "join_execution": {
        "select#": 1,
        "steps": [
        ]
      }
    }
  ]
}
MISSING_BYTES_BEYOND_MAX_MEM_SIZE: 0
          INSUFFICIENT_PRIVILEGES: 0
1 rows in set (0.00 sec)

refine_plan은 join_optimization 마지막 항목이다. 단순히 어떤 테이블에 접근 했는지를 나타낸다.

MISSING_BYTES_BEYOND_MAX_MEM_SIZE는 옵티마이저 트레이스를 잘라낸 부분을 표시한다. 0이므로 잘라낸 부분이 없다. 만일 메모리가 부족하여 일부를 잘라내게 되면 이곳에 바이트를 잘라냈는지 표시한다. 메모리의 크기는 optimizer_trace_max_mem_size 지정한다. 기본값은 16KB (8.0.18 기준 1MB)이다.

 

+ Recent posts