2020년 10월 30일 금요일

8.0.22 버전부터 변경된 prepared statement에 대하여

2020년 10월 MySQL 8.0.22 버전이 GA가 되었고, 그에 따라 변경된 prepared statement에 대해 알아보자.
먼저 prepared statement가 무엇이고 기본적인 사용법에 대해 알아보자

Prepared statement
placeholder를 이용하여 쿼리를 실행하는 구문으로, SQL Injection을 방어하기 위해 많이 사용한다 

사용법
# 쿼리 준비 PREPARE stmt1 FROM 'SELECT productCode, productName FROM products WHERE productCode = ?'; # 조건문의 변수 설정 SET @pc = 'S10_1678'; # 변수를 이용하여 쿼리 실행 EXECUTE stmt1 USING @pc; # statement 해제 DEALLOCATE PREPARE stmt1;
MySQL 8.0.22 변경 사항
For a prepared statement of the form SELECT expr1, expr2, ... FROM table ORDER BY ?, passing an integer value N for the parameter no longer causes ordering of the results by the Nth expression in the select list; the results are no longer ordered, as is expected with ORDER BY constant.
요약하면.. SELECT하는 COLUMN의 순번으로 ORDER BY를 할 수 없게 되었다
# 테스트용 테이블 생성
CREATE TABLE `test_table` (
  `id` int NOT NULL AUTO_INCREMENT,
  `price` int NOT NULL,
  PRIMARY KEY (`id`)
);

# 데이터 등록
INSERT INTO test_table (price) VALUES (10), (50), (20), (40), (30);

# statement 준비
mysql> PREPARE stmt1 FROM 'SELECT id, price FROM test_table ORDER BY ?';
Query OK, 0 rows affected (0.02 sec)
Statement prepared

# 2번 째로 SELECT하는 컬럼인 price를 기준으로 소트하기 위해 변수 설정
mysql> SET @a=2;
Query OK, 0 rows affected (0.00 sec)

# 예상대로 결과가 정렬되지 않는다
mysql-8.0.22> EXECUTE stmt1 USING @a;
+----+-------+
| id | price |
+----+-------+
|  1 |    10 |
|  2 |    50 |
|  3 |    20 |
|  4 |    40 |
|  5 |    30 |
+----+-------+
5 rows in set (0.01 sec)

# 이전 버전에서는 정상적으로 정렬되는 걸 확인할 수 있다
mysql-8.0.21> EXECUTE stmt1 USING @a;
+----+-------+
| id | price |
+----+-------+
|  1 |    10 |
|  3 |    20 |
|  5 |    30 |
|  4 |    40 |
|  2 |    50 |
+----+-------+
5 rows in set (0.01 sec)

2020년 10월 23일 금요일

SQL_SLAVE_SKIP_COUNTER로 Replication에러가 해소안되는 경우에 대하여

MySQL Replication 에러가 발생했을 때 SQL_SLAVE_SKIP_COUNTER로 해결안되는 경우에 대하여 알아보자.
보통 SQL Statement 레벨의 에러는 SQL_SLAVE_SKIP_COUNTER로 문제 없지만 하드웨어 또는 OS상의 문제로 에러가 발생한 경우는 다르다.

에러내용
# Slave 서버가 알 수 없는 원인으로 OS가 재기동 되었다.
# MySQL 접속 후 확인 한 결과
admin@localhost:(none) >show slave status\G;
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: x.x.x.x
                  Master_User: replication_user
                  Master_Port: 9999
                Connect_Retry: 60
              Master_Log_File: binary_log.010733
          Read_Master_Log_Pos: 305382657
               Relay_Log_File: relay_log.022962
                Relay_Log_Pos: 81579694
        Relay_Master_Log_File: binary_log.010732
             Slave_IO_Running: Yes
            Slave_SQL_Running: No
              Replicate_Do_DB:
          Replicate_Ignore_DB:
           Replicate_Do_Table:
       Replicate_Ignore_Table:
      Replicate_Wild_Do_Table:
  Replicate_Wild_Ignore_Table:
                   Last_Errno: 1594
                   Last_Error: Relay log read failure: Could not parse relay log event entry. The possible reasons are: the master's binary log is corrupted (you can check this by running 'mysqlbinlog' on the binary log), the slave's relay log is corrupted (you can check this by running 'mysqlbinlog' on the relay log), a network problem, or a bug in the master's or slave's MySQL code. If you want to check the master's binary log or slave's relay log, you will be able to know their names by issuing 'SHOW SLAVE STATUS' on this slave.
                 Skip_Counter: 0
          Exec_Master_Log_Pos: 81579530
              Relay_Log_Space: 842450765
              Until_Condition: None
               Until_Log_File:
                Until_Log_Pos: 0
           Master_SSL_Allowed: No
           Master_SSL_CA_File:
           Master_SSL_CA_Path:
              Master_SSL_Cert:
            Master_SSL_Cipher:
               Master_SSL_Key:
        Seconds_Behind_Master: NULL
