MySQL 5.7.8 부터 JSON 을 지원한다. 

JSON 문서 내의 값에서 Generated Column을 생성한 다음 해당 컬럼을 인덱싱하면 실제로 JSON 필드를 인덱싱할 수 있다. 

 

JSON 테스트 데이터 

{
    "id": 1,  
    "name": "Sally",  
    "games_played":{    
       "Battlefield": {
          "weapon": "sniper rifle",
          "rank": "Sergeant V",
          "level": 20
        },                                                                                                                          
       "Crazy Tennis": {
          "won": 4,
          "lost": 1
        },  
       "Puzzler": {
          "time": 7
        }
     }
 }

테스트 테이블 생성

CREATE TABLE `players` (  
   `id` INT UNSIGNED NOT NULL,
   `player_and_games` JSON NOT NULL,
   `names_virtual` VARCHAR(20) GENERATED ALWAYS AS (`player_and_games` ->> '$.name') NOT NULL, 
   PRIMARY KEY (`id`)
);

위 DDL문에서 아래 부분이 JSON 타입 컬럼 player_and_game 에서 name 속성 값을 참조하는 Generated Column (names_virtual) 을 지정한 부분이다. 

`names_virtual` VARCHAR(20) GENERATED ALWAYS AS (`player_and_games` ->> '$.name')

Generated Column 에 지정할수 있는 제약조건은 아래와 같다. 

[VIRTUAL|STORED] [UNIQUE [KEY]] [[NOT] NULL] [[PRIMARY] KEY]

VIRTUAL, STORED는 컬럼의 값이 실제로 저장되는지 여부를 나타낸다.

- VIRTUAL : 기본값, 컬럼의 값 저장안함, 저장 공간없음, 행을 읽을 때마다 계산해서 처리한다.

                    인덱스를 생성하면 값이 인덱스 자체에 저장된다.

- STORED : 데이터가 테이블에 저장(입력,수정)될때 컬럼의 값도 저장된다. 

                    인덱스는 일반적인 인덱스와 동일하게 처리된다. 

 

아래에 테스트 소스 깃헙에 있는 mysql-json.sql 파일에 있는 insert 문을 사용한다. 

INSERT INTO `players` (`id`, `player_and_games`) VALUES (1, '{  
    "id": 1,  
    "name": "Sally",
    "games_played":{    
       "Battlefield": {
          "weapon": "sniper rifle",
          "rank": "Sergeant V",
          "level": 20
        },                                                                                                                          
       "Crazy Tennis": {
          "won": 4,
          "lost": 1
        },  
       "Puzzler": {
          "time": 7
        }
      }
   }'
);
....

입력된 데이터 확인, names_virtual 컬럼에서 JSON 문서의 name 속성값 확인됨 

mysql> select * from players;
+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------+
| id | player_and_games                                                                                                                                                                                           | names_virtual |
+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------+
|  1 | {"id": 1, "name": "Sally", "games_played": {"Puzzler": {"time": 7}, "Battlefield": {"rank": "Sergeant V", "level": 20, "weapon": "sniper rifle"}, "Crazy Tennis": {"won": 4, "lost": 1}}}                  | Sally         |
|  2 | {"id": 2, "name": "Thom", "games_played": {"Puzzler": {"time": 25}, "Battlefield": {"rank": "Major General VIII", "level": 127, "weapon": "carbine"}, "Crazy Tennis": {"won": 10, "lost": 30}}}            | Thom          |
|  3 | {"id": 3, "name": "Ali", "games_played": {"Puzzler": {"time": 12}, "Battlefield": {"rank": "First Sergeant II", "level": 37, "weapon": "machine gun"}, "Crazy Tennis": {"won": 30, "lost": 21}}}           | Ali           |
|  4 | {"id": 4, "name": "Alfred", "games_played": {"Puzzler": {"time": 10}, "Battlefield": {"rank": "Chief Warrant Officer Five III", "level": 73, "weapon": "pistol"}, "Crazy Tennis": {"won": 47, "lost": 2}}} | Alfred        |
|  5 | {"id": 5, "name": "Phil", "games_played": {"Puzzler": {"time": 7}, "Battlefield": {"rank": "Lt. Colonel III", "level": 98, "weapon": "assault rifle"}, "Crazy Tennis": {"won": 130, "lost": 75}}}          | Phil          |
|  6 | {"id": 6, "name": "Henry", "games_played": {"Puzzler": {"time": 17}, "Battlefield": {"rank": "Captain II", "level": 87, "weapon": "assault rifle"}, "Crazy Tennis": {"won": 68, "lost": 149}}}             | Henry         |
+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------+
6 rows in set (0.00 sec)

players 테이블 컬럼 정보 확인 

mysql> show columns from players;
+------------------+--------------+------+-----+---------+-------------------+
| Field            | Type         | Null | Key | Default | Extra             |
+------------------+--------------+------+-----+---------+-------------------+
| id               | int unsigned | NO   | PRI | NULL    |                   |
| player_and_games | json         | NO   |     | NULL    |                   |
| names_virtual    | varchar(20)  | NO   |     | NULL    | VIRTUAL GENERATED |
+------------------+--------------+------+-----+---------+-------------------+

Extra 에 VIRTUAL GENERATED 으로 Generated Column 이  VIRTUAL 인것을 보여준다. 

STORED 로 지정된 컬럼의 경우 STORED GENERATED 로 표시된다. 

 

Battlefield 레벨, 승리한 테니스 게임, 패배한 테니스 게임 및 퍼즐 게임 시간 컬럼을 추가한다.

ALTER TABLE `players` ADD COLUMN `battlefield_level_virtual` INT GENERATED ALWAYS AS (`player_and_games` ->> '$.games_played.Battlefield.level') NOT NULL AFTER `names_virtual`;  
ALTER TABLE `players` ADD COLUMN `tennis_won_virtual` INT GENERATED ALWAYS AS (`player_and_games` ->> '$.games_played."Crazy Tennis".won') NOT NULL AFTER `battlefield_level_virtual`;  
ALTER TABLE `players` ADD COLUMN `tennis_lost_virtual` INT GENERATED ALWAYS AS (`player_and_games` ->> '$.games_played."Crazy Tennis".lost') NOT NULL AFTER `tennis_won_virtual`;  
ALTER TABLE `players` ADD COLUMN `times_virtual` INT GENERATED ALWAYS AS (`player_and_games` ->> '$.games_played.Puzzler.time') NOT NULL AFTER `tennis_lost_virtual`;

mysql> show columns from players;
+---------------------------+--------------+------+-----+---------+-------------------+
| Field                     | Type         | Null | Key | Default | Extra             |
+---------------------------+--------------+------+-----+---------+-------------------+
| id                        | int unsigned | NO   | PRI | NULL    |                   |
| player_and_games          | json         | NO   |     | NULL    |                   |
| names_virtual             | varchar(20)  | NO   |     | NULL    | VIRTUAL GENERATED |
| battlefield_level_virtual | int          | NO   |     | NULL    | VIRTUAL GENERATED |
| tennis_won_virtual        | int          | NO   |     | NULL    | VIRTUAL GENERATED |
| tennis_lost_virtual       | int          | NO   |     | NULL    | VIRTUAL GENERATED |
| times_virtual             | int          | NO   |     | NULL    | VIRTUAL GENERATED |
+---------------------------+--------------+------+-----+---------+-------------------+

 

Generated Column을 검색하는 쿼리 실행계획 확인 

mysql> EXPLAIN SELECT * FROM `players` WHERE `names_virtual` = "Sally"\G  
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: players
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 6
     filtered: 16.67
        Extra: Using where
1 row in set, 1 warning (0.00 sec)

해당 컬럼에 인덱스 생성하고 실행계획 확인, 생성한 인덱스 인덱스 잘탄다. 