Master_SSL_Verify_Server_Cert: No
                Last_IO_Errno: 0
                Last_IO_Error:
               Last_SQL_Errno: 1594
               Last_SQL_Error: Relay log read failure: Could not parse relay log event entry. The possible reasons are: the master's binary log is corrupted (you can check this by running 'mysqlbinlog' on the binary log), the slave's relay log is corrupted (you can check this by running 'mysqlbinlog' on the relay log), a network problem, or a bug in the master's or slave's MySQL code. If you want to check the master's binary log or slave's relay log, you will be able to know their names by issuing 'SHOW SLAVE STATUS' on this slave.
  Replicate_Ignore_Server_Ids:
             Master_Server_Id: 4359
                  Master_UUID: vnsjidq-238qwkdj2f-asjzxcniw-euwjds
             Master_Info_File: /mysql/log/master.info
                    SQL_Delay: 0
          SQL_Remaining_Delay: NULL
      Slave_SQL_Running_State:
           Master_Retry_Count: 86400
                  Master_Bind:
      Last_IO_Error_Timestamp:
     Last_SQL_Error_Timestamp: 201019 19:08:37
               Master_SSL_Crl:
           Master_SSL_Crlpath:
           Retrieved_Gtid_Set:
            Executed_Gtid_Set:
                Auto_Position: 0
1 row in set (0.00 sec)
SHOW SLAVE STATUS 해석
Master_Log_File: I/O thread가 처리하고 있는 마스터의 바이너리 로그 파일명
Read_Master_Log_Pos: I/O thread가 읽어들인 마스터의 바이너리 로그 포지션
Relay_Log_File: SQL thread가 처리하고 있는 슬레이브의 릴레리 로그 파일명
Relay_Log_Pos: SQL thread가 실행 완료한 슬레이브의 릴레리 로그 포지션
Relay_Master_Log_File: SQL thread가 마지막으로 실행한 쿼리가 기록되어 있는 마스터의 바이너리 로그 파일명
Exec_Master_Log_Pos: SQL thread가 마지막으로 실행한 쿼리의 마스터 바이너리 로그 포지션
대처 방법 1)
admin@localhost:(none) >SET GLOBAL SQL_SLAVE_SKIP_COUNTER = 1;
admin@localhost:(none) >START SLAVE;

# 에러 내용이 해소되지 않았고 Relay log position 또한 변함 없었다.
대처 방법 2)
# Master DB의 binlog position을 재설정 한 후, 복제 시작
# mysqlbinlog를 이용하여 마스터의 Relay_Master_Log_File 바이너리 로그 파일을 확인
# Exec_Master_Log_Pos 다음 포지션으로 CHANGE MASTER 실행
admin@localhost:(none) >CHANGE MASTER TO MASTER_LOG_FILE='binary_log.010732', MASTER_LO
G_POS=81579770;
admin@localhost:(none) >START SLAVE;
# 에러가 해소되고 복제가 순차적으로 이루어졌다.

2020년 10월 17일 토요일

XtraBackup에 의해서 발생하는 락 경합에 대하여

MySQL 백업을 할 때 사용하는 XtraBackup tool의 락 경합에 의해 일어날 수 있는 이슈에 대해 알아보자.
Single 구성 뿐만 아니라 Master-Slave에서도 일어날 수 있다

테스트 한 환경은 MySQL 5.7과 XtraBackup 2.4이다

XtraBackup 스크립트 작성
간단하게 백업을 실행하는 스크립트를 아래와 같이 작성 후 실행

#!/bin/bash
# test.sh

xtrabackup --backup --datadir=/var/lib/mysql/ --target-dir=/data/backups/mysql/

실행 로그
201016 14:00:11 >> log scanned up to (4633666800)
201016 14:00:11 [01]        ...done
201016 14:00:11 [01] Copying ./test/pg_category_mst.ibd to /data/backups/mysql/wtpayweb/test/pg_category_mst.ibd
201016 14:00:11 [01]        ...done
201016 14:00:11 [01] Copying ./test/wm_log.ibd to /data/backups/mysql/test/wm_log.ibd
201016 14:00:12 [01]        ...done
...skipping...
201016 14:00:13 Executing FLUSH NO_WRITE_TO_BINLOG TABLES...
201016 14:00:13 Executing FLUSH TABLES WITH READ LOCK...
맨 마지막 두 줄 
FLUSH NO_WRITE_TO_BINLOG TABLES, FLUSH TABLES WITH READ LOCK이 락 경합을 일으킬 수 있는 원인이 된다.

락 경합에 의해 발생하는 이슈
1. Replication lag
    - FLUSH TABLES 명령어는 meta data lock을 요구하므로 앞단의 쿼리가 끝날 때 까지 대기한다
# Master에서 테이블 생성
mysql> SELECT COUNT(1) SlaveThreadCount FROM information_schema.processlist WHERE user='system user';
+------------------+
| SlaveThreadCount |
+------------------+
|                0 |
+------------------+
1 row in set (0.01 sec)

mysql> CREATE TABLE lock_test (
    -> id int not null auto_increment,
    -> title varchar(50),
    -> primary key(id)
    -> ) engine=innodb;
Query OK, 0 rows affected (0.05 sec)

mysql> INSERT INTO lock_test (title) VALUES ('First title');
Query OK, 1 row affected (0.02 sec)

# Slave에서 20초 걸리는 SELECT실행
connection-1> SELECT *, SLEEP(20) FROM lock_test;

# 백업을 진행하고 있는 프로세스에서 아래의 명령어가 실행되었다고 가정
connection-2> FLUSH NO_WRITE_TO_BINLOG TABLES;

# Master에서 Insert
mysql> INSERT INTO lock_test (title) VALUES ('Second title');
Query OK, 1 row affected (0.01 sec)

# Slave processlist
connection-3> SHOW PROCESSLIST;
+----+-------------+-----------+------------+---------+-------+----------------------------------+-------------------------------------------------------+
| Id | User        | Host      | db         | Command | Time  | State                            | Info                                                  |
+----+-------------+-----------+------------+---------+-------+----------------------------------+-------------------------------------------------------+
| 20 | system user |           | NULL       | Connect | 80358 | Waiting for master to send event | NULL                                                  |
| 22 | system user |           | test | Connect |     5 | Waiting for table flush          | INSERT INTO lock_test (title) VALUES ('Second title') |
| 28 | root        | localhost | test | Query   |    12 | User sleep                       | SELECT *, SLEEP(20) FROM lock_test                    |
| 29 | root        | localhost | NULL       | Query   |    11 | Waiting for table flush          | FLUSH NO_WRITE_TO_BINLOG TABLES                       |
| 30 | root        | localhost | test | Query   |     0 | init                             | SHOW PROCESSLIST                                      |
+----+-------------+-----------+------------+---------+-------+----------------------------------+-------------------------------------------------------+
5 rows in set (0.00 sec)