mysql> CREATE INDEX `names_idx` ON `players`(`names_virtual`);  
Query OK, 0 rows affected (0.03 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> EXPLAIN SELECT * FROM `players` WHERE `names_virtual` = "Sally"\G  
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: players
   partitions: NULL
         type: ref
possible_keys: names_idx
          key: names_idx
      key_len: 82
          ref: const
         rows: 1
     filtered: 100.00
        Extra: NULL
1 row in set, 1 warning (0.00 sec)

다른 Generated Column 들도 인덱스를 생성하고, 생성된 인덱스를 확인한다. 

mysql> CREATE INDEX `times_idx` ON `players`(`times_virtual`);  
Query OK, 0 rows affected (0.04 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> CREATE INDEX `won_idx` ON `players`(`tennis_won_virtual`);  
Query OK, 0 rows affected (0.03 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> CREATE INDEX `lost_idx` ON `players`(`tennis_lost_virtual`);  
Query OK, 0 rows affected (0.03 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> CREATE INDEX `level_idx` ON `players`(`battlefield_level_virtual`);  
Query OK, 0 rows affected (0.04 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> SHOW INDEX FROM `players`;
+---------+------------+-----------+--------------+---------------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| Table   | Non_unique | Key_name  | Seq_in_index | Column_name               | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
+---------+------------+-----------+--------------+---------------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| players |          0 | PRIMARY   |            1 | id                        | A         |           6 |     NULL |   NULL |      | BTREE      |         |               | YES     | NULL       |
| players |          1 | names_idx |            1 | names_virtual             | A         |           6 |     NULL |   NULL |      | BTREE      |         |               | YES     | NULL       |
| players |          1 | times_idx |            1 | times_virtual             | A         |           5 |     NULL |   NULL |      | BTREE      |         |               | YES     | NULL       |
| players |          1 | won_idx   |            1 | tennis_won_virtual        | A         |           6 |     NULL |   NULL |      | BTREE      |         |               | YES     | NULL       |
| players |          1 | lost_idx  |            1 | tennis_lost_virtual       | A         |           6 |     NULL |   NULL |      | BTREE      |         |               | YES     | NULL       |
| players |          1 | level_idx |            1 | battlefield_level_virtual | A         |           6 |     NULL |   NULL |      | BTREE      |         |               | YES     | NULL       |
+---------+------------+-----------+--------------+---------------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
6 rows in set (0.03 sec)

 

STORED 로 지정된 Generated Column 을 사용하는 경우 

1. PRIMARY KEY 를 잡아야될경우 
2. 일반 BTREE 대신 FULLTEXT 또는 RTREE 인덱스가 필요한 경우 
3. 인덱스를 사용하지 않는 스캔이 많이 발생하는 컬럼일 경우 

 

테스트용 테이블 생성 

CREATE TABLE `players_two` (  
    `id` INT GENERATED ALWAYS AS (`player_and_games` ->> '$.id') STORED NOT NULL,
    `player_and_games` JSON NOT NULL,
    `names_virtual` VARCHAR(20) GENERATED ALWAYS AS (`player_and_games` ->> '$.name') NOT NULL,
    `times_virtual` INT GENERATED ALWAYS AS (`player_and_games` ->> '$.games_played.Puzzler.time') NOT NULL,
    `tennis_won_virtual` INT GENERATED ALWAYS AS (`player_and_games` ->> '$.games_played."Crazy Tennis".won') NOT NULL,
    `tennis_lost_virtual` INT GENERATED ALWAYS AS (`player_and_games` ->> '$.games_played."Crazy Tennis".lost') NOT NULL,
    `battlefield_level_virtual` INT GENERATED ALWAYS AS (`player_and_games` ->> '$.games_played.Battlefield.level') NOT NULL, 
    PRIMARY KEY (`id`),
    INDEX `times_index` (`times_virtual`),
    INDEX `names_index` (`names_virtual`),
    INDEX `won_index` (`tennis_won_virtual`),
    INDEX `lost_index` (`tennis_lost_virtual`),
    INDEX `level_index` (`battlefield_level_virtual`)
);

PK 컬럼을 STORED 로 지정하였다. 

`id` INT GENERATED ALWAYS AS (`player_and_games` ->> '$.id') STORED NOT NULL,

SHOW COLUMNS 로 컬럼 확인 

mysql> SHOW COLUMNS FROM `players_two`;
+---------------------------+-------------+------+-----+---------+-------------------+
| Field                     | Type        | Null | Key | Default | Extra             |
+---------------------------+-------------+------+-----+---------+-------------------+
| id                        | int         | NO   | PRI | NULL    | STORED GENERATED  |
| player_and_games          | json        | NO   |     | NULL    |                   |
| names_virtual             | varchar(20) | NO   | MUL | NULL    | VIRTUAL GENERATED |
| times_virtual             | int         | NO   | MUL | NULL    | VIRTUAL GENERATED |
| tennis_won_virtual        | int         | NO   | MUL | NULL    | VIRTUAL GENERATED |
| tennis_lost_virtual       | int         | NO   | MUL | NULL    | VIRTUAL GENERATED |
| battlefield_level_virtual | int         | NO   | MUL | NULL    | VIRTUAL GENERATED |
+---------------------------+-------------+------+-----+---------+-------------------+

 

출처 :

https://www.compose.com/articles/mysql-for-json-generated-columns-and-indexing/

https://dev.mysql.com/blog-archive/indexing-json-documents-via-virtual-columns/

테스트 소스 :

https://github.com/compose-ex/mysql-json-indexing-generated-columns/blob/master/mysql-json.sql

 

'MySQL' 카테고리의 다른 글

Metadata Lock 조회  (0) 2022.09.22
MySQL JSON 사용시 주의사항  (0) 2022.09.16
MySQL Performance Best Practice  (0) 2021.12.26
xtrabackup 8.0 (2)  (0) 2021.07.06
xtrabackup 8.0 (1)  (0) 2021.06.30

 

출처 : https://www.youtube.com/watch?v=dF8qmCOSE7g 

 

MySQL 8.0 
- Redo log 디자인을 변경해서 성능향상
- system mutex 를 최대한 분리해서 IO Capacity 를 충분히 이용하도록 변경 
- Information Schema, Performance Schmea 를 조회하는 성능 향상

Memory 
- 작업 데이터셋이 innodb buffer pool 에 적합한지 확인
  사용 가능한 메모리의 70~80%를 innodb buffer pool 에 할당 할 수 있음 
- MySQL 은 Connection 당 메모리 소모, Connection 당 할당되는 메모리는 크게 할당하는것을 권장하지 않는다. 
  max_allowed_packet, thread_stack, net_buffer_length, max_digest_length
- 디스크 접속을 완하하기 위한 Caching 및 Buffer 
  Read log/Join/Sorting Buffer 와 Table Cache
  Replication 관련 max_binlog_cache_size 
- 기타
  - OS, FS cache
  - temporary tables tmp_table_size 
  - Performance Schema
- 모니터링 방법: top 명령어로 확인, 전체 메모리의 80% 이하 

Monitoring MySQL Memory Usage 
- 대부분 메모리 지표는 비활성화 됨 
  select * from performance_schema_setup_instruments where name like '%memory%';
- performance_schema 
  memory_summary_global_by_event_name 
- sys 스키마 
  memory_global_by_current_bytes 
- 상태값 확인
  show global status like 'table_open%';
  show global status like 'binlog%';
  show global status like 'created_tmp%';
  
  select 
    substring_index(event_name, '/', 2) as core_area, 
    format_bytes(sum(current_alloc)) as current_alloc
  from sys.x$memory_global_by_current_bytes
  group by substring_index(event_name, '/', 2)
  order by sum(current_alloc) desc;

Disk 
- SSD, NVMe 권장 
 innodb datadir, tmp 파일, undo 로그 모두 랜덤 IO, 빠른 SSD 권장
 inndb_page_size = 4K (디폴트는 16K, 디스크 page 사이즈와 매칭)
 innodb_flush_neighbors = 0 
- Spinning 디스크는 log (순서 IO)에 여전히 사용할 수 있음 
- RAID 디스크를 사용한다면 성능을 위해 RAID 1+0 권장 
- FBWC/BBWC (Flash/Battery-Backed Write Cache) 권장 
  write 성능 향상 및 crash-safe
- 디스크 유형(rpm 지표) 및 부하에 따라 innodb_io_capactiy 설정 
  디폴트값은 200, 느린 ssd 혹은 일반 하드 디스크에 적합 
  디스크 성능이 좋다면 200보다 더 큰값을 설정

OS 
- 성능만 고려하면 Linux 권장 
- MySQL 8.0 인 경우 최신 커널 권장하지
- ext4, xfs 파일 시스템 추천
- ulimit을 사용하여 file/process limits 확장 
- MySQL 전용 서버인 경우, innodb_numa-interleave = 1 권장 
- Swappiness, 권장 옵션은 (1-6): sysctl - vn.swappinness=1
- InnoDB 데이터 파일은 FS 캐시를 거치지 않음, innodb_flush_method=O_DIRECT 

MySQL Configuration 
- 8.0 설정 파일 : my.cnf, mysqld-auto.cnf (SET PERSIST 변수로 지정한 값을 mysqld 재기동 후에도 유지)
- 설정값 확인 테이블 추가 
  - performance_schema.global_variables
  - performance_schema.session_variables
  - performance_schema.variables_by_thread
  
- 클라이언트 커넥션 관련 설정 
  - max_connections 를 필요 이상으로 크게 설정하지 말아야 할 것 
   - OS limit on file descriptors 
     - Linux/Solaris : UP to 10,000
     - Windows: Up to 16,384
   - 응답 시간의 기대치 및 워크로드 감안
   - 많은 연결 수가 예상되면 버퍼에 따른 메모리 용량을 확복 
   - wait_timeout으로 연결을 닫음 
  - 상태값 확인
    - Connections
    - Max used connections
    - Connection errors max connections 
  - 커넥션별로 할당되는 버퍼 값은 작게 시작 
    - sort_buffer_size
    - binlog_cache_size
    - net_buffer_length ~ max_allowed_packet 
    - read_buffer_size 
    - read_rnd_buffer_size 
    
Reusing Threads
- thread_cache_size : 서버가 스레드를 재사용할 수 있도록 저장한 스레드 개수 
  - 디폴트 값은 자동 설정 : 8 + (max_connections / 100)
  - Thread cache hit rate % = 100 - ((Threads_created / Connections) * 100)
- 연관된 상태 값 확인 
  - Connections
  - Threads_connected
  - Connections / Uptime 
  - Threads_created / Uptime 
  - Uptime_since_flush_status 
  - Threads_cached 
  - Threads_running 
- thread_pool_size 스레드 풀 성능 튜닝 (엔터프라이즈만 사용가능)
  - https://dev.mysql.com/doc/refman/8.0/en/thread-pool-tuning.html

InnoDB Buffer pool 
- innodb_buffer_pool_size 
  - 모든 데이터를 innodb_buffer_pool 에 넣어서 사용하면 이상적이나(이상일 뿐)
  - 테이블 및 인덱스 데이터를 캐시한 InnoDB의 메모리 영역, LRU 알고리즘에 의해 관리 
  - 전용 데이터베이스 서버에서 시스템의 실제 메모리 크기의 70%~80%정도로 설정 할 수 있음 
  - innodb_buffer_pool_size  = N * innodb_buffer_pool_chunk_size * innodb_buffer_pool_instances 
- 확인 방법 : SHOW ENGINE INNODB STATUS;
  - Total memory allocated
  - Buffer pool size 
  - Free buffers 
  - Database pages, Old database pages 
  - Modified db pages 
  - Pending reads, Pending writes 
  - Buffer pool hit rate 

Innodb redo log (트랜잭션 로그)
- innodb data file에 발생하는 랜덤 io 를 red log file을 사용해서 순차 io 로 변경해서 처리 해줘서 write 성능 향상 
- innodb_log_file_size 
  - write 성능에 유리하지만 복구 시간에 영향 (크래시 리커버는 redo log 기반)
  - 총 사이즈(~512G) : innodb_log_file_size * innodb_log_files_in_group (기본값 2)
  - 운영 환경에서 최소 512M으로 권장하고, 가능한 충분히 크게 설정할 것 권장 
- redo 사용율 
  - SHOW ENGINE INNODB STATUS 에서 Log sequence number, Last checkpoint at 값을 확인하여 계산 
  - SELECT NAME, COUNT FROM information_schema.INNODB_METRICS WHERE NAME IN ('log_lsn_current', 'log_lsn_last_checkpoint')
  - Used % = (Used log / Total log) * 100 
    = (log_lsn_current - log_lsn_last_checkpoint / (innodb_log_file_size * innodb_log_files_in_group)) * 100
    = (47605855 / 100663296) * 100 = 47.29% 
  - Used % 가 50% 정도는 괜찮지만 80%정도일 경우 위험함 
    redo log 가 부족하게 되면 innodb buffer pool flushing 이 발생하는데 이때 대량의 io가 발생하여 성능 저하가 발생한다. 
- innodb_log_buffer_size = 16MB
  - Redo 로그 I/O를 완화하기 위한 버퍼 영역 
  - 일반적으로 16MB정도면 충분하지만 트랜잭션 사이즈가 크면 commit 전에 disk IO 가 발생되기에, 따라서 크게 조절할 필요가 있음 
 - innodb_flush_log_at_trx_commit = 1
  - InnoDB가 commit한 트랜젝션에 대해서 언제 flush 할 것인지 결정
  - 디폴트값인 1로 권장하지만 다음 상황에서 제외:
    - InnoDB가 commit한 트랜젝션에 대해서 언제 flush 할 것인지 결정
    - Bulk 데이타 로딩할 경우 2로 설정하거나 8.0 버전에서 redo로그를 비활성화
    - 예측하지 못한 peak 부하로 인해 디스크 I/O부하가 심할 경우
    - 데이터 로스가 발생하여도 괜찮을 경우

바이너리 로그 성능만- 
-  binlog_cache_size에 비해 트랜젝션이 너무 크면, 임시 파일에 저장하기에 성능 영향
- 관련된 상태 변수 확인:
  - Binlog_cache_use
  - Binlog_cache_disk_use
  - 캐시 효율성% = (100 - ( Cache Disk Use/Cache Use) * 100)
- 트랜젝션 사이즈를 제어함으로 바이너리 로그 사이즈 제어, 복제 효율성 향상
  - DELETE 대신에 DROP TABLE/PARTITION
  - DELETE FROM … WHERE … LIMIT 1000;
  - binlog_row_image = minimal
- 복제 지연 방지 설정
  - binlog_transaction_dependency_tracking = WRITESET
  - replica_parallel_type = LOGICAL_CLOCK
  - replica-parallel-workers = 16
  - binlog_order_commits=OFF
  - replica_preserve_commit_order = 1(optional)
https://dev.mysql.com/blog-archive/improving-the-parallel-applier-with-writeset-based-dependency-tracking/



'MySQL' 카테고리의 다른 글

MySQL JSON 사용시 주의사항  (0) 2022.09.16
MySQL JSON, Generated Columns  (0) 2022.09.15
xtrabackup 8.0 (2)  (0) 2021.07.06
xtrabackup 8.0 (1)  (0) 2021.06.30
Hash join in MySQL 8  (0) 2021.06.25

CentOS 에서 yum 으로 xtrabackup 설치

# Percona yum repository 설치 
yum -y install https://repo.percona.com/yum/percona-release-latest.noarch.rpm
# Enable the repository:
percona-release enable-only tools
# xtrabackup 설치 
yum -y install percona-xtrabackup-80
# 압축 백업을 사용하기 위해 qpress 설치 
yum -y install qpress

CentOS 6 에서 xtrabackup 을 설치하기 위해서는 libev 패키지가 설치되어 있어야한다. 

rpm 설치 

# 필요 패키지 설치 
yum install -y libev perl-DBD-MySQL perl-Time-HiRes
# xtrabackup 설치 rpm 다운로드 
wget https://www.percona.com/downloads/XtraBackup/Percona-XtraBackup-8.0.4/binary/redhat/7/x86_64/percona-xtrabackup-80-8.0.4-1.el7.x86_64.rpm
# xtrabackup 설치 
yum localinstall percona-xtrabackup-80-8.0.4-1.el7.x86_64.rpm

서버에 연결 

xtrabackup --default-file=/etc/my.cnf --user=MySQL계정(백업 실행 권한 필요) --password=패스워드 --backup \
--host=접속할 MySQL서버 ip --port=접속할 MySQL서버 포트 --socket=접속할 MySQL 서버 소켓파일 \
--target-dir=백업할 디렉토리

xtrabackup 기본 백업 디렉토리 설정 

my.cnf 파일에 아래와 같이 지정할 수 있다. 

[xtrabackup]
target_dir=/data/backups/mysql/

필요 권한 

- BACKUP_ADMIN : performance_schema.log_status 테이블 쿼리,  LOCK INSTANCE FOR BACKUP, LOCK BINLOG FOR BACKUP, LOCK TABLES FOR BACKUP 실행 권한 

- PROCESS : SHOW ENGINE INNODB STATUS 실행과, 서버에서 실행중인 모든 스레드를 보기위한 권한 

- RELOAD, LOCK TABLES : 파일 복사를 시작하기전에 FLUSH TABLES WITH READ LOCK, FLUSH ENGINE LOGS를 실행하기 위한 권한 (백업 잠금이 사용될때 필요한 권한)

- REPLICATION CLIENT : binary log position 확인을 위한 권한 

- CREATE TABLESPACE : import tables 를 위한 권한 (개발 테이블 복원시)

- SUPER : 복제구성에서 복제 스레드 시작/중지를 위한 권한, XtraDB에서 증분백업을 위한 변경 페이지 추적을 위한FLUSH TABLES WITH READ LOCK를 사용하기 위한 권한

- CREATE : PERCONA_SCHEMA.xtrabackup_history 데이터베이스, 테이블을 생성하기 위한 권한

- INSERT : PERCONA_SCHEMA.xtrabackup_history 테이블에 히스토리 레코드를 추가하기 위한 INSERT 권한

- SELECT : PERCONA_SCHEMA.xtrabackup_history 테이블에서 innodb_to_lsn 값 조회, --incremental-history-name 또는 --incremental-history-uuid 을 사용하기 위한 권한 

전체 백업에 필요한 최소 권한을 가진 데이터베이스 사용자 생성 예제

mysql> CREATE USER 'bkpuser'@'localhost' IDENTIFIED BY 's3cr%T';
mysql> GRANT BACKUP_ADMIN, PROCESS, RELOAD, LOCK TABLES, REPLICATION CLIENT ON *.* TO 'bkpuser'@'localhost';
mysql> GRANT SELECT ON performance_schema.log_status TO 'bkpuser'@'localhost';
mysql> GRANT SELECT ON performance_schema.keyring_component_status TO bkpuser@'localhost'
mysql> FLUSH PRIVILEGES;

전체백업

- xtrabackup 실행시 --backup, --target-dir 을 지정하여 실행

- --target-dir 로 지정된 디렉토리가 비어있는 경우에만 백업이 진행됨

  지정 디렉토리에 파일이 있는경우 에러가 발생한다.

xtrabackup --backup --target-dir=/data/backups/

전체백업 복원 

준비 단계 

- xtrabackup 실행시 --prepare, --target-dir 을 지정하여 실행

  백업파일 무결성, 일관성 검증실행 

- --prepare 옵션을 주고 xtrabackup 프로세스 실행중에 중지하지 않는것이 좋다. 

  데이터파일이 손상되고 백업을 사용할수 없게 될수도 있다. 

xtrabackup --prepare --target-dir=/data/backups/

복원 

- xtrabackup 실행시 --copy-back 또는 --move-back 옵션을 사용

  기존의 data 디렉토리에 자동으로 복원됨 

  data 디렉토리는 비어있어야 하며, mysql 서버도 종료된 상태여야함 

- --copy-back, --move-back 대신 rsync를 사용할수도 있다. 

 rsync -avrP /data/backup/ /var/lib/mysql/

- mysql 을 구동하기 전에 data 디렉토리의 소유권을 변경한다. 

chown -R mysql:mysql /var/lib/mysql

 

증분 백업

- xtrabackup 은 증분 백업을 지원한다. 

  마지막 백업 이후 변경된 데이터만 복사할 수 있다. 

- 증분 백업은 각 InnoDB 페이지에 로그 시퀀스번호 또는 LSN이 포함되어 있기 때문에 작동한다. 

  LSN은 전체 데이터베이스의 시스템 버전 번호.

- 증분 백업은 LSN이 이전 증분, 전체 백업의 LSN보다 최신인 각 페이지를 복사한다. 

- 복사할 페이지 세트를 찾는데 사용되는 알고리즘 

  - 모든 데이터 페이지를 읽어 페이지 LSN을 직접 확인 

  - 변경된 페이지 추적 기능 사용 (Percona Server for MySQL 에서만 사용가능)

- 증분 백업은 실제로 데이터 파일을 이전 백업 파일과 비교하지 않는다.  

  부분 백업후 증분 백업을 실행하면 데이터가 일치하지 않을 수도 있다. 

- 증분 백업으로 증분 변경사항을 복구하라면 전체 백업이 필요하다. 

- LSN 을 알고 있는 경우 --incremental-lsn 옵션을 사용하여 이전 백업 없이도 증분 백업 수행 가능 

 

증분 백업 생성

- 중분 백업을 수행하려면 전체 백업본이 필요하다. 

xtrabackup --prepare --target-dir=/data/backups/base

- xtrabackup 백업 대상 디렉토리에 xtrabackup_checkpoints 라는 백업 기록 파일이 있다. 

  이 파일에는 백업 시작,  종료 lsn 정보(from_lsn, to_lsn) 가 있다. 

backup_type = full-backuped
from_lsn = 0
to_lsn = 1626007
last_lsn = 1626007
compact = 0
recover_binlog_info = 1

- 전체 백업을 기반으로 증분 백업 진행 

xtrabackup --backup --target-dir=/data/backups/inc1 \
--incremental-basedir=/data/backups/base

- /data/backups/inc1 디렉토리의 xtrabackup_checkpoints 파일에는 아래와 같이 백업 시작,  종료 lsn 정보(from_lsn, to_lsn) 가 있다. 

backup_type = incremental
from_lsn = 1626007
to_lsn = 4124244
last_lsn = 4124244
compact = 0
recover_binlog_info = 1

- /data/backups/inc1 디렉토리를 다른 증분 백업의 기반으로 사용할 수 있다.

 xtrabackup --backup --target-dir=/data/backups/inc2 \
--incremental-basedir=/data/backups/inc1

- /data/backups/inc2 디렉토리의 xtrabackup_checkpoints 파일에는 아래와 같이 백업 시작,  종료 lsn 정보(from_lsn, to_lsn) 가 있다. 

backup_type = incremental
from_lsn = 4124244
to_lsn = 6938371
last_lsn = 7110572
compact = 0
recover_binlog_info = 1

증분 백업 복원

준비 단계 

- 증분 백업을 복원하기 위한 준비 단계는 전체 백업과 동일하지 않다. 

- 전체 백업에서는 데이터베이스 일관성을 유지하기 위해 두기지 유형의 작업이 수행된다. 

  커밋된 트랜잭션은 데이터파일에 대해 로그파일에서 재생되고 커밋 되지 않은 트랜잭션은 롤백된다. 

  백업시 커밋되지 않은 트랜잭션이 진행중 일수 있고 다음 증분 백업에서 키밋 될 가능성이 있으므로 

  증분 백업을 준비할 때 커밋되지 않은 트랜잭션의 롤백을 건너 뛰어야 한다. 

  롤백 단계를 방지하기 위해서 --apply-log-only 옵션을 사용해야 한다. 

- --apply-log-only 옵션을 사용하지 않으면 증분백업이 소용 없다. 

  트랜잭션이 롤백된 이후에는 추가 증분 백업을 적용할 수 없다. 

- 롤 백 단계를 건너 뛰고 복원해도 안전하다.

  복원하고 MySQL을 시작하면 InnoDB는 롤백 단계가 수행되지 않았음을 감지하고 일반적으로 시작시 충돌 복구에 

  대해 수행하는 것처럼 백그라운드에서 복구를 수행한다. 

 

예제 ) 다음과 같은 백업이 있을 때 복원 

/data/backups/base
/data/backups/inc1
/data/backups/inc2

- 기본 백업을 준비하려면 --perpare 옵션에  --apply-log-only 옵션을 사용하여 롤백 단계를 방지한다. 

xtrabackup --prepare --apply-log-only --target-dir=/data/backups/base

- 첫번째 증분 백업을 전체 백업에 적용

xtrabackup --prepare --apply-log-only --target-dir=/data/backups/base \
--incremental-dir=/data/backups/inc1

- 두번째 증분 백업을 전체 백업에 적용

※ --apply-log-only 옵션은 마지막 증분 백업을 적용할때는 사용하지 않는다.

xtrabackup --prepare --target-dir=/data/backups/base \
--incremental-dir=/data/backups/inc2

복원은 전체 백업과 동일하게 xtrabackup 실행시 --copy-back 또는 --move-back 옵션을 사용한다. 

 

압축 백업

- 압축 백업을 만들려면 --compress 옵션을 사용한다. 

 xtrabackup --backup --compress --target-dir=/data/compressed/

- 압축 백업을 사용하려면 qpress 패키지가 필요하다.

- --compress-threads 옵션으로 압축 속도를 높일 수 있다.

# 4개의 스레드를 사용한 압축 백업
trabackup --backup --compress --compress-threads=4 \
--target-dir=/data/compressed/

압축 백업본으로 복원 준비

- --decompress 옵션으로 압축을 해제한다. 

 xtrabackup --decompress --target-dir=/data/compressed/

- --parallel 옵션을 사용해서 동시에 여러파일의 압축을 해제할 수 있다.

- 압축이 해제되도 압축된 백업파일은 삭제되지 않는다. 

  --copy-back, --move-back 옵션 사용시에 압축된 백업 파일은 복사되지 않는다. 

  --remove-original 옵션을 사용할 경우 압축된 백업파일이 삭제된다. 

- 백업 파일의 압축이 해제되면 --prepare 옵션을 사용하여 복원을 준비할 수 있다.

xtrabackup --prepare --target-dir=/data/compressed/

백업 복원 

- xtrabackup 실행시 --copy-back 또는 --move-back 옵션을 사용하여 기존의 data 디렉토리에 복원한다.

xtrabackup --copy-back --target-dir=/data/backups/

 

참고

https://www.percona.com/doc/percona-xtrabackup/8.0/index.html

'MySQL' 카테고리의 다른 글

MySQL JSON, Generated Columns  (0) 2022.09.15
MySQL Performance Best Practice  (0) 2021.12.26
xtrabackup 8.0 (1)  (0) 2021.06.30
Hash join in MySQL 8  (0) 2021.06.25
Redo Log  (0) 2021.06.25

xtrabackup 사용시 이점

- 빠르고 안정적으로 완료되는 백업

- 백업 중 중단없는 트랜잭션 처리

- 디스크 공간 및 네트워크 대역폭 절약

- 자동 백업 확인

- 더 빠른 복원시간으로 더 높은 가동시간 

 

지원버전 

- xtrabackup 8.0.x 버전은 MySQL 8.0.x 버전 호환

- xtrabackup 2.4 버전은 MySQL 5.6, 5.7 버전 호환

 

지원되는 스토리지 엔진

- InnoDB, XtraDB, MyRocks 스토레지 엔진의 완전 비차단 백업 지원

  - MyRocks 스토리지 엔진은 8.0.6 버전부터 지원 

- MyISAM, Merge, Archive, Patition Table, Trigger, Database Option 의 경우 백업이 끝날때 쓰기를 일시 중지하여 백업 가능 

- xtrabackup 8.0 은 TukuDB 스토리지 엔진 지원 안함 

 

xtrabackup 기능 

- 데이터베이스를 중지하지 않고 InnoDB 백업 생성

- MySQL 증분 백업

- 압축된 MySQL 백업을 다른 서버로 스트리밍

- 온라인으로 MySQL 서버간의 테이블 이동

- 새로운 MySQL 복제 복제본을 쉽게 생성

- 서버에 부하를 추가하지 않고 MySQL 백업 

 

xtrabackup 동작 방식

- xtrabackup 은 InnoDB의 crash-recovery 기능을 기반으로한다.

- InnoDB 데이터 파일을 복사하므로 내부적으로 일관성이 없는 데이터 생성된다. 

  그런 다음 파일에 대해 응급 복구를 수행하여 일관되고 사용 가능한 데이터베이스를 다시 만든다. 

- 이 작업은 InnoDB가 트랜잭션 로그라고도하는 Redo Log를 유지하기 때문에 작동한다. 

- Redo Log에는 InnoDB 데이터에 대한 모든 변경 기록이 포함된다.

  InnoDB가 시작되면 데이터 파일과 트랜잭션 로그를 검사하는 단계를 수행한다.

  커밋된 트랜잭션 로그 항목을 데이터 파일에 적용하고 데이터를 수정했지만

  커밋하지 않은 모든 트랜잭션에 대해 실행 취소 작업을 수행한다. 

- xtrabackup 은 시작할때 로그 시퀀스 번호(LSN)을 기억한 당므 데이터 파일을 복사하는 방식으로 작동한다. 

  이를 수행하는데 약간의 시간이 걸리므로 파일이 변경되면 다른 시점의 데이터베이스 상태를 반영한다. 

- xtrabackup 은 트랜잭션 로그파일을 감시하는 백그라운드 프로세스를 실행해서 변경 사항을 복사한다. 

  트랜잭션 로그가 라운드 로빈 방식으로 작성되고 재사용 가능성이 있기 때문에 이를 지속적으로 수행한다. 

- xtrabackup 실행후 데이터 파일이 변경될 때마다 트랜잭션 로그 레코드가 필요하다. 

- xtrabackup은 FLUSH TABLES WITH READ LOCK의 경량 대안으로 사용 가능한 백업 작업을 사용한다. 

  이 기능은 MySQL 5.6 이후부터 사용가능하며, MySQL 8.0에서는 LOCK INSTANCE FOR BACKUP 문을 통해

  인스턴스 수준 백업 잠금을 획득할 수 있다. 

- 잠금은 xtrabackup 이 모든 InnoDB/XtraDB 데이터 및 로그 백업을 완료한 후에

  MyISAM 및 non-InnoDB 테이블에 대해서만 수행된다.

- LOCK INSTANCE FOR BACKUP 또는 LOCK TABLES FOR BACKUP을 효과적으로 사용하려면 

  performance_schema.log_status를 쿼리하기 위해 BACKUP_ADMIN 권한이 필요하다.

- xtrabackup은 인스턴스에 InnoDB 테이블만 포함된 경우 백업 작금 및 FLUSH TABLES WITH READ LOCK을 방지한다. 

  이 경우 xtrabackup 은 perfomance_schema.log_status 에서 binary log position를 가져온다. 

  --slave-info 로 xtrabackup을 시작할때 MySQL 8.0에서 FLUSH TABLES WITH READ LOCK가 여전히 필요하다. 

-  Percona Server for MySQL 8.0 의 log_status 테이블은 relay log 좌표를 포함하도록 확장되어

   --slave-info 옵션을 사용해도 잠금이 필요하지 않다. 

 

- 서버에서 backup lock을 지원하면 xtrabackup은 InnoDB 데이터를 복사하고 LOCK TABLES FOR BACKUP을 실행한

  다음 MyISAM 테이블을 복사한다. 이 작업이 완료되면 파일 백업이 시작된다.

  .frm, .MRG, .MYD, .MYI, .ARM, .ARZ, .CSM, .CSV, .sdi 및 .par 파일을 백업한다. 

- 그 후 xtrabackup은 LOCK BINLOG FOR BACKUP 을 사용하여 SHOW MASTER / SLAVE STATUS 에서 확인한 

  Binary log position, Exec_Master_Log_Pos, Exec_Gtid_Set 을 변경할 수 있는 모든 작업을 차단한다. 

- xtrabackup 은 Redo Log 파일 복사를 완료하고 Binary log position을 가져온다. 

  이것이 완료되면 xtrabackup은 Binary log와 테이블의 잠금을 해제한다. 

- 마지막으로 Binary log position은 STDERR에 인쇄되고 xtrabackup 은 모든 것이 정상이면 0을 반환하고 종료된다. 

  xtrabackup 의 STDERR은 파일에 기록되지 않는다. 

  파일로 리디렉션 해야한다. (예 : xtrabackup OPTIONS 2>backupout.log)

 

- 백업 디렉토리에 다음 파일을 생성한다. 

  - backup-my.cnf

    my.cnf 파일의 백업이 아니며, --prepare 중에 InnoDB 의 미니 인스턴스를 시작하기 위한 정보가 포함되어 있다.

  - xtrabackup_checkpoints

    백업 유형(전체 또는 증분), 상태(ex, prepared) 및 포함된 LSN 범위가 저장되어 있으며

    이 정보는 증분 백업에 사용된다. 

  - xtrabackup_binlog_info

    SHOW MASTER STATUS의 확인된 Binary Log 파일 및 Position 

  - xtrabackup_binlog_pos_innodb

    Binary Log 파일 및 InnoDB 또는 XtraDB 테이블의 현재 위치 

  - xtrabackup_binary

    프로세스에 사용되는 xtrabackup 바이너리 

  - xtrabackup_logfile

    --prepare 를 실행하는데 필요한 데이터를 포함한다.

    이 파일이 클수록 --prepare 프로세스에 걸리는 시간이 늘어난다. 

  - <table_name>.delta.meta

    증분 백업을 수행할때 생성되는 파일로 페이지 크기, 압축된 페이지 크기 및 공간 ID와 같은

    테이블별 delta metadata 가 포함된다. 

  - xtrabackup_slave_info

    --slave-info 옵션을 사용하는 경우 생성되며 복제본 설정에 필요한 CHANGE MASTER 문이 기록된다. 

  - xtrabackup_galera_info

    --galera-info 옵션을 사용하는 경우 생성되며 Galera 및 Percona XtraDB 클러스터 정보가(wsrep_local_state_uuid 및 wsrep_last_committed 상태 변수의 값) 기록된다. 

    

- perpare 단계 동안 xtrabackup은 복사된 트랜잭션 로그 파일을 사용하여 복사된 데이터 파일에 대해 응급복구를 수행한다.  이 작업이 완료되면 데이터베이스를 복원하고 사용할 수 있다.

- perpare(recovery) 프로세스후에 InnoDB의 데이터는 백업이 완료된 시점으로 롤백되지 않고 백업이 완료된 시점으로 롤 포위드 된다. 이 시점은 FLUSH TABLES WITH READ LOCK이 취해진 위치와 일치하므로 MySQL 데이터와 준비된 InnoDB 데이터가 동기회된다. 

 

백업 복원 

- xtrabackup 으로 백업으로 복원하려면 --copy-back 또는 --move-back 옵션을 사용한다. 

- xtrabackup은 my.cnf에서 datadir, innodb_data_home_dir, innodb_data_file_path, innodb_log_group_home_dir 변수를

  읽고 디렉토리가 있는지 확인한다.

- 아래의 순서로 복사한다.

  1) MyISAM 테이블, 인덱스 등 (.MRG, .MYD, .MYI, .ARM, .ARZ, .CSM, .CSV, .sdi 및 par 파일)

  2) InnoDB 테이블과 인덱스

  3) 로그

- 파일을 복사 할 때 파일의 속성을 보존한다.

  - 데이터베이스 서버를 시작하기 전에 복원된 데이터 파일의 소유권 확인, 변경 한다.

- --move-back 옵션을 사용하여 백업을 복원할 수 있다. 

   --copy-back 옵션과 비슷하지만 파일을 복사하는 대신 대상위치로 이동하는다는 점만 다르다. 

    대상위치로 이동하기 때문에 속도는 빠르지만 백업 파일이 이동되어 사라진다. 

 

참고:

https://www.percona.com/doc/percona-xtrabackup/LATEST/index.html

 

'MySQL' 카테고리의 다른 글

MySQL Performance Best Practice  (0) 2021.12.26
xtrabackup 8.0 (2)  (0) 2021.07.06
Hash join in MySQL 8  (0) 2021.06.25
Redo Log  (0) 2021.06.25
Update process  (0) 2021.06.19

MySQL 8.0.18 부터 Hash Join 을 지원한다. 

Hash Join 은 Hash Table을 사용하여 두 입력간에 일치하는 행을 찾는 Join  방법이다. 

입력 중 하나가 메모리에 들어갈 수 있는 경우 Nested Loop Join 보다 효율적이다. 

 

빌드 단계

일반적으로 Hash Join을 빌드 단계와 프로브 두단계로 나눈다.

빌드 단계에서 서버는 조인 속성을 해시 테이블 키로 사용하여 입력 중 하나의 행이 저장되는 인메모리 해시 테이블을 빌드한다. 

이 입력을 빌드 입력이라고도하며 'countries'가 빌드 입력으로 지정되었다고 가정했을때, 이상적으로 서버는 두 입력중 더 작은것을 빌드 입력으로 선택한다. (행수가 아닌 바이트로 측정) 'countries.country_id'는 빌드 입력에 속하는 조인 조건이므로 해시 테이블에서 키로 사용된다. 모든 행이 해시 테이블에 저장되면 빌드 단계가 완료된다. 

프로브 단계

프로브 단계 동안 서버는 프로브 입력(이 예에서는 'persons')에서 행을 읽기 시작한다. 

각 행에 대해 서버는 'persons.country_id'의 값을 조회 키로 사용하여 행과 일치하는 해시 테이블을 조사한다. 각 일치에 대해 조인된 행이 클라이언트로 전송된다. 결국 서버는 두 입력간에 일치하는 행을 찾기 위해 일정한 시간 조회를 사용하여 각 입력을 한번 만 스캔했다. 

이것은 서버가 전체 빌드 입력을 메모리에 저장할 수 있다는 점을 감안할때 매우 잘 작동한다. 사용 가능한 메모리양은 시스템변수 join_buffer_size에 의해 제이되며 운영중에 조정할 수 있다. 그러나 빌드 입력이 사용 가능한 메모리보다 크면 디스크를 사용한다. 

 

디스크로 유출

빌드 단계에서 메모리가 가득차면 서버는 빌드 입력의 나머지 부분을 디스크의 여러 청크 파일에 쓴다. 서버는 가장 큰 청크가 메모리에 정확히 맞도록 청크 수를 설정하려고 하지만 MySQL은 입력당 최대 128개의 청크파일제한한다. 행이 기록되는 청크 파일은 조인 속성의 해시를 계산하여 결정된다. 그림에는 메모리 내 빌드 단계에서 사용된것과 다른 해시 함수가 사용된다. 그 이유는 나중에 살펴본다. 

프로브 단계 동안 서버는 모든 것이 메모리에 맞을때와 마찬가지로 해시 테이블에서 일치하는 행을 조사한다. 그러나 행은 디스크에 기록된 빌드 입력의 행중 하나와 일치할 수도 있다. 따라서 프로브 입력의 각 행도 청크 파일 세트에 기록된다. 행이 기록되는 청크 파일은 빌드 입력이 디스크에 기록될때 사용되는 동일한 해시 함수 및 공식을 사용하여 결정된다. 이렇게 하면 두 입력사이에 일치하는 행이 동일한 청크파일쌍에 위치하게 된다. 

프로브 단계가 완료되면 디스크에서 청크 파일을 읽기 시작한다. 일반적으로 서버는 첫번째 청크 파일 세트를 빌드 및 프로브 입력으로 사용하며 빌드 및 프로브 단계를 수행한다. 빌드 입력의 첫번째 청크 파일을 메모리 내 해시 테이블로 로드한다. 이것은 우리가 메모리에 정확히 맞는 가장 큰 청크를 원하는 이유를 설명한다. 청크 파일이 너무 크면 더 작은 청크로 분할해야한다. 빌드 청크가 로드된후 프로브 입력에서 해당 청크 파일을 읽고 모든 것이 메모리에 맞을때와 마찬가지로 해시 테이블에서 일치항목을 검색한다. 청크 파일의 첫번째 쌍을 처리하면 다음 쌍의 청크 파일로 이동하고 모든 청크파일이 처리 될때까지 계속한다. 

이제 행을 청크 파일로 분할하고 해시 테이블에 행을 쓸때 두개의 다른 해시함수를 사용해야하는 이유를 짐작했을 것이다. 두작업 모두에 동일한 해시 함수를 사용하면 동일한 청크 파일의 모든 행이 동일한 값으로 해시되므로 빌드 청크 파일을 해시 테이블에 로드 할때 매우 나쁜 해시 테이블을 얻게 된다. 

 

Hash Join 사용방법

해시 조인은 기본적으로 활성화되어 있으므로 해시 조인을 사용하기 위해 필요한 작업이 없다. 한가지 주목할만한 점은 해시 조인이 새로운 반복자 실행기를 기반으로 구축된다는 것이다. 즉 EXPLAIN FORMAT=tree를 사용하여 해시 조인이 사용되는지 여부를 확인할 수 있다. 

mysql> EXPLAIN FORMAT=tree
    -> SELECT
    ->   given_name, country_name
    -> FROM
    ->   persons JOIN countries ON persons.country_id = countries.country_id;
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN                                                                                                                                                                                                 |
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Inner hash join (countries.country_id = persons.country_id)  (cost=0.70 rows=1)
|   -> Table scan on countries  (cost=0.35 rows=1)
|   -> Hash
|      -> Table scan on persons  (cost=0.35 rows=1)
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.01 sec)