connection-3> SHOW PROCESSLIST;
+----+-------------+-----------+------------+---------+-------+----------------------------------+-------------------------------------------------------+
| Id | User        | Host      | db         | Command | Time  | State                            | Info                                                  |
+----+-------------+-----------+------------+---------+-------+----------------------------------+-------------------------------------------------------+
| 20 | system user |           | NULL       | Connect | 80363 | Waiting for master to send event | NULL                                                  |
| 22 | system user |           | test | Connect |    10 | Waiting for table flush          | INSERT INTO lock_test (title) VALUES ('Second title') |
| 28 | root        | localhost | test | Query   |    17 | User sleep                       | SELECT *, SLEEP(20) FROM lock_test                    |
| 29 | root        | localhost | NULL       | Query   |    16 | Waiting for table flush          | FLUSH NO_WRITE_TO_BINLOG TABLES                       |
| 30 | root        | localhost | test | Query   |     0 | init                             | SHOW PROCESSLIST                                      |
+----+-------------+-----------+------------+---------+-------+----------------------------------+-------------------------------------------------------+
5 rows in set (0.00 sec)

connection-3> SHOW PROCESSLIST;
+----+-------------+-----------+------------+---------+-------+-----------------------------------------------------------------------------+------------------+
| Id | User        | Host      | db         | Command | Time  | State                                                                       | Info             |
+----+-------------+-----------+------------+---------+-------+-----------------------------------------------------------------------------+------------------+
| 20 | system user |           | NULL       | Connect | 80403 | Waiting for master to send event                                            | NULL             |
| 22 | system user |           | NULL       | Connect |    50 | Slave has read all relay log; waiting for the slave I/O thread to update it | NULL             |
| 28 | root        | localhost | test | Sleep   |    57 |                                                                             | NULL             |
| 29 | root        | localhost | NULL       | Sleep   |    56 |                                                                             | NULL             |
| 30 | root        | localhost | test | Query   |     0 | init                                                                        | SHOW PROCESSLIST |
+----+-------------+-----------+------------+---------+-------+-----------------------------------------------------------------------------+------------------+
5 rows in set (0.00 sec)

2. 접속 지연
    - 1번과 같은 상황에서 DB 접속을 하기 위해선 "-A" 옵션 지정해야 한다. 미지정 시 대기상태에 빠진다

[root@346ecc2d8c40 /]# mysql -uroot -p test_db_01
Enter password:
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
...

3. Select waiting
    - 1번과 같은 상황에서 동일한 테이블에 대한 SELECT도 대기상태에 빠진다.

connection-4> SELECT * FROM my_table LIMIT 1;
+------+
| id   |
+------+
|    1 |
+------+
1 row in set (0.00 sec)

connection-4> SELECT * FROM lock_test LIMIT 1;

connection-5> SHOW PROCESSLIST;
+----+-------------+-----------+------------+---------+-------+-----------------------------------------------------------------------------+------------------------------------+
| Id | User        | Host      | db         | Command | Time  | State                                                                       | Info                               |
+----+-------------+-----------+------------+---------+-------+-----------------------------------------------------------------------------+------------------------------------+
| 20 | system user |           | NULL       | Connect | 83315 | Waiting for master to send event                                            | NULL                               |
| 22 | system user |           | NULL       | Connect |  2962 | Slave has read all relay log; waiting for the slave I/O thread to update it | NULL                               |
| 28 | root        | localhost | test | Query   |    31 | User sleep                                                                  | SELECT *, SLEEP(20) FROM lock_test |
| 29 | root        | localhost | NULL       | Query   |    28 | Waiting for table flush                                                     | FLUSH NO_WRITE_TO_BINLOG TABLES    |
| 33 | root        | localhost | test | Query   |     0 | init                                                                        | SHOW PROCESSLIST                   |
| 37 | root | localhost | test | Query | 12 | Waiting for table flush | SELECT * FROM lock_test LIMIT 1 |
+----+-------------+-----------+------------+---------+-------+-----------------------------------------------------------------------------+------------------------------------+ 6 rows in set (0.01 sec) mysql> SHOW PROCESSLIST; +----+-------------+-----------+------------+---------+-------+-----------------------------------------------------------------------------+------------------------------------+ | Id | User | Host | db | Command | Time | State | Info | +----+-------------+-----------+------------+---------+-------+-----------------------------------------------------------------------------+------------------------------------+ | 20 | system user | | NULL | Connect | 83323 | Waiting for master to send event | NULL | | 22 | system user | | NULL | Connect | 2970 | Slave has read all relay log; waiting for the slave I/O thread to update it | NULL | | 28 | root | localhost | test | Query | 39 | User sleep | SELECT *, SLEEP(20) FROM lock_test |
| 29 | root | localhost | NULL | Query | 36 | Waiting for table flush | FLUSH NO_WRITE_TO_BINLOG TABLES | | 33 | root | localhost | test | Query | 0 | init | SHOW PROCESSLIST |
| 37 | root | localhost | test | Query | 20 | Waiting for table flush | SELECT * FROM lock_test LIMIT 1 |
+----+-------------+-----------+------------+---------+-------+-----------------------------------------------------------------------------+------------------------------------+ 6 rows in set (0.00 sec) mysql> SHOW PROCESSLIST; +----+-------------+-----------+------------+---------+-------+-----------------------------------------------------------------------------+------------------+ | Id | User | Host | db | Command | Time | State | Info | +----+-------------+-----------+------------+---------+-------+-----------------------------------------------------------------------------+------------------+ | 20 | system user | | NULL | Connect | 83516 | Waiting for master to send event | NULL | | 22 | system user | | NULL | Connect | 3163 | Slave has read all relay log; waiting for the slave I/O thread to update it | NULL | | 28 | root | localhost | test | Sleep | 232 | | NULL |
| 29 | root | localhost | NULL | Sleep | 229 | | NULL | | 33 | root | localhost | test | Query | 0 | init | SHOW PROCESSLIST |
| 37 | root | localhost | test | Sleep | 213 | | NULL |
+----+-------------+-----------+------------+---------+-------+-----------------------------------------------------------------------------+------------------+ 6 rows in set (0.00 sec)