일반적으로 하나 이상의 동등 조인 조건을 사용하여 테이블을 조인하고 조인 조건에 사용할 수 있는 인덱스가 없는 경우 해시 조인이 사용된다. 인덱스를 사용할 수 있는 경우 MySQL은 인덱스 조회가 있는 중첩 루프(Nested Loop)를 선호하는 경향이 있다. 

 

모든 쿼리에 대해 해시조인을 비활성화 할수 있는 새로운 optimizer switch를 도입했다. 

mysql> SET optimizer_switch="hash_join=off";
Query OK, 0 rows affected (0.00 sec)
 
mysql> EXPLAIN FORMAT=tree
    -> SELECT
    ->   given_name, country_name
    -> FROM
    ->   persons JOIN countries ON persons.country_id = countries.country_id;
+----------------------------------------+
| EXPLAIN                                |
+----------------------------------------+
|                                        |
+----------------------------------------+
1 row in set (0.00 sec)

해시 조인이 꺼지면 MySQL은 블록 중첩 루프로 돌아가고 따라서 이전 실행 프로그램이 된다. (블록 중첨 루프는 반복기 실행 프로그램에서 지원되지 않는다.) 이 스위치를 사용하면 해서 조인과 블록 중첩 루프의 성능을 쉽게 비교할 수 있다. 

 