대책
1. xtrabackup 옵션인 "ftwrl-wait-timeout"을 설정하여 방지
2. MySQL 8.0부터 서포트된 "LOCK INSTANCE FOR BACKUP"을 사용

# Refer
https://www.percona.com/doc/percona-xtrabackup/2.4/xtrabackup_bin/xbk_option_reference.html
https://dev.mysql.com/doc/refman/5.7/en/flush.html
https://dev.mysql.com/doc/refman/8.0/en/lock-instance-for-backup.html

2020년 10월 10일 토요일

CREATE TEMPORARY TABLE에 대하여

MySQL애서 일시 테이블을 사용하기 위한 CREATE TEMPORARY TABLE구문에 대하여 알아보자.

일시 테이블을 생성한 세션 내에서만 유효하고, 세션을 끊으면 자동적으로 테이블이 삭제된다. 
사용 가능한 storage engine은 InnoDB, MEMORY, MyISAM, MERGE가 있고 Default는 InnoDB이다

CREATE TEMPORARY TABLE작성
일시 테이블을 사용하기 위해서는 CREATE TEMPORARY TABLES 권한이 필요하다.

mysql> CREATE TEMPORARY TABLE tmp_t1 (
    -> id INT,
    -> name varchar(256)
    -> );
Query OK, 0 rows affected (0.03 sec)

# innodb_strict_mode가 ON인 경우는 ROW_FORMAT지정이 불가능하다.
# 해당 변수는 MySQL 5.7부터 Default가 ON으로 변경되었다.
# SQL표준에 준거하지 않으면 경고가 아닌 에러를 반환하도록 하는 역할
mysql> CREATE TEMPORARY TABLE tmp_t2 (id INT, name varchar(256)) ROW_FORMAT = COMPRESSED;
ERROR 3500 (HY000): CREATE TEMPORARY TABLE is not allowed with ROW_FORMAT=COMPRESSED or KEY_BLOCK_SIZE.

mysql> SHOW WARNINGS;
+-------+------+-------------------------------------------------------------------------------------+
| Level | Code | Message                                                                             |
+-------+------+-------------------------------------------------------------------------------------+
| Error | 3500 | CREATE TEMPORARY TABLE is not allowed with ROW_FORMAT=COMPRESSED or KEY_BLOCK_SIZE. |
| Error | 1031 | Table storage engine for 'tmp_t2' doesn't have this option                          |
+-------+------+-------------------------------------------------------------------------------------+

실제로 동일한 세션 안에서만 유효한지 확인해보자.

conn1> CREATE TEMPORARY TABLE tmp_t1 as SELECT * FROM t1;
Query OK, 2 rows affected (0.01 sec)
Records: 2  Duplicates: 0  Warnings: 0

conn1> SELECT COUNT(1) FROM tmp_t1;
+----------+
| COUNT(1) |
+----------+
|        2 |
+----------+
1 row in set (0.00 sec)

# conn2 세션에서만 테이블이 존재하지 않는다.
conn2> SELECT COUNT(1) FROM tmp_t1;
ERROR 1146 (42S02): Table 'd1.tmp_t1' doesn't exist

# 트랜잭션을 종료한 후 세션을 끊고 재차 테이블을 검색하면 존재하지 않는다.
conn1> COMMIT RELEASE;
Query OK, 0 rows affected (0.00 sec)

conn1> SELECT COUNT(1) FROM tmp_t1;
ERROR 2006 (HY000): MySQL server has gone away
No connection. Trying to reconnect...
Connection id:    93
Current database: d1

ERROR 1146 (42S02): Table 'd1.tmp_t1' doesn't exist

CREATE TEMPORARY TABLE과 리플리케이션
BINLOG_FOTMAT이 ROW 또는 MIXED인 경우는 CREATE TEMPORARY TABLE가 바이너리 로그에 출력되지 않는다. 즉, 마스터에서 작성한 일시 테이블이 슬레이브에서는 생성되지 않는다.
하지만, 일시 테이블을 이용해서 일반 테이블을 갱신하는 경우에는 갱신 데이터의 바이너리 로그가 출력되므로 복제가 이루어진다.

BINLOG_FOTMAT이 STATEMENT인 경우는 CREATE TEMPORARY TABLE도 바이너리 로그에 출력된다.
일시 테이블이 존재하는 슬레이브를 재기동하면 해당 테이블들이 삭제되므로 리플리케이션 에러가 발생할 수도 있다.
BINLOG_FOTMAT=STATEMENT에서 일시 테이블을 이용하고 있는 경우에는 STOP SLAVE 후 Slave_open_temp_tables=0 인지 확인한 후 재기동 할 필요가 있다.
mysql> SHOW STATUS like 'Slave_open_temp_tables';
+------------------------+-------+
| Variable_name          | Value |
+------------------------+-------+
| Slave_open_temp_tables | 0     |
+------------------------+-------+
1 row in set (0.01 sec)

일시 테이블을 이용할 때의 주의점
작성한 일시 테이블은 SHOW TABLES로는 확인할 수 없다. 세션 내에 일시 테이블이 있는 지 확인하고 싶은 경우는 SHOW CREATE TABLE 또는 INFORMATION_SCHEMA.INNODB_TEMP_TABLE_INFO로 status가 activity인 테이블을 확인할 수 있다.

mysql> SELECT * FROM INNODB_TEMP_TABLE_INFO;
+----------+---------------+--------+------------+
| TABLE_ID | NAME          | N_COLS | SPACE      |
+----------+---------------+--------+------------+
|     1577 | #sql3e04_1e_c |      6 | 4294501266 |
|     1576 | #sql3e04_1e_1 |      6 | 4294501266 |
+----------+---------------+--------+------------+
2 rows in set (0.04 sec)

동일한 일시 테이블은 쿼리 안에서 한번만 이용할 수 있다.
만약 두번 이상 이용하고 싶은 경우는 MySQL 8.0 신기능인 CTE(WITH문)를 이용할 필요가 있다. 

mysql> SELECT * FROM tmp1 JOIN tmp1 as _tmp2 on tmp1.id = _tmp2.id;
ERROR 1137 (HY000): Can't reopen table: 'tmp1'

# Refer
https://dev.mysql.com/doc/refman/5.7/en/create-table.html
https://dev.mysql.com/doc/refman/5.7/en/create-temporary-table.html

결혼이민비자 신청방법(F-6-1 국민의 배우자)

 제가 일본인 여자친구와 결혼 후, 한국에 귀국하기 위해 신청한 결혼이민비자에 대하여 작성해보도록 하겠습니다. 필자는 일본에서 근무하고 있었으며, 한국에서의 소득은 없었습니다. 결혼이민비자를 신청한 날짜는 2021-04-21 이며, 사증이 발급된 날짜...