빌드 입력이 너무 커서 메모리에 맞지 않아 해시조인이 디스크를 사용하는 경우 조인 버퍼 크기를 늘릴 수 있다. 블록 중첩 로프와 달리 해시 조인은 메모리를 점진적으로 할당하므로 필요한것보다 더 많은 메모리를 사용하지 않는다. 이러한 이유로 해시 조인을 사용할 때 더 큰 조인 버퍼 크기를 사용하는것이 더 안전하다. 

 

해시 조인 제한사항

MySQL은 내부 해시 조인만 지원한다. 

anit, semi, outer join 은 블록 중첩 루프를 사용하여 실행된다. 

optimizer/planner 는 블록 중첩 루프를 사용하여 조인을 실행하는것에 우선을 둔다. 

 

 

참고

https://mysqlserverteam.com/hash-join-in-mysql-8/

 

'MySQL' 카테고리의 다른 글

xtrabackup 8.0 (2)  (0) 2021.07.06
xtrabackup 8.0 (1)  (0) 2021.06.30
Redo Log  (0) 2021.06.25
Update process  (0) 2021.06.19
OPTIMIZE TABLE Statement  (0) 2021.06.18

- Redo Log는 불완전한 트랜잭션에 의해 작성된 데이터를 수정하기 위해 응급 복구 중에 사용되는 디스크 기반 데이터 구조이다. 정상 작업중에는 Redo Log는 SQL문 또는 하위 수준 API 호출로 인한 테이블 데이터 변경 요청을 인코딩한다. 예기치 않은 종료 전에 데이터 파일 업데이트를 완료하지 않은 수정 사항은 초기화 중, 연결이 승인되기전에 자동으로 재생된다.

- 기본적으로 Redo Log는 ib_logfile0, ib_logfile1 이라는 두개의 파일로 디스크에 물리적으로 저장된다. MySQL은 순환방식으로 Redo Log 파일에 기록한다. Redo Log의 데이터는 영향을 받는 레코드 측면에서 인코딩된다. 이 데이터 집합을 redo 라고 한다. Redo Log를 통한 데이터 전달은 계쏙 증가하는 LSN 값으로 표시된다. 

 

Redo Log 파일수 또는 크기 변경

- MySQL 서버를 중지하고 오류 없이 종료되었는지 확인한다. 

- my.cnf 를 편집하여 로그 파일 구성을 변경한다. 

  로그 파일 크기를 변경하려면 innodb_log_file_size 를 수정한다. 

  로그 파일 수를 늘릴려면 innodb_log_file_in_group을 수정한다.

- MySQL 서버를 다시 시작한다. 

- InnoDB는 innodb_log_file_size가 Redo Log 파일 크기와 다른것을 감지하면 로그 체크 포인트를 작성하고 이전 로그파일을 닫고 제거하며 요청된 크기로 새 로그 파일을 생성하고 새 로그 파일을 연다. 

 

Redo Log 플러싱을 위한 그룹 커밋

- 다른 ACID 호환 데이터베이스 엔진과 마찬가지로 InnoDB는 트랜잭션이 커밋되기 전에 다시 실행 로그를 플러시한다.  InnoDB는 그룹 커밋 기능을 사요하여 여러 플러시 요청을 함께 그룹화하여 각 커밋에 대해 하나의 플러시를 방지한다. 그룹 커밋을 사용하면 InnoDB는 로그 파일에 단일 쓰기를 실해앟여 거의 동시에 커밋하는 여러 사용자 트랜잭션에 대해 커밋 작업을 수행하여 처리량을 크게 향상시킨다. 

 

Redo Log Archiving

- Redo Log 레코드를 복사하는 백업 유틸리티는 때때로 백업 작업이 진행되는 동안 Redo Log 생성 속도를 유지하지 못하여 해당 레코드를 덮어 쓰므로 Redo Log 레코드가 손실 될 수 있다. 이 문제는 백업 작업중 상당한 MySQL 서버 활동이 있고 Redo Log 파일 저장소 미디어가 백업 저장소 미디어보다 빠른 속도로 작동할때 가장 자주 발생한다. MySQL 8.0.17에 도입된 Redo Log Archiving 기능은 Redo Log 파일 외에도 Redo Log 레코드를 아카이브 파일에 순차적으로 기록하여 이 문제를 해결한다. 백업 유틸리티는 필요에 따라 아카이브 파일에서 Redo Log 레코드를 복사하여 잠재적인 데이터 손실을 방지할 수 있다. 

- Redo Log Archiving이 서버에 구성되어 있는 경우 MySQL Enterprise Edition을 사용할 수 있는 MySQL Enterprise Backup 은 MySQL 서버를 백업할때 Redo Log Archiving 기능을 사용한다. 

- Redo Log Archiving을 활성화 하려면 innodb_redo_log_archive_dirs 시스템 변수의 값을 설정한다. 

  값은 레이블이 저장된 Redo Log 아카이브 디렉토리의 세미콜론으로 구분된 목록으로 지정된다. lable:directory 쌍은 콜론(:)으로 구분된다. 

  예)

mysql> SET GLOBAL innodb_redo_log_archive_dirs='label1:directory_path1[;label2:directory_path2;…]';

- lable은 아카이브 디렉토리에 대한 임의의 식별자이다. 허용되지 않는 콜론(:)을 제외한 모든 문자열을 사용할 수 있다. 빈 lable도 허용되지만 이 경우에도 콜론(:)이 필요하다. 

  directory_path를 지정해야한다. Redo Log 아카이브 파일에 대해 선택한 디렉토리는 Redo Log 아키이브가 활성화되거나 오류가 반활 될 때 존재해야한다. 경로에는 콜론(:)이 포함될 수 있지만 세미콜론(;)은 허용되지 않는다. 

- Redo Log 아카이브를 활성화하기 전에 innodb_redo_log_archive_dirs 변수를 설정해야한다. 기본값은 NULL 이며 Redo Log 아카이브 활성화를 허용하지 않는다. 

- Redo Log 아키이브를 지원하는 백업 유틸리티가 백업 유틸리티가 백업을 시작하면 백업 유틸리티는 innodb_redo_log_archive_start() 함수를 호출하여 Redo Log 아카이브를 활성화한다. 

- Redo Log 아카이브를 지원하지 백업 유틸리티를 사용하지 않는 경우 다음과 같이 Redo Log 아카이브를 수동으로 활성화 할 수도 있다. 

mysql> SELECT innodb_redo_log_archive_start('label', 'subdir');
+------------------------------------------+
| innodb_redo_log_archive_start('label') |
+------------------------------------------+
| 0                                        |
+------------------------------------------+

또는 
mysql> DO innodb_redo_log_archive_start('label', 'subdir');
Query OK, 0 rows affected (0.09 sec)

- Redo Log 아카이브를 활성화하는 MySQL 세션은 (innodb_redo_log_archive_start() 사용)은 archiving 기간동안 열려 있어야한다. 반드시 동일 세션에서 Redo Log 아카이브를 비활성화(innodb_redo_log_archive_stop() 사용) 해야한다.  Redo Log 아카이브가 명시적으로 비활성화되기 전에 세션이 종료되면 서버는 Redo Log 아키이브를 암시적으로 비활성화하고 Redo Log 아카이브 파일을 제거한다. 

- innodb_redo_log_archive_start 에서 lable은 innodb_redo_log_archive_dirs에 정의된 레이블이다. subdir은 아카이브 파일을 저장하기 위해 lable로 식별되는 디렉토리 하위 디렉토리를 지정하기 위한 선택적인 입력값이다. 단순한 디렉토리 이름이어야한다. (슬레시(/), 백슬레시(\) 또는 콜론(:)은 허용되지 않는다.(). subdir은 비어 있거나 NULL이거나 생략 할 수 있다. 

- INNODB_REDO_LOG_ARCHIVE 권한이있는 사용자만 innodb_redo_log_archive_start()를 호출하여 Redo Log 아카이브를 활성화하거나 innodb_redo_log_archive_stop()을 사용하여 비활성화할 수 있다. 백업 유틸리티를 실행하는 MySQL 사용자 또는 수동으로 Redo Log 아카이브를 활성화 및 비활성화하는 MySQL 사용자는 이 권한이 있어야한다. 

- Redo Log 아카이브 파일 경로는 directory_identified_by_label/[subdir/]archive.serverUUID.000001.log 입니다. 여기서 directory_identified_by_label은 innodb_redo_log_archvie_start()의 레이블 인수로 식별되는 아카이브 디렉토리이다. subdir은 innodb_redo_log_archive_start()에 사용되는 선택적인 입력값이다. 

  예를 들어 Redo Log 아카이브 파일의 전체 경로 및 이름은 다음과 유사하게 나타난다. 

/directory_path/subdirectory/archive.e71a47dc-61f8-11e9-a3cb-080027154b4d.000001.log

- 백업 유틸리티가 InnoDB 데이터 파일 복사를 마치면 innodb_redo_log_archive_stop() 함수를 호출하여 Redo Log 보관(archiving)을 비활성화 한다. 

- Redo Log 아카이브를 지원하는 백업 유틸리티를 사용하지 않는 경우 다음과 같이 Redo Log 아카이브를 수동으로 비활성화 할 수 있다. 

 

mysql> SELECT innodb_redo_log_archive_stop();
+--------------------------------+
| innodb_redo_log_archive_stop() |
+--------------------------------+
| 0                              |
+--------------------------------+

또는
mysql> DO innodb_redo_log_archive_stop();
Query OK, 0 rows affected (0.01 sec)

- stop function 이 성공적으로 완료되면 백업 유틸리티는 아카이브 파일에서 Redo Log 데이터의 관련 섹션을 찾아 백업에 복사한다. 

- 백업 유틸리티가 Redo Log 데이터 복사를 완료하고 더 이상 Redo Log 아카이브 파일이 필요하지 않으면 아카이브 파일을 삭제한다. 

- 아카이브 파일의 제거는 정상적인 상황에서 백업 유틸리티가 제거한다. 그러나 Innodb_redo_log_archive_stop()이 호출되기 전에 Redo Log 아카이브 작업이 예기치 않게 종료되면 MySQL 서버가 아카이브 파일을 제거한다. 

 

성능 고려 사항 

- Redo Log 아카이브를 활성화하면 일반적으로 추가 쓰기 작업으로 인해 약간의 성능 비용이 발생한다.

- Unix 및 Uinx 유사 운영 체제에서 지속적으로 높은 업데이트 비율이 없다고 가정하면 성능에 미치는 영향은 일반적으로 경미하다. Windows 에서 성능 영향은 일반적으로 조금 더 높으며, 동일하게 가정한다. 

- 업데이트 비율이 지속적으로 높고 Redo Log 아카이브 파일이 Redo Log 파일보다 느린 저장매체에 있는 경우 복합적인 쓰기 활동으로 인해 성능 영향이 더 클 수 있다.

- 업데이트 비율이 지속적으로 높고 Redo Log 아카이브 파일이 Redo Log 파일보다 느린  저장매체에 있는 경우 성능이 임의로 영향을 받는다.

- Redo Log 아카이브 파일에 쓰기는 Redo Log 아카이브 파일 저장매체가 Redo Log 파일 저장매체보다 휠씬 느린속도로 작동하는 경우를 제외하고는 정상적인 트랜잭션 로깅을 방해하지 않는다. 

- 이 경우 트랜잭션 로깅 속도는 Redo Log 아카이브 파일이 있는 느린 저장매체에서 관리 할 수 있는 수준으로 감소한다. 

 

Redo Logging 비활성화 

- MySQL 8.0.21 부터 ALTER INSTANCE DISABLE INNODB REDO_LOG 문을 사용하여 Redo Logging 을 비활성화 할 수 있다.  이기능은 데이터를 새로운 MySQL 인스턴스로 로드하기 위한것이다. Redo Logging을 비활성화하면 Redo Log 쓰기 및 doublewrite 버퍼링을 방지하여 데이터 로드 속도를 높일 수 있다. 

- Redo Logging 활성화 비활성화를 하려면 INNODB_REDO_LOG_ENABLE 권한이 필요하다.

- innodb_redo_log_enabled 상태 변수로 Redo Logging 상태 모니터링 할 수 있다.

- Redo Logging 이 비활성화된 동안 복제 작업 및 Redo Log 아카이브가 허용되지 않으며 그 반대의 경우도 마찬가지다.

- ALTER INSTANCE [ENABLE|DISABLE] INNODB REDO_LOG  작업에는 다른 ALTER INSTANCE 작업이 동시에 실행되는것을 방지하는 베타적 백업 백업 메타 잠금(exclusive backup metadata lock)이 필요하다. 다른 ALTER INSTANCE 작업은 잠금이 해제 될때까지 기다려야 한다. 

 

예제) 새로운 MySQL 인스턴스로 데이터를 로드할때 Redo Logging을 비활성화하는 방법

1. INNODB_REDO_LOG_ENABLE 권한 부여 

mysql> GRANT INNODB_REDO_LOG_ENABLE ON *.* to 'data_load_admin';

2. data_log_admin 계정으로 Redo Logging을 비활성화 

mysql> ALTER INSTANCE DISABLE INNODB REDO_LOG;

3. innodb_redo_log_enabled 상태 변수를 확인하여 Redo Logging 이 비활성화 되었는지 확인 

mysql> SHOW GLOBAL STATUS LIKE 'Innodb_redo_log_enabled';
+-------------------------+-------+
| Variable_name           | Value |
+-------------------------+-------+
| Innodb_redo_log_enabled | OFF   |
+-------------------------+-------+

4. 데이터 로드 작업 실행

5. 데이터 로드 작업 완료후 data_load_admin 계정으로 Redo Logging 활성화 

mysql> ALTER INSTANCE ENABLE INNODB REDO_LOG;

6. innodb_redo_log_enabled 상태 변수를 확인하여 Redo Logging 이 활성화 되었는지 확인

 

mysql> SHOW GLOBAL STATUS LIKE 'Innodb_redo_log_enabled';
+-------------------------+-------+
| Variable_name           | Value |
+-------------------------+-------+
| Innodb_redo_log_enabled | ON    |
+-------------------------+-------+

 

참고

https://dev.mysql.com/doc/refman/8.0/en/innodb-redo-log.html

 

'MySQL' 카테고리의 다른 글

xtrabackup 8.0 (1)  (0) 2021.06.30
Hash join in MySQL 8  (0) 2021.06.25
Update process  (0) 2021.06.19
OPTIMIZE TABLE Statement  (0) 2021.06.18
ANALYZE TABLE Statement  (0) 2021.06.17

1. 트랜잭션 시작 (처음으로 트랜잭션 시작)

- 이 트랜잭션에 트랜잭션 ID(TRX_ID)를 할당한다. 트랜잭션 ID는 시스템 테이블 스페이스의 TRX_SYS 페이지에서 가장 큰 트랜잭션 ID필드에 기록될 수 있다. 

  - 시스템 테이블 스페이스의 TRX_SYS 페이지에서 가장 큰 트랜잭션 ID 필드가 업데이트되면 업데이트가 Redo Log에 기록된다. 

- 할당된 TRX_ID를 기반으로 read view를 만든다. 

 

2. 레코드 수정 (한번에 하나의 레코드 행만 수정됨)

2-1. Undo Log 로그 공간 할당 

2-2. 수정전 값을 Undo Log 로그에 복사 (실제 변경되는 컬럼의 이전 값과 PK 값만 저장)

2-3. 버퍼풀 데이터 페이지의 변경 대상 레코드의 메타 정보중에서 Roll-Point 값을 2-2에서 생성된 Undo Log의 주소 값으로 저장

2-4. 버퍼풀 데이터 페이지의 변경 대상 레코드의 컬럼 값을 새로운 값으로 변경하고, 변경되는 컬럼의 이후 값과 데이터 페이지 주소 및 Offset을 Redo 로그에 저장. 롤백 세그먼트 포인터는 Undo Log에 있는 레코드의 이전 버전을 가리킨다. 

2-5. 변경되는 컬럼을 이용한 인덱스가 있을 경우 인덱스의 값 변경 

      해당 인덱스 페이지가 버퍼풀에 없으면 Insert Buffer에 임시 저장한다. 

      - 인덱스 페이지는 단순히 기존 인덱스 키를 덮어쓰기 하는 것이 아니라, 기존 인덱스 키는 그대로 보존하고 새로운 키 값을 추가한다. 

      - 인덱스 키 하나는 트랜잭션 가시성에 관계없이 모든 값들을 다 가지고 있게된다. 

         (변경 전/후의 레코드 포인터 모두 가지고 있다.)

 

3. 다른 트랜잭션은 어떻게 되나요?

- 레코드가 수정되면 커밋되지 않더라도 다른 트랜잭션도 해당 트랜잭션 격리 수준에 따라 수정된 레코드를 볼 수 있다. 

  - RU 격리 수준인 경우 색인 페이지를 사용하여 최신 버전 기록을 읽는다.

  - RC 격리 수준인 경우 가정 최근에 제출된 레코드 버전 검색

  - RR 격리 수준인 경우 read view에 해당하는 레코드 버전을 찾는다. 

- 최신 버전 레코드보다 오래된 버전 레코드를 읽기 위해 인덱스 페이지를 사용해야하는 경우, 이전 버전 레코드를 다시 빌드하려면 Undo Log 를 사용해야한다. 

 

4. 트랜잭션 커밋

- 트랜잭션에 해당하는 Undo Log 페이지는 "purge"로 설정된다. 

  - Undo Log 페이지는 다른 트랜잭션에서 더이상 참조되지 않을때 지을 수 있다.

- Redo Log 버퍼가 디스크의 로그 파일로 기록된다. 

  - 디스크 플러시 여부는 innodb_flush_log_at_trx_commit 시스템 변수 값에 따라 다르다. 

- Redo Log의 더티페이지가 로그 파일에 기록되기 전에 이 더티 페이지를 만든 LSN 이전의 LSN의 Read Log는 반드시 먼저 디스크에 기록되어야 한다. 

 

5. 백그라운드 스레드가 다른 트리거 매카니즘에 따라 지속적으로 새로고침을 트리거함 

- 가장 오래된 더티 페이지를 찾아서 플러시 배치에 추가한다.

- 플러시 배치는 최신 LSN 번호가 리두 로그에 기록되고 배치되었는지 확인한다.

- duble write가 활성화 된 경우 더티 페이지는 먼저 duble write 버퍼로 플러시 되고 동기화를 기다린다. 

- 버퍼풀의 각 더티 페이지를 최종 목적지 (테이블 스페이스 파일)에 쓴다. 

 

6. 정기적으로 체크 포인트 수행

- 체크 포인트보다 오래된 더티 페이지(체크 포인트 LSN보다 작은)가 테이블 스페이스 파일로 기록됐는지 확인한다.  체크 포인트 LSN보다 큰 더티 페이지가 있는 경우 더티 페이지가 즉시 데이터 파일로 기록된다. 

  - 체크 포인터로 인해 디스크에 기록되는 데이터가 모두 Commit된 데이터가 아닐 수 있다.

 

7. 백그라운드 스레드 제거 

- 백그라운드 스레드는 Undo Log 및 히스토리 연결 목록을 포함하여 필요에 따라 주기적으로 지속적으로 제거를 실행함 

- 각 롤백 세그먼트에서 더이상 필요하지 않은 가장 오래된 Undo Log를 찾는다. 

- 실제로 인덱스에서 삭제 표시가 있는 레코드를 삭제한다. 

- Undo Log 페이지 해제 

 

참고

https://www.programmersought.com/article/49353530904/

http://intomysql.blogspot.com/2010/12/innodb-redoundo.html

https://m.blog.naver.com/PostView.nhn?isHttpsRedirect=true&blogId=lyh1620&logNo=220794460510&categoryNo=46 

 

'MySQL' 카테고리의 다른 글

Hash join in MySQL 8  (0) 2021.06.25
Redo Log  (0) 2021.06.25
OPTIMIZE TABLE Statement  (0) 2021.06.18
ANALYZE TABLE Statement  (0) 2021.06.17
GTID - 트랜잭션 건너뛰기  (0) 2021.06.13
OPTIMIZE [NO_WRITE_TO_BINLOG | LOCAL]
    TABLE tbl_name [, tbl_name] ...

- OPTIMIZE TABLE은 테이블 데이터 및 관련 인덱스 데이터의 물리적 저장소를 재구성하여 저장 공간을 줄이고 테이블에 액세스할때 I/O 효율성이 향상된다. 테이블의 스토리지 엔진에 따라 테이블의 변경사항이 달라진다. 

- OPTIMIZE TABLE은 테이블 유형에 따라 다음과 같은 경우에 사용한다. 

  - innodb_file_per_table 옵션이 활성화된 상태에서 자체 .ibd 파일이 있는 InnoDB 테이블에서

    대량 삽입, 업데이트 또는 삭제작업을 수행한 후 테이블과 인덱스가 재구성되고 운영 체제에서 사용하기 위해

    디스크 공간을 재확보 할 수 있다.

  - InnoDB 테이블의 Fulltext 인덱스의 일부인 컬럼에 대량의 삽입, 업데이트 또는 삭제 작업을 수행한 후

    innodb_optimize_fulltext_only=1 옵션을 설정한다. 색인 유지 보수 기간을 적절한 시간으로 유지하기 위해

    검색 인덱스에서 업데이트하는 단어 개수를 지정하는 innodb_ft_num_word_optimize 옵션을 설정하고

    검색 인덱스가 완전히 업데이트 될때까지 OPTIMIZE TABLE문을 순차적으로 실행한다.

  - MyISAM 또는 ARCHIVE 테이블의 큰 부분을 삭제하거나 가변 길이 행

    (VARCHAR, VARBINARY, BLOB 또는 TEXT 컬럼이 있는 테이블)이 있는 MyISAM 또는 ARCHIVE 테이블을

    많이 변경한후 삭제된 행은 연결된 목록에서 유지 관리되고 후속 INSERT 작업은 이전 행 위치를 재사용한다. 

    OPTIMIZE TABLE 을 사용하여 사용되지 않는 공간을 회수하고 데이터 파일의 조각 모음을 만들수 있다.

    테이블에 대한 광범위한 변경이 있을 경우 OPTIMIZE TABLE 문을 실행하여 성능을 향상시킬수 도 있다. 

- OPTIMIZE TABLE 은 테이블에 대한 SELECT, INSERT 권한이 필요하다.

- OPTIMIZE TABLE 은 InnoDB, MyISAM 및 ARCHIVE 테이블에서 동작한다. 

  OPTIMIZE TABLE 은 in-memory NDB 테이블의 동적 컬럼도 지원한다. in-memory 테이블의 고정 너비 컬럼에는

  작동하지 않으며 디스크 데이터 테이블에서도 작동하지 않는다. NDB 클러스터 테이블의 최적화 성능은

  최적화 테이블을 통해 행 처리 배치 간에 대기하는 시간을 제어하는  --ndb-optimization-delay 을 사용하여

  조정할 수 있다. 

- NDB 클러스터 테이블의 경우 OPTIMIZE TABLE은 최적화 작업을 수행하는 SQL Thread 를 Kill 해서 중단할 수 있다.

  기본적으로 OPTIMIZE TABLE은 다른 저장소 엔진을 사용하여 만든 테이블에 대해 작동하지 않으며 이러한 지원이

  부족하다는 결과를 반환한다. -skip-new 옵션으로 mysqld를 시작하여 다른 스토리지 엔진에 OPTIMIZE TABLE 작업을

  수행할 수 있다. 이경우 OPTIMIZE TABLE은 ALTER TABLE에 매핑된다. 

- OPTIMIZE TABLE 은 뷰에서는 작동하지 않는다. 

- OPTIMIZE TABLE 은 파티션 테이블을 지원한다. 

- OPTIMIZE TABLE 은 바이너리 로그에 기록되어 복제본에 반영된다. 

 

OPTIMIZE TABLE Output

- OPTIMIZE TABLE 은 다음표에 표시된 컬럼로 결과 집합을 반환한다. 

Column Value
Table 테이블 이름
Op 항상 최작화
Msg_type 상태, 오류, 정보, 참고 또는 경고
Msg_text 정보 메시지

- OPTIMIZE TABLE은 테이블을 최적화하여 이전 파일에서 새로 생성된 파일에 테이블 통계를 복사하는 동안 발생하는 오류를 예외처리한다. 예를 들어 .MYD 또는 .MYI 파일의 소유자 사용자ID와 mysqld 프로세스의 사용자ID가 다른 경우 OPTIMIZE TABLE은 root 사용자로 mysqld를 실행하지 않는한 "파일 소유권을 변경할 수 없음"오류를 생성한다.

 

InnoDB 세부 정보 

- 테이블의 경우 OPTIMIZE TABLE은 TABLE ALTER ... FORCE 에 매핑된다. 

  인덱스 통계를 업데이트하고 클러스터된 인덱스에서 사용하지 않은 공간을 해제한다. 

- OPTIMIZE TABLE을 테이블에서 실행할때 다음과 같이 출력된다. 

mysql> OPTIMIZE TABLE foo;
+----------+----------+----------+-------------------------------------------------------------------+
| Table    | Op       | Msg_type | Msg_text                                                          |
+----------+----------+----------+-------------------------------------------------------------------+
| test.foo | optimize | note     | Table does not support optimize, doing recreate + analyze instead |
| test.foo | optimize | status   | OK                                                                |
+----------+----------+----------+-------------------------------------------------------------------+

- OPTIMIZE TABLE은 일반, 파티션 테이블을 OLINE DDL을 사용여 동시 DML 작업에 대한 가동 중지시간을 줄인다. 

  OPTIMIZE TABLE로 트리거되는 테이블 재구축이 완료된다. exclusive table lock은 준비 단계와 작업의 commit 단계에서만 간단히 수행된다. 준비 단계에서 마타데이터가 업데이트되고 중간 테이블이 만들어진다. commit 단계에서 테이블 메타데이터 변경이 commit 된다. 

- OPTIMIZE TABLE은 다음 조건에서 테이블을 copy 방식으로 테이블을 다시 빌드한다. 

  - old_alter_table 시스템 변수가 활성화된 경우

  - 서버 시작시 --skip-new 옵션이 적용된경우 

- OPTIMIZE TABLE은 Full Text Index가 포함된 테이블은 Online DDL을 지원하지 않고 테이블 copy 방식이 사용된다. 

- InnoDB는 페이지 할당 방법을 사용하여 데이터를 저장하며 레거시 스토리지 엔진과 동일한 방식으로 조각화가 발생하지 않는다. 

- OPTIMIZE TABLE을 실행할지 여부를 고려할때 서버가 처리할것으로 예상되는 트랜잭션 워크로드를 고려한다. 

  - 일정 수준의 조각화가 예상되어 page 를 93%까지 채워 page 를 분할하지 않고도 update 할수 있는 공간을 남겨둔다.

  - delete 작업은 page를 원하는것보다 덜 채워 간격을 남길수 있으므로 OPTIMIZE TABLE을 수행하는것이 좋다. 

  - 행에 대한 update 는 일반적으로 충분한 공간을 사용할 수 있는 경우 data type, row format에 따라 동일한 page 내에서 데이터를 다시 작성한다. 

   - 동시성 워크로드가 많기 때문에 MVCC 메커니즘을 통해 동일한 데이터의 여러버전을 유지하므로 시간이 지남에 따라 인덱스의 간격 생길수 있다. 

 

MyISAM 세부 정보 

- MyISAM 테이블의 경우 OPTIMIZE TABLE이 다음과 같이 작동한다.

  - 테이블이 삭제되거나 행을 분할할 경우 테이블을 복구한다. 

  - 인덱스 페이지가 정렬되지 않은 경우 정렬한다.

  - 테이블의 통계가 최시 상태이고 인덱스를 정렬하여 복수를 수행할 수 없는 경우 업데이트한다. 

 

 

참고

https://dev.mysql.com/doc/refman/8.0/en/optimize-table.html

 

'MySQL' 카테고리의 다른 글

Redo Log  (0) 2021.06.25
Update process  (0) 2021.06.19
ANALYZE TABLE Statement  (0) 2021.06.17
GTID - 트랜잭션 건너뛰기  (0) 2021.06.13
GTID 복제 상태 확인  (0) 2021.06.12

+ Recent posts