mariaDB-Optimization and Tuning(Table)

OPTIMIZE TABLE

OPTIMIZE TABLE 구문은 두 개의 함수로 이루어져 있다. 각각 테이블의 조각화 현상을 해결하거나 전문인덱스를 업데이트하는데 사용된다.

Defragmenting

OPTIMIZE TABLE 명령은 InnoDB, Aria, MyISAM, ARCHIVE 테이블에서 동작 가능하며 많은 양의 레코드를 삭제하였거나 가변길이 row(VARCHAR나 VARBINARY, BLOB, Text 컬럼등)를 대량 변경하였을 경우 수행해야 한다. (MariaDB 10.1.1 이전에는 innodb_file_per_table 파라미터가 ON 된 경우에만 가능했었음) 삭제된 row는 링크된채로 리스트에 보관되며 insert 동작이 오면 해당 row위치를 재사용한다.

이 구문은 테이블에 대해 SELETE와 INSERT 권한이 필요하다.

기본적으로 OPTIMIZE TABLE 명령은 바이너리 로그에 남고 복제본에 전달되는데 NO_WRITE_TO_BINLOG 키워드를 (혹은 별칭으로 그냥 LOCAL만 쳐도 됨) 사용하면 바이너리 로그를 남기지 않게 된다.

OPTIMIZE TABLE은 또한 파티셔닝 된 테이블에도 지원된다. ALTER TABLE … OPTIMIZE PARTITION 명령을 사용하면 다수의 파티셔닝도 최적화할 수 있다.

Updating an InnoDB fulltext index

전문 인덱스에 row가 추가되거나 삭제되어도 전문인덱스의 재구성 작업은 비용이 크기 때문에 즉시 수행되지 않는다. 변경된 통계정보는 다른 위치에 저장된다. 전문 인덱스는 오직 OPTIMIZE TABLE 명령을 통해서만이 재구성된다.

기본적으로 OPTIMIZE TABLE 명령은 테이블의 조각화 현상을 해결한다. 만약 전문 인덱스의 통계정보를 업데이트하려면 innodb_optimize_fulltext_only 시스템 변수를 1로 셋팅해야 한다. 이것은 반드시 임시적으로만 쓰여야 하며 전문인덱스의 재구성이 완료되면 0으로 돌려놔야 한다. 전문인덱스의 재구성 작업은 시간이 꽤 오래 걸리기 때문에 innodb_ft_num_word_optimize 변수는 재구성하려는 단어의 값을 제한하고 있다. (기본적으로 2000) 전문인덱스 재구성 명령을 동시에 여러 개 수행해도 된다.

Defragmenting InnoDB Tablespaces

mariaDB 10.1.1 에서 페이스북과 카카오의 조각모음 방식 패치를 받아들여 InnoDB 테이블스페이스에 대해서도 조각모음이 가능하게 되었다. 이 기능을 사용하려면 innodb_defragment 변수를 활성화시켜야 한다.

InnoDB 테이블에서 row가 삭제되면 물리적으로 제거하는 대신 delete maker가 새겨놓는다. 그리고 삭제된 영역의 재사용을 위해 OS로 반환하지 않는다. purge 쓰레드가 인덱스의 키와 row를 지우기 위해 수행되는데, 이렇게 지워진 free 여역도 OS에 반환되지 않는다. 이것은 page에 빈 공간을 유발하게 된다. 만약 당신이 가변길이 row를 사용하고 있을 때 새로운 row의 길이가 방금 지운 row의 길이보다 크다면 지운 영역을 재사용할 수 없게 된다.

OPTIMIZE TABLE이나 ALTER TABLE … ENGINE=InnoDB 명령은 테이블을 재구성하도록 해준다. 그런데 불행히도 공통 테이블스페이스 파일인 ibdata1에 대해 최적화를 수행하면 아래와 같은 현상이 일어난다.

  • 테이블의 데이터와 인덱스를 연속적으로 만든다.
  • 연속적인 데이터와 인덱스 페이지가 ibdata1에 이어써지기 때문에 ibdata1의 크기가 증가한다. (?)

MariaDB 10.1 에서는 페이스북의 Matt, Kakao의 이성욱에 의해 제공된 조각모음 패치를 포함하였다. 페이스북의 코드와 Matt코드의 차이점은 MariaDB는 새로운 literal을 SQL에 포함하지 않았고 서버 코드를 변경하지 않았다는 것이다. 대신 InnoDB/XtraDB 스토리지 엔진 내부에만 코 변경을 하였다.

OPTIMIZE TABLE의 동작 원리는 기본적으로 변하지 않았다. 새로 추가된 이 기능을 사용하려면 innodb_defragment 시스템 변수를 1로 설정해야 한다.

새로운 테이블을 만들어서 데이터를 이전 테이블에서 새로운 테이블로 옮기는 작업이 필요없어졌다. 대신 n개의 새로운 page를 만들어서 record를 새로운 page로 옮기게 된다. 새로운 page가 꽉 차면 기존 record가 있던 page는 빈 영역이 된다. 이와 관련하여 새로 소개된 시스템 변수는 아래와 같다. (이 변수들도 10.1.1부터 추가됨)

System variables

  • Innodb_defragment: InnoDB의 조각모음을 활성화한다.
  • innodb_defragment_n_pages: 조각모음시 한 타임에 몇 개의 page를 가지고 작업할 것인지를 결정하는 파라미터이다. 기본 값: 7
  • innodb_defragment_stats_accuracy:  조각모음 통계의 개수… (?)
  • innodb_defragment_fill_factor_n_recs: 조각모음 수행시 추후 사용을 위해 남겨놓을 페이지 여유분으로 record 건수를 기준으로 함. 기본 값: 20
  • innodb_defragment_fill_factor: 조각모음 수행시 페이지를 얼마나 채울 지 비율을 결정하는 변수. 기본 값: 0.9
  • innodb_defragment_frequency: 단일 인덱스에 대해 조각모음을 수행할 수 있는 초당 횟수의 최대값. 기본 값: 40

Status variables

  • innodb_defragment_compression_failures: 조각모음 재압축의 실패 횟수
  • innodb_defragment_failures: 조각모음 실패 횟수
  • innodb_defragment_count: 조각모음 수행 횟수

 

ANALYZE TABLE

ANALYZE TABLE은 테이블의 키 분포에 대해 분석하고 저장하는 구문이다. MyISAM, Aria, InnoDB 테이블에서 모두 수행 가능한데, MyISA 엔진에서 수행하면 테이블에 read 락이 걸리며 InnoDB 엔진에서 수행하면 write 락이 걸린다. MyISAM 테이블 같은 경우 myisamchk –analyze 명령을 수행한 것과 동일한 효과를 보인다.

Join 수행시 MariaDB는 키의 분포도를 보고 테이블의 조인 순서를 결정한다. 그리고 키 분포를 보고 특정 테이블에서 어떤 인덱스를 사용할 것인지를 결정하기도 한다. ANALYZE 문을 수행하기 위해선 SELECT와 INSERT 권한이 필요하다. 기본적으로 ANALYZE문은 바이너리 로그에 남고 replication에 전달되기 때문에 만약 바이너리 로그에 남기지 않으려면 NO_WRITE_TO_BINLOG 키워드를 추가하면 된다.

※ MariaDB 10.0.1에서 ANALYZE시 스토리지 엔진과 무관한 통합 통계정보를 수집하도록 되었으며 현재 이러한 통계정보는 풀 테이블 스캔이나 풀 인덱스 스캔시 수집된다. 물론 비용은 꽤 크다.

MariaDB 10.0.1부터 ANALYZE TABLE 명령은 PERSISTENT FOR 키워드를 이용하여 특정 컬럼이나 인덱스를 선택할 수 있도록 되었다. 아래는 사용법에 대한 예제이다. 그리고 BLOB이나 텍스트 컬럼은 통계정보 수집되지 않는다.

ANALYZE TABLE tbl PERSISTENT FOR ALL;
ANALYZE TABLE tbl PERSISTENT FOR COLUMNS (col1,col2,...) INDEXES (idx1,idx2,...);
ANALYZE TABLE tbl PERSISTENT FOR COLUMNS () INDEXES (idx1,idx2,...);
ANALYZE TABLE tbl PERSISTENT FOR COLUMNS () INDEXES ();
--> 컬럼과 인덱스에 대한 통계는 빼고 테이블의 통계 정보만 수집함.
use_stats_tables 변수가 never로 설정되어 있지 않은 한 단순 ANALYZE TABLE 명령은
통합 통계 테이블에 저장되게 된다.
SET SESSION use_stat_tables='COMPLEMENTARY';
ANALYZE TABLE tbl;

테이블 옵션에 STATS_PERSISTENT=1이 설정되어 있다 하더라도 통합 통계정보를 업데이트하도록 하려면 PERSISTENT FOR 키워드를 써야 한다.

 

Converting Tables from MyISAM to InnoDB

MyISAM 을 InnoDB로 옮기는 것은 ALTER TABLE … ENGINE=innoDB 명령으로 간단히 바꿀수는 있다. 그러나 스토리지 엔진을 변경하는 것은 미처 알아채기 힘든 여러 이슈들을 포함하고 있다.

인덱스가 어떻게 작동하는지 이해하는 것은 InnoDB에서 무엇이 더 빠르고 무엇이 더 느릴지 이해하는데에 도움을 줄 것이다.

INDEX Issues

모든 InnoDB 테이블은 PK를 갖는다. 만약 별도로 지정하지 않았다면, Not null & Unique 속성을 갖는 컬럼이 PK가 될 것이며, 그것도 없으면 6바이트의 숨겨진 컬럼이 내부적으로 생성된다.
별도로 PK컬럼을 지정하지 않은 테이블을 생각해보자. 비록 인위적인 AUTO_INCREMENT 속성이 포함된다 하더라도 PK를 명시적으로 지정하자. 이것은 강제적인 사항은 아니지만 MyISAM이 아닌 InnoDB를 사용할  강력한 권고사항이다. 아래는 PK에 대해 몇 가지 사실들을 이야기하고 있다.

Fact: 모든 세컨더리 인덱스의 키는 PK를 포함하고 있다.

PRIMARY KEY(id),
INDEX(b), -- effectively the same as INDEX(b, id)
INDEX(b, id) -- effectively the same as INDEX(b)

Index(b)를 만드나 Index(b, id)를 만드나 동일하기 때문에 하나는 삭제해도 좋다.

PRIMARY KEY(id),
UNIQUE(b), -- keep for uniqueness constraint
INDEX(b, id) -- DROP this one

이런 경우도 동일한 인덱스이지만 UNIQUE(b)는 b가 Unique하다는 것을 알려주기 때문에 좀 더 많은 정보를 담고 있다. 따라서 INDEX(b,id)를 삭제하는 것이 좋다.

PRIMARY KEY(id),
INDEX(id, b) -- DROP this one; it adds almost nothing

이 경우에도 인덱스는 삭제하는 것이 좋다.

Contrast: MyISAM의 특성이 InnoDB에서는 발현되지 않는 케이스도 있다. MyISAM에서 아래 케이스의 경우 ‘abc’의 값마다 별도의 ‘id’의 값을 가지며 1씩 증가하게 된다.

id INT UNSIGNED NOT NULL AUTO_INCREMENT,
PRIMARY KEY (abc, id)

이런 MyISAM의 특성을 흉내내기 위해 아마 당신은 아래와 같이 코딩할 것이다.

INSERT INTO foo
    (other, id, ...)
    VALUES
    (123, (SELECT MAX(id)+1 FROM foo WHERE other = 123), ...);

그러나 테이블을 두 번 언급하였기 때문에 아래와 같이 에러를 반환하게 된다.

ERROR 1093 (HY000): Table ‘foo’ is specified twice, both as a target for ‘INSERT’ and as a separate source for data

따라서 변수를 이용하여 아래와 같이 만들어야 할 것이다.

BEGIN;
SELECT @id := MAX(id)+1 FROM foo WHERE other = 123;
INSERT INTO foo
    (other, id, ...)
    VALUES
    (123, @id, ...);
COMMIT;

다른 쓰레드에서 중복된 id값을 가져가는 것을 방지하기 위해 반드시 트랜잭션을 형성해야 한다.

 

 

mariaDB-Optimization and Tuning(OS)

1. Configuring Swappiness

Why to avoid swapping

  • mariaDB는 메모리가 스왑되지 않았다고 가정한다. 만약 swap이 발생한 상태라면 단순히 최초 access로 disk에서 로딩하는 것보다 성능에 더 좋지 않은 영향을 미친다.

 

스와핑을 피할 수 있는 가장 좋은 방법은 충분한 RAM을 확보하는 것이다. 시스템 변수를 너무 높게 잡으면 서버의 메모리가 부족해지면서 스왑을 일으킬 수도 있게 된다. 따라서 어떤 셋팅 값을 사용할 것인지, 이것이 메모리에 어떤 영향을 미칠지 이해하는 것이 매우 중요하다.

Setting swappiness on Linux

리눅스는 swappiness 라는 셋팅값을 가지고 있는데 이것은 RAM에서 하드디스크에 존재하는 스왑영역으로 스왑하는 정도를 의미한다. 0부터 100까지 설정이 가능하며 낮은 값은 잘 스왑하지 않는 다는 의미이다. 기본 값은 60이며 아래 커맨드로 확인해 볼 수 있다.

sysctl vm.swappiness

이 기본값은 서버로 하여금 스왑하도록 권장하고 있다고 볼 수 있다. 왠만해선 MariaDB에서 스왑하는 상황을 원치 않을 것이므로 이 값을 0으로 하는 것이 좋다. sysctl.conf 파일을  (대부분 /etc/sysctl.conf에 존재) 아래와 같이 swappniess를 0으로 만드는 설정을 추가하면 된다.

vm.swappiness = 0

/etc/sysctl.conf 파일에 적용하는 것은 리부팅 이후에도 지속된다.

데이터베이스 워크로드에는 낮은 수준의 swappiness를 추천한다. mariaDB 데이터베이스에 대해서 이 값을 1로 설정하는 것을 추천한다.

vm.swappiness = 1

 

Disabling swap altogether

스와핑을 완전히 비활성화 함으로써 데이터베이스 프로세스가 스와핑되는 것을 원천봉쇄하고자 하는 유혹이 있을 것이다. 그러나 조금의 스왑 기능을 남겨놓아야 실행중인 프로세스가 kill 되는 상황을 방지할 수 있다.

 

2. Filesystem Optimizations

Which filesystem is best?

사실 MariaDB 성능에 있어서 파일시스템은 중요한 포인트가 아니다. RAM이나 Disk drive의 속도, 시스템 변수의 셋팅이 더 중요하다고 볼 수 있다.

그러나 파일시스템의 최적화에 따라 큰 차이를 보일 수도 있다. 현재 리눅스 파일시스템 중 가장 훌륭한 성능을 보이는 것은 ext4와 XFS, Btrfs 이다. 이들은 모두 리눅스 커널에 포함돼있고 대부분의 리눅스 배포판에서 사용 가능하다. (Redhat제품에서 Brtfs는 미리 보기만 가능하고 아직 운영환경에 사용할 수 있는 준비는 되지 않음)

 

 

 

 

MariadB-Backup and Restore Overview

Logical vs Physical backups

로지컬 백업은 복구에 필요한 SQL문을 이용하는 것이고 피지컬 백업은 데이터파일이나 디렉터리를 통째로 복사하는 것을 의미한다. 이 둘은 아래와 같은 차이점이 있다.

  • 로지컬 백업이 좀 더 유연성이 있다. 다른 하드웨어에서도 복구할 수 있고 다른 mariaDB버전, 심지어 다른 DBMS에서도 복구할 수도 있다.
  • 로지컬 백업은 데이터베이스와 테이블 단위에서 실행되며 피지컬 백업은 디렉터리와 파일 단위에서 실행된다.
  • 로지컬 백업의 사이즈가 피지컬 백업보다 더 크다.
  • 로지컬 백업이 피지컬 백업보다 백업/복구 시간이 더 오래 걸린다.
  • 로그 파일과 구성파일은 로지컬 백업의 대상이 아니다.

Backup tools

mysqldump

mysqldump는 로지컬 백업을 수행하는 도구이며 백업/복구 하는데 있어 가장 유연성 있다. 데이터 사이즈가 상대적으로 작으면 가장 좋은 선택이라 할 수 있다.

더 큰 데이터 사이즈에서는 백업 파일이 커질 수 있으며 복구시간도 더 길어지게 된다. mysqldump는 SQL 형식(CSV나 XML형식으로도 가능) 으로 data를 덤프하기 때문에 다른 데이터베이스에 쉽게 임포트할 수 있다. 따라서 특정 DBMS에 맞춘 SQL문법만 아니면 MySQL이나 다른 DBMS로도 임포트할 수 있다.

mysqldump로 덤프를 뜨면 트리거는 자동으로 받아지지만 스토어드 프로시저나 뷰, 이벤트들은 명시적으로 옵션을 써야한다. (–routins, –events). 프로시저와 펑션은 시스템 테이블의 일부에 속한다. (예: mysql.proc)

InnoDB logical backups

로지컬 백업을 수행하면 테이블 Full scan을 유발하게 된다. 이것은 버퍼풀의 효율성을 떨어뜨릴 수도 있는데 이 문제를 회피할 수 있는 방법 중 한가지는 innodb_old_blocks_time 변수 값을 늘리는 것이다. 이 파라미터는 새로운 페이지가 버퍼 풀에 로딩되었을 때 LRU 알고리즘에서 recent측으로 옮겨가기 전 대기해야 하는 시간을 의미한다. 단위는 밀리 세컨드이며 기본값은 1000이다. 만약 0이면 재사용되는 즉시 recent 측으로 옮겨가게 된다. 따라서 이 시간을 늘리면 그만큼 recent 측으로 옮겨갈 가능성이 줄어드므로 금방 flush 될 수 있다.

또 다른 방법 하나는 innodb_old_block_pct 변수 값을 더 작게 하는 것이다. 그럼 LRU에 insert 되는 위치가 좀 더 old 영역에 가깝게 되기 때문에 금방 flush될 수 있다.

※ 로지컬 백업을 하면 버퍼 풀의 페이지가 많이 바뀔 수 있으므로 작업 전 버퍼 풀의 내용을 dump 떠넣는 것도 좋은 방법이다. innodb_buffer_pool_dump_now 변수를 ON하는 즉시 버퍼 풀을 dump뜨며 innodb_buffer_pool_load_now 변수를 ON하는 즉시 버퍼 풀을 복구한다.

Examples

백업하기

shell> mysqldump db_name > backup-file.sql

복구하기

shell> mysql db_name < backup-file.sql

 

mysqlhotcopy

mysqlhotcopy는 물리적으로 복제를 하는 방식이며 MyISAM과 ARCHIVE 테이블에 대해서만 수행할 수 있으며 원격서버로는 불가하기 때문에 스크립트를 수행하는 서버에 존재하는 데이터베이스에 대해서만 백업할 수 있다.
작동 방식은 FLUSH TABLES 를 하고 LOCK을 건 후 cp 명령으로 데이터 파일을 복사하는 것으로 매우 간단하지만 번거로운 수작업을 덜어주는 정도라 보면 되겠다.

Examples

shell> mysqlhotcopy db_name_1 ... db_name_n /path/to/new_directory

 

XtraBackup

빠르게 핫백업할 수 있는 오픈소스 툴이며 XtraDB와 InnoDB 데이터베이스를 위해 특별히 제작되었다. 그러나 다른 스토리지 엔진에도 사용할수는 있으며 mariaDB에는 기본적으로 포함돼있지 않다.

Filesystem snapshots

Veritas 같은 파일시스템은 스냅샷 기능을 제공하고 있으며 이 때 테이블은 반드시 lock 되어 있어야 한다. 순서는 아래와 같다.

  • FLUSH TABLES WITH READ LOCK 명령으로 버퍼 풀을 플러쉬시키고 락을 건다.
  • mount vxfs snapshot  명령으로 스냅샷을 시작한다.
  • 이제 UNLOCK TABLES 으로 락을 풀어줄 수 있으며 필요하다면 수행한다.
  • 스냅샷 파일을 카피한다.
  • umount snapshot 명령으로 스냅샷을 umount한다.

LVM

생략

Percona TokuBackup

생략

MariaDB-바이너리로그

바이너리로그를 활성화시키려면 mariaDB 시작시 –log-bin 옵션에 로그파일이름을 지정해주면 된다. 확장자를 포함하여 파일이름을 지정할 수 있지만 확장자는 그냥 무시된다. 만약 이름을 명시하지 않으면 기본 위치는 datadir/log-basename-bin 으로 되거나 data/mysql-bin으로 되거나 datadir/mariadb-bin 으로 된다. (앞의 두 개는 basename이 지정되지 않은 경우이며 mariadb의 버전에 따라 다르다). –log-basename 옵션을 통해 경로를 지정하거나 파일이름 지정시 전체 경로를 명시하는것을 추천한다. 그래야 호스트명이 바뀌거나 서버에 어떤 변경이 일어났을 때 replication(복제)가 중단되는 것을 방지할 수 있다.

바이너르 로그를 보관하는 디렉토리는 해당 로그 파일뿐만 아니라 로그 인덱스도 보유한다. 파일이름의 확장자로 연속적인 숫자가 오게 되는데, 바이너리 로그가 추가되면 숫자도 증가한다. mariaDB가 재기동되면 새로운 바이너리로그가 생성된다. 또는 최대사이즈에 도달했을 때도 새로운 바이너리 로그가 생성된다. (max_binlog_size에 의해 결정, 기본 1G) 인덱스 파일은 바이너리 로그에 대한 리스트를 가지고 있는 마스터 파일라고 할 수 있다. 바이너리 로그 디렉토리를 보면 아래와 같이 존재하는 것을 볼 수 있다.

shell> ls -l 
total 100
...
-rw-rw---- 1 mysql adm 2098 Apr 19 00:46 mariadb-bin.000079
-rw-rw---- 1 mysql adm  332 Apr 19 00:56 mariadb-bin.000080
-rw-rw---- 1 mysql adm  347 Apr 19 07:36 mariadb-bin.000081
-rw-rw---- 1 mysql adm  306 Apr 20 07:15 mariadb-bin.000082
-rw-rw---- 1 mysql adm  332 Apr 20 07:41 mariadb-bin.000083
-rw-rw---- 1 mysql adm  373 Apr 21 07:56 mariadb-bin.000084
-rw-rw---- 1 mysql adm  347 Apr 21 09:09 mariadb-bin.000085
-rw-rw---- 1 mysql adm  398 Apr 21 21:24 mariadb-bin.000086
-rw-rw---- 1 mysql adm  816 Apr 21 17:05 mariadb-bin.index

 

인덱스파일은 log-bin-index 파라미터를 이용해 이름을 명시할 수 있다. 바이너리 로그의 포맷은 statement기반, row기반, 두가지를 합친 것으로 총 3가지의 형식이 가능하다.

 

Using and Maintaining the Binary Log

Purging log files

모든 로그파일을 삭제하려면 RESET MASTER 명령을 사용해라. 특정 시간 이전이나 특정 넘버의 로그만 지우려면 PURGE BINARY LOGS 명령을 이용해라.

※ 슬레이브가 활성화되어 있고 삭제하려는 바이너르 로그를 읽고있다면 삭제할 수 없다. 그러나 슬레이브가 비활성화되어 있다면 삭제할 수 있다. 또한 삭제한 이후부터 슬레이브와 다시 연결하기 전까지는 replication이 이뤄지지 않는다.

또 expire_logs_days 파라미터에 의해 정해진 기간에 도달하면 삭제되기도 한다. 기본 값은 0이지만 day 단위로 값을 지정할 수 있으며 적어도 슬레이브 lag 시간 보다는 길게 설정해야 한다.

※ 인덱스 파일이 삭제되거나 인위적으로 잘못 수정되면, 위에 언급한 purge 작업은 실패하게 된다. 인덱스 파일은 텍스트 파일이기 때문에 직접 수정이 가능하며 현재 존재하는 로그파일에 대해 시간순으로 리스트를 담고있다.

Examples

PURGE BINARY LOGS TO 'mariadb-bin.000063';
PURGE BINARY LOGS BEFORE '2013-04-22 09:55:22';

 

Safely purging binary log files while replicating

바이너리 로그를 삭제하는 도중 replication이 깨지지 않도록 하려면 아래 절차를 잘 지켜라.

  1. SHOW BINARY LOGS 명령을 통해 현존하는 로그 파일의 리스트를 확인해라.(마스터에서)
  2. 각 슬레이브에서 SHOW SLAVE STATUS 명령을 통해 각각의 슬레이브가 현재 읽고 있는 로그 파일이 어떤 것인지 확인해라.
  3. 슬레이브들이 읽고 있는 것 중 가장 오래된 파일을 확인해라. 이 파일 이전의 로그파일은 필요없다.
  4. 필요하다면 로그 파일을 백업해둬라.
  5. 그리고 가장 오래된 파일로 확인했던 로그 파일 이전의 것들을 삭제해라.

 

Selectively logging to the binary log

기본적으로 모든 데이터, 데이터구조를 변화시키는 쿼리가 기록된다. –binlog-ignore-db=database_name 옵션이나 –binlog-do-db=database_name 옵션을 통해 로그를 남기지 않을 데이터베이스나 남길 데이터베이스를 선택할 수 있다.

※ 여러 DB를 명시할 때는 콤마를 쓰면 안된다(DB명에 콤마가 있을 수도 있기 때문). 대신 이 옵션을 여러번 반복해서 쓰면 된다.

–binlog-ignore-db 옵션이나 –binlog-d-db 옵션은 로그파일 포맷이 statement기반이냐, row기반이냐에 따라 조금씩 다르다. 로그파일 형식이 statement 기반일 경우엔 USE명령으로 사용하고 있는 기본 데이터베이스가 해당 옵션에 있는 경우에만 적용된다고 보면 된다. 가령 DB가 A,B,C,D 가 있고 파일포맷이 statement이며, ignore  (또는 do) 대상이 A,B 인 상황에서 내가 C 데이터베이스를 기본으로 사용하고 있다면 내가 입력하는 작업내용은 기록되지 않는 (또는 기록되는)것이다. 로그파일 형식이 row기반일 경우엔 기본 데이터베이스와 상관없이 적용된다.

 

Binary Log Formats

파일형식은 statement기반, row기반, 이 둘을 합친 것해서 총 3가지가 지원된다. statement기반이라고 해서 텍스트형식으로 저장되는 것이 아니라 기본적으로 항상 바이너리 형식으로 저장된다. 따라서 mysqlbinlog 라고 하는 CLI기반의 명령어를 통해 텍스트로 변환하여 볼 수 있다. 파일 형식은 binlog_format 시스템 변수를 통해 정할 수 있다.

Statement-based

파일 형식의 기본 설정이며 –binlog-format=STATEMENT 로 지정할 수 있다. 데이터나 테이블 구조에 변경을 가하는 모든 SQL구문을 기록한다. 어떤 경우엔 SQL구문이 확정적(Deterministic)이지 않은 경우가 존재하며 이 경우 replication이 안전하지 않게 된다. 이 경우 log_warning 시스템 변수에 정의된 곳에 경고문이 뜨게 된다.

Row-based

테이블의 개별적인 row가 영향받는 event를 기록한다. –binlog-format=ROW 로 지정한다.

Mixed

일단 statement-based가 기본적으로 사용되지만 replication에 안전하지 않은 statement라고 mariaDB가 판단하는 경우 row-based 방식으로 기록하는 방식이다. statement방식 대신 row방식으로 기록되는 경우는 아래와 같다.

  • INSERT-DELAYED 구문이 사용된 경우
  • AUTO_INCREMENT 컬럼을 가진 테이블이 업데이트, 트리거되거나 스토어드 함수가 사용된 경우
  • LOAD_FILE() 함수가 사용된 경우
  • ROW_COUNT() 함수나 FOUND_ROWS() 함수가 사용된 경우
  • USER()나 CURRENT_USER()함수가 사용된 경우
  • UUID() 함수가 사용된 경우
  • 구문에 포함된 테이블 중 하나가 mysql database의 로그 테이블일 경우
  • 구문이 시스템 변수를 참조하는 경우(세션단위에서 사용하는 것은 예외)
  • 유저정의 함수가 사용되는 경우

참고로 마스터와 슬레이브간 서로 다른 스토리지 엔진을 쓰는 것보다 같은 엔진을 사용할 때 에러가 줄어든다.

Changing the binary log format

로그포맷의 기본값은 statement기반이며 mariaDB 기동 중에 변경할 수 있다.

※ replication을 사용중에는 파일 포맷 변경하는 것을 조심해야 한다. 바이너리 로그 형식은 서버 자신에 의해서만 변경될 수 있다. 따라서 마스터에서 로그 포맷을 바꾸더라도 슬레이브의 로그 포맷에는 영향을 미치지 않으며 로그의 불일치를 가져올 수도 있다. 또한 Global 파라미터로 변경하더라도 현재 기동 중인 쓰레드를 보유한 세션에는 영향을 주지 않는다. 그리고 parallel 복제를 사용하는 환경에서 slave가 STOP SLAVE 명령 및 재기동되어도 slave_parallel_threads 값이 변경되기 전까지는 워커 쓰레드가 계속 기동하게 된다. 따라서 이때에는 slave_parallel_threads=0 으로 값을 변경해야 워커 쓰레드가 즉시 종료된다.

MariaDB 10.0.22 부터는 로그 형식과는 상관없이 마스터로에서 발생하는 어떤 이벤트도 적용할 수 있게 되었다.

비록 로그 포맷을 바꾸는 일은 거의 없겠지만 아래의 케이스는 알아두면 좋겠다.

  • 단일 구문 또는 적은 구문을 통한 update가 많은 수의 row를 업데이트할 때는 statement로깅기법이 더 효과적이다.
  • 많은 SQL문이 결과적으로 미미한 양의 row를 변경하는 경우 row로깅기법이 더 효과적이다.
  • 매우 오래 수행되지만 결과적으로 매우 적은 row가 변경되는 경우에도 row로깅기법이 더 효과적이다.

 

Binary Logging of Stored Routines

statement기반의 로깅을 사용하는 경우 마스터와 슬레이브간 서로 다른 결과를 만들어내는 SQL구문이 존재할 수도 있다. 스토어드 루틴같은 경우는 2가지 이유 때문에 이러한 경향이 더욱 짙다.

  1. 스토어드 루틴은 확정적(Deterministic)이지 않다. 다른 말로 repeatable하지 않다는 의미로 실행될 때마다 다른 결과 값을 만들어낸다.
  2. 슬레이브에서 스토어드 루틴을 실행하는 쓰레드는 전체 권한을 가지고 있는 반면, 마스터에서는 그렇지 않은 경우가 있다.

리플리케이션에서 발생하는 문제는 오직 statement기반의 로깅에서만 일어난다. 만약 row기반의 로깅이 사용되면, 마스터의 row를 기반으로 변경사항이 전달되기 때문에 sync가 어긋날 가능성이 완전히 사라지게 된다.

기본적으로 row기반의 리플리케이션을 사용하면, 마스터에서 트리거를 수행시켜 슬레이브로 해당 내용이 전달되도록 한다. 그러나 MariaDB 10.1.1부터는 슬레이브에서도 트리거를 수행시킬 수 있게 되었다.

How MariaDB handles statement-based binary logging of routines

이번 장에서 설명하는 내용은 statement기반의 로깅이 활성화된 케이스에만 해당한다.

  • 바이너리 로그가 활성화된 상태에서 스토어드 펑션이 만드려면 반드시 DETERMINISTIC이나 NO SQL 또는 READS SQL DATA 를 선언해주어야 한다. 안그러면 에러가 발생한다.
  • mariaDB는 펑션이 확정적(Deterministic)인지 판단하지 않으며 대신 올바른 정의가 사용되었는지를 본다.(?)
  • 스토어드 펑션을 만드려면 정규권한 외에도 SUPER 권한이 필요하다.
  • log_bin_trust_function_creators=1로 설정되면 위에서 언급한 조건은 필요없다.
  • 트리거…

 

PURGE logs

PURGE { BINARY | MASTER } LOGS { TO ‘log_name’ | BEFORE datetime }

명시한 로그파일명 혹은 날짜값 이전에 있는 로그파일을 삭제한다. 인덱스 파일내 목록에서도 삭제되므로 명시했던 파일명이 첫 번째로 존재하는 로그파일이 된다.

삭제하기 전 SHOW BINARY LOGS 명령을 통해 로그 리스트를 확인하고, 어떤 로그파일이 읽히고 있는지를 확인해보려면 SHOW SLAVE STATUS 명령을 이용하면 된다. 슬레이브가 읽고 있는 파일 중 가장 old한 것보다 더 이전에 생긴 로그파일부터 지울 수 있다.

전체 로그파일을 다 삭제하려면 RESET MASTER 명령을 날리면 된다. (여태까지의 로그파일이 모두 삭제되고 새로운 로그파일부터 시작함)

Examples

PURGE BINARY LOGS TO 'mariadb-bin.000063';
PURGE BINARY LOGS BEFORE '2013-04-21';
PURGE BINARY LOGS BEFORE '2013-04-22 09:55:22';

 

 

 

 

 

 

 

14.4 InnoDB Configuration

14.4.1 InnoDB Initialization and Startup Configuration

InnoDB의 구성할 때 가장 처음 결정했던 것은 데이터파일을 어떻게 표현할 것인가와 InnoDB 스토리지 엔진을 위한 메모리를 어떻게 할당할 거인가였다. 당신은 configuration 파일에 값을 넣어 MySQL이 시작하면서 읽도록 할 수 있으며 CLI 옵션으로도 지정할 수 있다.

Overview of InnoDB Tablespace and Log Files

InnoDB 스토리지 엔진에 의해 관리되는 것 중 가장 중요한 디스크 기반의 리소스를 꼽으라면 테이블스페이스 데이터 파일과 로그 파일이다. configuration 파일에서 따로 명시하지 않았다면 MySQL은 자동으로 증가하고 12MB보다 약간 큰 데이터 파일인 ibdata1과 로그파일 2개(ib_logfile0, ib_logfile1)을 데이터 디렉토리에 생성한다. 이 로그 사이즈의 크기는 innodb_log_file_size 파리미터에 의해 결정된다. 좋은 성능을 위해 파라미터의 값을 명시적으로 조절할 필요가 있다. 물론 당신의 하드웨어와 요구사항에 맞게 조절해야 한다.

Considerations for Storage Devices

어떤 케이스에서는 데이터파일이 서로 다른 물리적인 디스크에 위치해야 더 좋은 성능이 나온다. 로그 파일을 데이터 파일과 다른 디스크에 구성하는 것은 매우 자주 쓰이며 효과적인 방법이다. 다음 예제는 어떻게 하는지를 보여준다. 두 개의 데이터 파일을 서로 다른 디스크에 위치시키며 로그 파일 또한 세 번째 다른 디스크에 위치시킨다. InnoDB는 테이블스페이스를 채울 때 데이터파일의 첫 번째 데이터 파일을 사용한다. 그리고 I/O를 향상시키 위해 raw device를 InnoDB의 데이터 파일로 사용할 수도 있다.

Caution
InnoDB는 트랜잭션이 보장되는 엔진이다. 그러나 OS나 하드웨어가 그러한 요구사항을 받아주지 못한다면 무용지물이 된다. 많은 OS와 disk 시스템이 성능을 위해 write 동작을 delay시키거나 reorder 시키고 있다. 어떤 OS에서는 fsync() 시스템 콜이 모든 데이터가플러쉬 될 때까지 기다려야함에도 완료되기 전에 return하는 경우가 있다. 이 때문에 OS crash나 정전등으로 인해 최근 commit 된 데이터가 유실되는 경우나 write 동작이 reorder됨으로써 데이터베이스가 깨지는 경우도 있다. 만약 데이터의 정합성이 중요하다면 프로덕션에 적용하기 전 pull-the-plug 테스트를 해봐라. OS X 10.3 보다 높은 경우 InnoDB는 플러쉬를 위해 특별한 fcntl() 라는 함수를 사용한다. Linux에서는 write-back cache를 비활성화 하는 것을 고려해볼만 하다 (CPU 캐시에만 update하고 메모리에는 update하지 않아 데이터 일관성이 맞지 않을 수 있다)

ATA/SATA 디스크에서는 write-back cache를 비활성화하는 게 불가능할 수도 있다. 어떤 drive나 디스크 컨트롤러에서도 write-back cache를 비활성화하는게 불가능한 경우가 있으니 유의해라.

유저의 데이터를 보호함에 관련하여 복구 능력을 향상시켜주는 것이 존재하는데 InnoDB는 doublewrite buffer라고 불리는 파일 플러쉬 기술이 존재한다 (여러 개의 dirty page를 각각의 데이터파일에 플러쉬하려면 랜덤I/O가 발생하여 시간이 오래 걸리므로 해당 dirty page를 시스템 테이블스페이스 내 하나의 연속적인 extent에 때려넣음으로써 단일 I/O로 저장하는 방법, 이렇게 저장이 되면 정상적인 플러쉬 과정을 한다.) innodb_doublewrite파라미터는 기본적으로 ON인데 서버 crash나 정전 등으로 인한 사고에서 더 안전하게 복구할 수 있도록 해준다. 또한 대부분의 unix 장비에서 fsync() 시스템 콜의 요청을 줄여줌으로써 성능도 향상시키게 된다. 따라서 innodb_doublewrite 옵션을 활성화된 상태로 놔둘 것을 권고하는 바이다.

Specifying the Location and Size for InnoDB Tablespace Files

InnoDB의 테이블스페이스 파일을 셋업하려면 [mysqld] 세션에서 innodb_data_file_path 파라미터를 수정해라. 이 파라미터에는 하나 이상의 데이터 파일에 대한 리스트가 있어야 하며 세미콜론(;)으로 구분해주면 된다.

innodb_data_file_path=datafile_spec1[;datafile_spec2]...

예를 들어 아래와 같이 작성하면 된다.

[mysqld]
innodb_data_file_path=ibdata1:12M:autoextend

이것은 자동 증가하는 ibdata1 라는 하나의 12MB짜리 파일을 명시하는 것이다.위치에 대한 내용이 없으므로 자동으로 데이터 디렉토리에 생서하게 된다. 자동 증가하는 50M짜리의 두 개의 데이터 파일을 명시하려면 아래와 같이 쓰면 된다.

[mysqld]
innodb_data_file_path=ibdata1:50M;ibdata2:50M:autoextend

만약 자동증가 옵션을 명시했다면 InnoDB는 테이블스페이스가 꽉 차면 자동으로 증가시키며 한번에 8MB씩 증가시킨다.  (MariaDB에서는 64로 정의되어 있음) 만약 이 값을 수정하려면 innodb_autoextend_increment 파라미터를 수정하면 된다.

InnoDB는 파일시스템 내 최대 파일 사이즈를 알지 못한다. 따라서 파일시스템 내 최대 파일의 사이즈가 적당히 작은지 잘 모니터링 해야 한다. 자동 증가하는 데이터 파일의 최대 사이즈를 지정하기 위해서는 autoextend 속성 다음에 max 속성으로 지정할 수 있다. max 속성은 데이터 사용량이 매우 중요한 이유가 있는 경우에만 사용해라. 아래 예제는 ibdata1이 최대 500MB까지만 커질 수 있도록 제한을 두는 예제이다.

[mysqld]
innodb_data_file_path=ibdata1:12M:autoextend:max:500M

그리고 위치를 지정하려면 innodb_data_home_dir 파라미터를 작성해라. 아래 예제는 /ibdata 아래에 데이터파일을 생성하는 예제이다.

[mysqld]
innodb_data_home_dir = /ibdata
innodb_data_file_path=ibdata1:50M;ibdata2:50M:autoextend

Note
InnoDB는 디렉토리를 만들지 않기 때문에 /ibdata 디렉토리가 존재하는지 미리 확인해라. 이것은 로그 파일을 지정할 때도 마찬가지이다. 그리고 mysql이 해당 디렉토리에 적당한 권한이 있는지 잘 확인해라.

innodb_data_home_dir 파라미터를 빈 칸으로 놔두고 innodb_data_file_path 파라미터에서 데이터 파일을 절대 경로로 표시할 수 있다.

[mysqld]
innodb_data_home_dir =
innodb_data_file_path=/ibdata/ibdata1:50M;/ibdata/ibdata2:50M:autoextend

어떤 파일시스템에서는 데이터 파일의 크기가 2G보다 작아야 한다는 것을 명심해라.

 

14.4.3.1 Configuring InnoDB Buffer Pool Prefetching (Read-Ahead)

read-ahead 요청이란 페이지들이 곧 사용될 것이란 예측을 하고 비동기적으로 여러 개의 페이지를 버퍼 풀로 미리 올려놓는 것을 의미한다. 즉 하나의 익스텐트에 있는 모든 페이지를 한꺼번에 올려놓는 것이다. InnoDB는 I/O 성능을 향상시키기 위해 두 개의 read-ahead 알고리즘을 갖고 있다.

  • Linear: 버퍼 풀에서 순차적으로 접근하는 페이지를 보고 어떤 페이지가 곧 사용될 것인지를 판단하는 기법이다. 언제 read-ahead 가 작동될 것인지는 얼마나 많은 연속적인 페이지가 접근되는지를 기준으로 삼으며 innodb_read_ahead_threshold 파라미터로 조절할 수 있다. 이 파라미터가 있기 전에는 현 익스텐트의 마지막 파이지를 읽으면서 다음 익스텐트 전체를 미리 꺼내야 하는지만을 계산했었다.innodb_read_ahead_threshold 파라미터는 InnoDB가 순차적인 페이지 접근 패턴에 대해 얼마나 민감한지를 결정한다. 만약 이 파라미터에 정의된 값보다 더 많은 수의 순차적 페이지 접근이 발생한다면 InnoDB는 비동기 read-ahead 를 동작시켜 전체 익스텐트를 가져온다. 파라미터의 값은 0~64까지 올 수 있ㅇ며 기본 값은 56이다. 이 값을 더 높이면 그만큼 접근 패턴에 대해 엄격하다는 뜻이다. 에를 들어 이 값은 48개로 낮추면 현 익스텐트에서 48개의 page만큼만 순차적 접근이 발생하면 linear read-ahead를 동작시킨다.
  • Random: read-ahead 는 이미 버퍼 풀에 있는 페이지에 대해 페이지가 읽히는 순서에 상관없이 언제 페이지가 필요할 것인지를 예측하는 것이다. 만약 버퍼 풀에서 동일한 익스텐트 내 13개의 연속적인 페이지만 발견되어도 InnoDB는 익스텐트의 남은 페이지에 대해 미리 비동기적으로 가져오는 것이다. 이것을 활성화하려면 innodb_randmon_read_ahead 기능을 ON해라.SHOW ENGINE INNODB STATUS를 보면 read-ahead 알고리즘이 얼마나 효과적인지 확인할 수 있다.

 

14.4.3.2 Configuring the Rate of InnoDB Buffer Pool Flushing

InnoDB는 어떤 task들은 백그라운드로 동작시킨다. 가령 dirty page를 백그라운드로 내려보내는 것이 있다. InnoDB는 innodb_max_dirty_pages_pct 로 정의한 값보다 버퍼 풀 내에 더티 페이지의 비율이 커지면  플러쉬 시킨다.

InnoDB는 적당한 플러쉬 속도를 측정하는 알고리즘을 갖고 있는데 리두로그 발생량과 현재의 플러쉬 속도를 기반으로 측정한다. 이 방법의 목적은 버퍼 풀의 상태를 클린하게 유지하면서 적당한 플러쉬 속도를 유지하도록 만듦으로써 전반적으로 안정적인 성능을 만드는데에 있다. 플러쉬 속도를 자동적으로 조절하는 것은 과도한 버퍼 풀 플러쉬가 발생하여 정상적인 R/W을 방해하는 갑작스런 사고를 방지하는데 도움이 된다.

InnoDB는 로그파일을 원형 방식으로 사용한다. 로그 파일을 재사용하기 전에 InnoDB는 해당 로그파일이 가지고 있는 리두 엔트리에 대한 부분을 버퍼 풀에서 디스크로 플러쉬시킨다. 이 과정을 sharp 체크포인트라고 한다. 만약 워크로드가 write의 비중이 크다면 많은 로그를 발생시킬 것이며 모두 리두 로그에 쓰이게 될 것이다. 만약 리두 파일의 공간이 모두 사용되면 sharp 체크포인트가 발생하게 되어 가용한 성능이 잠깐 줄어들게 된다. 이 현상은 innodb_max_dirty_pages_pct에 도달하지 않더라도 발생한다.

InnoDB는 이 현상을 회피하기 위해 버퍼 풀 내에 더티 페이지의 비율과 리두 로그의 발생 속도를 측정한 값을 기반으로 휴리스틱 기반의 알고리즘을 사용한다. 이 값들을 가지고 버퍼 풀에서 초당 플러쉬해야 하는 더티 페이지의 개수를 결정한다. 이 자율적응 알고리즘은 워크로드가 급변하는 상황에서도 효과적으로 쓰일 수 있다.

이 알고리즘을 내부적으로 벤치마킹한 결과 성능을 일정하게 잘 유지할 뿐만 아니라 전반적인 성능을 매우 향상시켰다.

적응적 플러쉬 기법은 워크로드의 I/O 패턴에 매우 큰 효과를 미치기 때문에 innodb_adaptive_flushing 파라미터를 통해 이 기법을 비활성화 하는 방법도 존재한다.

 

14.4.3.3 Making the Buffer Pool Scan Resistant

엄격한 LRU 알고리즘을 사용하기 보다 InnoDB는 버퍼 풀로 로딩되었다가 두 번 다시는 쓰이지 않을 데이터의 양을 최소화하는 기술을 사용하고 있다. 이 기술의 목표는 비록 read-ahead나 full table 스캔으로 인해 어쩌면 재사용하지 않을 수도 있는 페이지라도 가급적 자주 억세스되는 hot 페이지를 선별하는 것이다.

새로 읽히는 블럭은 LRU의 중간에 insert된다. 새로 읽히는 모든 페이지들은 기본적으로 LRU 리스트의 3/8 위치에 insert된다. 버퍼 풀에서 페이지가 처음으로 읽히면 정면 방향(최근 사용된 것들 있는 방향)으로 이동한다. 그러므로 절대 사용되지 않은 페이지는 절대 LRU의 정면 방향으로 이동할 수가 없게 된다. 그리고는 엄격한 LRU 도달에 의해 age out 된다. 이 배열 방법은 LRU 리스트를 두 개의 세그먼트로 나누는데 insert 포인트보다 아래에 있는 페이지들을 old라 간주하고 LRU에서 방출 대상의 희생양으로 삼는다.

InnoDB의 버퍼 풀 작동 방식이나 LRU 알고리즘의 자세한 특성을 알고싶으면 8.10.1의 The InnoDB Buffer Pool 을 참조해라.

LRU list의 insert 포인트를 조절할 수 있는데 테이블 스캔이나 인덱스 스캔에도 동일하게 적용할 것인지도 결정할 수 있다. innodb_old_blocks_pct 파라미터는 old 블럭의 비중을 결정한다. 기본 값은 37로써 3/8에 100을 곱한 값이다. 5부터 95까지 설정할 수 있다. 5로 설정하면 버퍼 풀로 새로 insert된 페이지는 매우 빨리 age out 된다. 95로 설정하면 버퍼 풀의 5% 만이 hot 페이지로 설정되어 일반적인 LRU와 비슷한 구조를 갖게 된다.

read-ahead로 인해 버퍼 풀이 지저분해지는 것을 막아주는 이 최적화 방식 테이블 및 인덱스 스캔 페이지로 버퍼풀이 지저분해지는 것을 방지해주기도 한다. 이런 스캔에서 올라온 데이터 페이지의 경우 대부분 짧은 시간 내에 몇 번 억세스 되고 그 이후로는 접근되지 않는 경우가 많다. innodb_old_blocks_time 파라미터는 처음 페이지가 억세스 되고 ?? 이 파라미터의 기본 값은 1000이며 이 값을 증가시키면 버퍼 풀에서 더 빨리 age out 된다.

innodb_old_blocks_pct 파라미터와 innodb_old_blocks_time 파라미터는 모두 글로벌 변수이고 운영중에 변경할 수 있다.

아래는 이 파라미터들의 효과에 대해 추정하는데 도움을 줄 수 있는 자료이다. show engine innodb status 명령에서 나온 BUFFER POOL AND MEMORY 섹션이다. (MariaDB의 조회한 내용을 가져옴)

Total memory allocated 138412032; in additional pool allocated 0
Total memory allocated by read views 200
Internal hash tables (constant factor + variable factor)
 Adaptive hash index 2217568 (2213368 + 4200)
 Page hash 139112 (buffer pool 0 only)
 Dictionary cache 681082 (554768 + 126314)
 File system 822512 (812272 + 10240)
 Lock system 333232 (332872 + 360)
 Recovery system 0 (0 + 0)
Dictionary memory allocated 126314
Buffer pool size 8191
Buffer pool size, bytes 134201344
Free buffers 7901
Database pages 290
Old database pages 0
Modified db pages 0
Percent of dirty pages(LRU & free pages): 0.000
Max dirty pages percent: 75.000
Pending reads 0
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 0, not young 0
0.00 youngs/s, 0.00 non-youngs/s
Pages read 186, created 109, written 351
0.00 reads/s, 0.00 creates/s, 0.00 writes/s
No buffer pool page gets since the last printout
Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s
LRU len: 290, unzip_LRU len: 0
I/O sum[0]:cur[0], unzip sum[0]:cur[0]
  • Old database page: LRU 리스트의 old에 해당하는 페이지를 의미한다.
  • Pages made young, not young: young또는  young하지 않게 된 old 페이지의 총 개수
  • youngs/s and non young/s: old 페이지가 young 하게 된 초당 속도와 young하지 않게 된 초당 속도이며 InnoDB 상태를 마지막으로 확인해 본 시점간의 비교를 통해 계산한다.

하드웨어 조건, 데이터, 워크로드의 형태에 따라 이 파라미터가 가져오는 효과의 변동이 매우 크므로 운영 환경에 적용하기 전에 반드시 벤치마크를 통해 이 파라미터 변경이 가져오는 효과를 검증해야 한다.

버퍼 풀에 다 들어올 수도 없을 만큼 큰 규모의 테이블을 스캔할 때는 innodb_old_blocks_pct의 값을 작게 함으로써 한번 읽히고 말 페이지가 버퍼 풀의 비중을 많이 차지하지 않도록 해준다. 예를 들어 이 값을 5로 설정하면 한번 읽히고 말 페이지는 버퍼 풀의 5%만 사용할 수 있게 된다.

메모리에 딱 들어맞는 작은 규모의 테이블을 스캔할 때에는, 페이지를 버퍼 풀로 옮기는데 큰 비용이 들지 않기 때문에 innodb_old_blocks_pct의 값을 그냥 놔두던지 더 높게해도 된다.

innodb_old_blocks_time 파라미터의 효과는 innodb_old_blocks_pct 파라미터의 효과보다 예측하기가 어렵다. 그리고 상대적으로 효과도 미미하고 워크로드의 형태에 따라 효과의 격차도 크다. 따라서 innodb_old_blocks_pct 파라미터로 효과를 거두지 못한 경우에 벤치마크 테스트를 고려해 보아라.

 

14.4.3.4 Using Multiple Buffer Pool Instances

버퍼 풀이 수 기가 바이트에 해당하는 시스템의 경우 버퍼 풀을 몇 개의 인스턴스로 구분하는 것이 동시성을 향상시킬 수 있다. 캐시된 페이지에 대한 읽기/쓰기 시 분리된 쓰레드를 이용함으로써 경합을 감소시키게 된다. 이 특성은 버퍼 풀의 사이즈가 수 기가 바이트에 해당하는 시스템을 목표로 만들어진 것이다. 멀티플 버퍼 풀 인스턴스는 innodb_buffer_pool_instance 파라미터를 통해 조절할 수 있다.

만약 버퍼 풀의 사이즈가 크면 대부분의 데이터 요청이 메모리 안에서 해결됨으로써 만족하게 될 것이다. 그러나 여러 개의 쓰레드가 버퍼 풀을 한꺼번에 억세스하려고 시도함에 따라 병목구간에 빠지게 될 수도 있다. 멀티플 버퍼 풀을 활성화함으로써 이 경합을 최소화할 수 있는데 버퍼 풀에 저장되거나 read되는 페이지는 해시함수에 의해 버퍼 풀 중 하나에 임의적으로 할당된다. 각각의 버퍼 풀은 free리스트, flush리스트, LRU리스트등 버퍼 풀에 관련된 데이터 구조체들을 각자 관리한다.

멀티플 버퍼 풀을 활성화하려면 innodb_buffer_pool_instance 파라미터를 1에서 64 사이의 값을 주면 된다. 다만 이것이 효과를 발휘하려면 innodb_buffer_pool_size의 값이 1G 이상이 되어야 한다. 지정한 값은 total size로 이것은 각각의 버퍼 풀로 쪼개지게 된다. 최고의 성능을 위해서 각각의 버퍼 풀이 최소 1G 이상이 되도록 innodb_buffer_pool_instance와 innodb_buffer_pool_size 파라미터를 알맞게 명시하도록 하라.

 

14.4.3.5 Preloading the InnoDB Buffer Pool for Faster Restart

 

 

14.4.3.6 Tuning InnoDB Buffer Pool Flushing

innodb_flush_neighbors나 innodb_lru_scan_depth 파라미터는 버퍼 풀 플러쉬 과정을 좀 더 세밀하게 튜닝할 수 있는 방법을 제공해준다. 이 옵션은 주로 write 비율이 높은 워크로드에 큰 도움이 된다. 플러쉬의 속도가 충분치 않아 버퍼 풀에 과도한 메모리 사용율을 초래하거나, 플러쉬 속도고 너무 과도하여 I/O 용량을 모두 소모시키는 일이 발생할 수 있다. 이상적인 셋팅은 워크로드 환경, 데이터의 억세스 패턴, 스토리지 구성에 따라 달려있다 (HDD냐 SSD냐 등)

지속적으로 무거운 워크로드가 발생하는 환경이나 긴 간격으로 워크로드가 변동하는 시스템에서 아래의 파라미터는 플러쉬 방법을 정밀하게 튜닝할 수 있는 방법을 제공해준다.

innodb_adaptive_flushing_lwm
innodb_max_dirty_pages_pct_lwm
innodb_io_capacity_max
innodb_flushing_avg_loops

위의 파라미터들은 innodb_adaptive_flushing 옵션에 의해 사용되는 공식을 구성하는 파라미터이다. 파라미터간 서로를 제한하거나 확장하는 관계를 가진 파라미터는 아래와 같다.

innodb_adaptive_flushing   –> innodb_adaptive_flushing_lwm
innodb_io_capacity                –> innodb_io_capacity_max
innodb_max_dirty_pages_pct –> innodb_max_dirty_pages_pct_lwm

  • InnoDB의 adaptive flushing 메커니즘은 모든 케이스에 대해 잘 작동하진 않는다. 단지 리두 로그가 위험 수준에 도달했을 때 가장 큰 혜택을 준다. innodb_adaptive_flushing_lwm 파라미터는 리두 로그의 사용량에 대한 비율로써 한계값을 지정한다. 만약 리두로그의 사용량이 이 값을 넘어서면 innodb_adaptive_flushing 파라미터를 OFF해도 adaptive flushing 기법이 사용된다.
  • 만약 adaptive flushing 기법을 사용해도 충분치 않다면 InnoDB는 innodb_io_capacity 파라미터를 이용하여 좀 더 공격적으로 플러쉬시킨다. innodb_io_capacity_max 파라미터는 비상 상황에서 사용되는 사용할 수 있는 I/O의 한계를 의미한다. 이로써 비상 상황에서도 I/O를 모두 소비하지 않도록 제한을 둘 수 있다.

  • InnoDB는 버퍼 풀에서 더티 페이지를 플러쉬 시킴으로써 더티 페이지의 비율이 innodb_max_dirty_pages_pct를 초과하지 않도록 한다. 이 파라미터의 기본 값은 75이다.Note
    innodb_max_dirty_pages_pct 파라미터는 플러쉬 활동의 목표치만 제공할 뿐이지 플러쉬 속도에는 영향을 주지 않는다.innodb_max_dirty_pages_pct_lwm 옵션은 더티 페이지가 innodb_max_dirty_pages_pct 비율에 도달하지 않도록 미리미리 flushing 하도록 하는 최소한의 더티 페이지 비율을 의미한다. 기본 값은 0.001이며 0인 경우 미리 플러쉬하지 않는다.

innodb_flushing_avg_loops 파라미터는 InnoDB가 얼마나 많은 구 버전의 플러쉬 상태 계산 캡쳐본을 유지할 지를 결정하는 파라미터이다. 이것은 adaptive flush가 워크로드의 변화에 얼마나 빨리 반응하는지를 결정하는 파라미터이다. 이 값을 높게 설정하면 이전에 계산해놓은 스냅본이 더 오래 유지되므로 adaptive 응답속도가 좀 더 느려진다. 또한 백그라운드와 포그라운드의 양적 피드백 관계를 약화시키게 된다. 따라서 이 파라미터를 높게 잡았을 경우 InnoDB의 리두로그 사용율이 75%에 도달하지 않도록 하고 innodb_max_dirty_pages_pct의 세팅이 워크로드에 적당한 더티 페이지의 비율을 유지하고 있는지 반드시 확인하여야 한다.

innodb_log_file_size 의 값이 크고 리두 로그의 사용율이 75%에 도달하지 않는 일정한 워크로드가 있는 시스템의 경우 반드시innodb_flushing_avg_loops 의 값을 높게 잡아야 한다. 그래서 플러쉬가 가능한 한 부드럽게 일어나도록 해야 한다. 워크로드가 극단적으로 튀는 시스템이나 로그 파일의 크기가 충분치 않은 환경에서는 이 값을 작게 잡아야 한다. 그래야 부하에 따라 플러쉬 속도가 맞춰서 작동되며 리두 로그의 사용율이 75%에 도달하지 않도록 관리하는데 도움이 된다.

 

14.4.3.7 Configuring InnoDB Buffer Pool Size

버퍼 풀의 사이즈는 startup 시 조절할 수도 있고 운영 중에도 조절할 수도 있다. 이 장에서 설명하는 내용은 두 가지 방법에 모두 해당되는 내용이다.

만약 innodb_buffer_pool_size 파라미터를 통해 버퍼 풀의 사이즈를 줄이면 이 명령은 청크 상태로 동작한다. (MariaDB에는 청크 단위가 없음)

 

 

 

 

Ch14.3 InnoDB Transaction Model and Locking

InnoDB의 트랜잭션 모델의 목표는 다른 멀티버저닝 데이터베이스의 best 특성들을 두 가지 락킹으로 묶어 구현하는 것이다. InnoDB는 오라클처럼 row 레벨 락을 사용하며 쿼리 수행시 락을 유발하지 않는 consistent 읽기방식을 이용한다. 락 정보는 매우 공간 효율적으로 저장되기 때문에 lock 에스컬레이션이 필요없다. 몇몇의 유저가 매 row마다 락을 걸거나 어떤 임의의 집합 단위로 row 락을 걸어도 메모리 고갈이 유발되지 않는다.

InnoDB에서 모든 유저의 활동은 트랜잭션 안에 존재한다. 만약 자동커밋모드가 활성화되어 있으면 각각의 SQL문은 스스로 하나의 트랜잭션을 형성한다. 기본적으로 MySQL은 세션마다 자동커밋 모드를 활성화시킨 채 커넥션을 맺어주는데, 해당 세션에서 발생된 SQL문이 에러를 리턴하지 않는 한 항상 자동 커밋을 한다. 만약 에러를 리턴한다면 에러 종류에 따라 커밋을 할지, 롤백을 할지가 결정된다. 자세한 내용은 14.9.4 “InnoDB Error Handling”을 봐라.

자동커밋모드가 활성되어 있어도 여러 개의 SQL문을 묶어 하나의 트랜잭션으로 만들 수 있다. 명시적으로 START TRANSACTION 또는 BEGIN ~ COMMIT/ROLLBACK 문을 사용하면 된다. 자세한 내용은 13.3.1 “START TRANSACTION, COMMIT, and ROLLBACK Syntax”을 봐라.

만약 autocommit = 0 으로 설정하여 자동커밋 기능을 끈다면 해당 세션이 만든 트랜잭션은 항상 열려있게 된다. COMMIT이나 ROLLBACK 구문이 현 트랜잭션을 끄거나 시작할 수 있다.

COMMIT의 의미는 현 트랜잭션에 의해 수정된 내용이 영구적으로 저장되고 다른 세션에게도 보여지게 된다는 뜻이다. 반면 ROLLBACK은 현 트랜잭션에 의해 수정된 내용을 취소한다는 의미이다. COMMIT이나 ROLLBACK 모두 현 트랜잭션에 의해 잡혀있던 락을 놓아주게 된다.

SQL: 1992 트랜잭션 고립화 수준에 따르면 InnoDB는 REPEATABLE READ를 따르고 있다. InnoDB는 READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, SERIALIZABLE으로 설명되는 4가지 트랜잭션 고립화 수준을 모두 지원하고 있다.

유저는 SET TRANSACTION 구문을 통해 고립화 수준을 변경할 수 있다. 서버의 기본 고립화 수준을 설정하려면서버 시작시 –transaction-isolation 옵션을 이용하거나 옵션파일에서 설정할 수 있다.

행 단위 락킹에서 InnoDB는 next-key 락킹을 사용한다. InnoDB는 gap 락도 사용하는데 이것은 gap 사이에 인덱스화된 값을 insert할 수도 있는 구간 사이를 다른 세션이 insert하지 못하도록 구간을 락킹하는 것이다. next-key 락은 해당 레코드와 이전 레코드 구간에 대해 락을 거는 것이다.?? gap 락은 구간만 락을 거는 것이다.

row-level 락에 대해 궁금하다면, 그리고 gap locking이 무효화되는 환경이 궁금하다면 14.3.2 “InnoDB Record, Gap, and Next-Key Locks”을 봐라.

공간 인덱스와 관련된 동작에서 락을 통제하기 위해, repeatable read, resializable 트랜잭션 고립화 수준에서 next-key 락은 작동하지 않는다. 이 경우 예측 lock을 사용한다. 14.3.6의 “Predicate Locking for Spatial Indexes”을 봐라.

 

14.3.1 InnoDB Lock Modes

InnoDB는 두 가지 표준 row-level 락을 구현하고 있는데 shared lock(S)과 exclusive lock(X) 을 사용하고 있다. record락, gap락, next-key락등에 대해선 다음 절을 봐라.

  • Shared lock(S)은 트랜잭션이 row를 읽는 것을 허용한다.
  • Exclusive lock(X)은 트랜잭션이 update하거나 delete 하는 것을 허용한다.

만약 트랜잭션 T1이 row r에 대해서 S락을 갖고 있다면 동일한 row r에 대한 다른 트랜잭션 T2의 요청은 아래와 같이 처리된다.

  • T2가 S lock을 요청한다면 해당 락을 즉시 얻을 수 있다. 그 결과 T1와 T2는 모두 row r에 대해 S락을 얻게 된다.
  • T2가 X lock을 요청한다면 락을 즉시 얻을 수 없다.

만약 트랜잭션 T1이 row r에 대해 X락을 얻었다면 T2는 S락이나 X락 모두 즉시 얻을 수 없다. T2는 T1이 row r에 대해 락을 놓을 때까지 기다려야 한다.

Intention Locks

덧붙여서 InnoDB는 레코드에 대한 락과 테이블에 대한 락이 공존하는 ‘multiple granularity lock”을 지원한다. 이것을 실제적으로 지원하기 위해 intention 락이라는 타입을 사용한다. intention락은 일종의 테이블 락인데 트랜잭션이 테이블 내부 row에 대해 어떤 종류의 락(S 혹은 X)을 걸고 작업할 것인지를 알려주는 역할을 한다. InnoDB에는 두 가지의 intention 락이 존재한다.

  • Intention shared(IS): 트랜잭션 T가 테이블 row에 대해 S락을 걸 것임을 알림
  • Intention Exclusive(IX): 트랜잭션 T가 row에 대해 X락을 걸 것임을 알림

SELECT … LOCK IN SHARE MORE는 IS 락을 거는 것이며
SELECT … FOR UPDATE는 IX 락을 거는 것이다.

intention 락 프로토콜은 아래와 같다.

  • 트랜잭션이 테이블t 내부 row에 대해 S락을 걸려면, 먼저 테이블 t에 대해 IS락이나 더 강한 락을 획득해야 한다.
  • row에 대해 X락을 걸려면 테이블 t에 대해 반드시 IX락을 먼저 획득해야 한다.

이상 설명한 rule은 lock type 호환표로 정리할 수 있다.

X IX S IS
X Conflict Conflict Conflict Conflict
IX Conflict Compatible Conflict Compatible
S Conflict Conflict Compatible Compatible
IS Conflict Compatible Compatible Compatible

현재 걸려있는 락과 호환 가능하면 트랜잭션은 락을 얻을 수 있다. 그러나 현재 걸려있는 락과 충돌이 발생하면 얻을 수 없다. 그렇게 되면 락을 얻으려는 트랜잭션은 락이 풀릴 때까지 대기해야 한다. 만약 락 충돌이 발생하고 데드락 때문에 즉시 락을 얻을 수 없는 것이라면 에러가 발생한다.

그러므로 intention 락은 Full 테이블 요청(LOCK TABLES … WRITE)을 제외하고는 어떠한 것도 막지 않는다. IX와 IS 락의 가장 큰 목적은 누군가가 row 락을 걸어놨거나 락을 걸으려 한다는 것을 보여주려고 함에 있다.

Deadlock Example

아래 예제는 lock 요청이 어떻게 데드락을 발생시키는지 보여준다. 클라이언트 A와 B가 있다고 해보자. 먼저 A는 1개의 row를 갖는 테이블을 만들고 트랜잭션을 시작하였다. 트랜잭션에서 A는 row에 S락을 설정하였다.

mysql> CREATE TABLE t (i INT) ENGINE = InnoDB;
Query OK, 0 rows affected (1.07 sec)

mysql> INSERT INTO t (i) VALUES(1);
Query OK, 1 row affected (0.09 sec)

mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT * FROM t WHERE i = 1 LOCK IN SHARE MODE;
+------+
| i    |
+------+
|    1 |
+------+

그리고 클라이언트 B는 트랜잭션을 시작하면서 이 값을 지우려고 하고 있다.

mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)

mysql> DELETE FROM t WHERE i = 1;

삭제 작업은 X 락이 필요하다. 이 락은 A가 쥐고 있는 S 락과 호환되지 않기 때문에 대기하게 된다. 따라서 해당 요청은 row과 block과 함께 lock request 큐로 옮겨가게 된다.

그 후 A가 delete를 시도하려고 할 때 아래와 같은 메시지를 보게 된다.

mysql> DELETE FROM t WHERE i = 1;
ERROR 1213 (40001): Deadlock found when trying to get lock;
try restarting transaction

A가 삭제를 하려면 X 락이 필요하게 되므로 데드락이 발생한다. B는 A가 잡고 있는 S락이 풀리기를 기다리며 A는 B가 잡은 X락이 풀리기만을 기다리고 있으므로 충돌이 발생하는 것이다. 결과적으로 InnoDB는 클라이언트 중 한 명에게 에러를 뿌리며 락을 놓아주게 된다. 해당 클라이언트는 아래와 같은 메시지를 받게 된다.

ERROR 1213 (40001): Deadlock found when trying to get lock;
try restarting transaction

그러면 다른 한 명은 락을 획득하여 row를 삭제할 수 있게 된다.

 

14.3.2 InnoDB Record, Gap, and Next-Key Locks

InnoDB는 레코드락 외에도 gap 락, next-key 락등 여러 락을 가지고 있다.

  • 레코드 락: 인덱스 레코드를 잠그는 것(인덱스란 clustered index)
  • Gap 락: 인덱스 레코드 사이를 잠그는 것
  • 레코드 락과 gap 락을 함쳐놓은 것

Record Locks

테이블을 인덱스 없이 생성했어도 레코드 락은 항상 인덱스 레코드에 대해서 잠금을 한다. 왜냐하면 InnoDB는 클러스터드 인덱스를 보이지 않게 가지고 있기 때문에 이 인덱스에 레크드 락킹을 구현하는 것이다.

Next-key Locks

기본적으로 InnoDB는 REPEATABLE READ 트랜잭션 고립화 수준을 유지한다. 이 경우 InnoDB 유저는 인덱스를 스캔하는데 next-key 락을 사용하여 팬텀 row 를 방지할 수 있게 된다.

next-key 락킹은 인덱스 row 락과 gap 락을 합친 것이다. InnoDB는 테이블을 스캔할 때 이런 방식으로 row 수준 락킹을 적용한다. 따라서 쿼리에 따라 마주치는 레코드에 대해 shared 또는 exclusive 락을 걸어둔다. 결국 row 수준 락은 인덱스 레코드 락이라 할 수 있다. 덧붙여서 인덱스 레코드 에 대한 next-key 락은 해당 레코드에 대한 gap에도 영향을 미친다. 왜냐하면 next-key 락은 레코드 락과 레코드 앞에 있는 대상에 락을 거는 gap락을 합친 것이기 때문이다. 만약 어떤 세션이 레코드 R 에다가 shared, 또는 exclusive 락을 걸었다면 다른 세션은 레코드 R보다 인덱스 순서상 앞에 있는 레코드에 대해 새로운 값을 insert 할 수 없다.

인덱스가 값 10, 11, 13, 20을 가지고 있다고 해보자. 이 인덱스에 대해 next-key를 설정할 수 있는 구간은 아래와 같다. ] 표시는 해당 숫자까지 포함됨을 의미한다.

(negative infinity, 10]
(10, 11]
(11, 13]
(13, 20]
(20, positive infinity)

마지막 구간에서는 인덱스에서 가장 큰 값(20)과 어떤 값보다도 큰 가상의 “supremum”이라는 가상 레코드 구간 사이를 잠근다. 이 “supremum”은 사실 인덱스에 존재하지 않는 값이기 때문에 ??

Gap Locks

위에서 본 next-key 예제은 gap의 구간이 하나의 인덱스 값이 될 수도 있으며, 여러 개의 인덱스 값 구간이 될 수도 있고, 심지어 비어있을 수도 있다는 것을 보여준다.

Gap 락킹은 유니크 인덱스로 단일의 유니크 row를 검색하는 SQL구문에는 필요없다. 예를 들어 id 컬럼이 유니크 인덱스를 가질 때, 아래와 같은 값이 100인 row에 인덱스 레코드 락이 사용되며 다른 세션에서 이 값보다 작은 값을 insert하는 것을 허용한다.

SELECT * FROM child WHERE id = 100;

만약 id 가 인덱스를 갖고 있지 않거나 유니크하지 않다면 SQL은 앞선 gap구간에 락을 건다. 여기서 호환되지 않는 락이 다른 트랜잭션에 의해 얻어질 수도 있음을 유념해야 한다. 예를 들어 트랜잭션 B가 exclusive gap 락을 걸어놓은 구간에 트랜잭션 A가 shared gap 락을 걸수도 있다는 것이다. gap 락이 동시에 걸릴 수 있는 이유는 만약 어떤 레코드가 인덱스에서 삭제되면, 다른 트랜잭션에 의해 걸려있던 gap 락과 합쳐져야 하기 때문이다..?????

InnoDB에서 gap 락은  “순전히 억제하는” 기능을 갖고 있다. 즉 gap 사이에 다른 트랜잭션이 insert 하는 것을 막는 역할을 하는 것이다. 다른 트랜잭션이 같은 구간에 또 다른 gap 락을 거는 것을 막지 않는다. 그러므로 gap X-lock은 gap S-lock과 똑같은 효과를 보인다.

gap 락의 한 종류 중 insert intention gap 락이라고 불리는 gap 락은 insert 구문에서 row에 값을 삽입하기 전에 설정된다. 이 락은 insert시에 여러 개의 트랜잭션이 동일한 구간 내에서 똑같은 위치에 insert하는 것만 아니라면 서로가 대기하는 현상이 필요없음을 알려준다. insert 레코드가 4부터 7까지 있는 것을 생각해보자. 각각의 트랜잭션이 값 5와 6을 insert하려고 한다고 해보면, 각각은 insert하려는 row에 대한 exclusive 락을 얻기 전에 4부터 7 구간에 대해 insert intention 락을 건다. 그러나 서로가 insert 하려는 row가 다르기 때문에 충돌을 일으키지 않는다.

아래 예제는 값을 insert하기 위한 exclusive 락을 얻기 이전에 insert intention 락을 얻는 과정을 보여준다.

클라이언트 A가 두 개의 레코드(90과 102)가 있는 테이블을 만든 후 id가 100보다 큰 레코드에 대해 exclusive 락을 얻으려고 하고 있다. 이 exclusive 락은 레코드 102 이전에 대해 gap lock을 포함한다.

mysql> CREATE TABLE child (id int(11) NOT NULL, PRIMARY KEY(id)) ENGINE=InnoDB;
mysql> INSERT INTO child (id) values (90),(102);

mysql> START TRANSACTION;
mysql> SELECT * FROM child WHERE id > 100 FOR UPDATE;
+-----+
| id  |
+-----+
| 102 |
+-----+

클라이언트 B가 이 구간 사이로 값을 insert하려고 한다고 해보자. 이 트랜잭션은 insert intention lock을 얻지만 exclusive lock을 얻기 위해 대기하게 된다.

mysql> START TRANSACTION;
mysql> INSERT INTO child (id) VALUES (101);

insert intention lock을 보기 위해 SHOW ENGINE INNODB STATUS 명령을 쳐봐라. TRANSACTIONS 부분에 아래와 같은 내용을 볼 수 있을 것이다. (생략)

Disabling Gap Locking

gap 락은 명시적으로 해제시킬 수 있다. 트랜잭션 고립화 수준을 READ COMMITTED로 변경하던지innodb_locks_unsafe_for_binlog 파라미터(지금은 비권장하는)를 활성화시켜라. 이런 환경에서는 검색이나 인덱스 탐색에서는 gap 락이 쓰이지 않게 되며, FK 제약조건 체크나 중복키 를 체크하는 환경에서만 쓰이게 된다.

그리고 트랜잭션 고립화 수준을 READ COMMITTED로 변경하는 것이나 innodb_locks_unsafe_for_binlog 파라미터를 활성화 시켰을 경우 발생하는 또 다른 특이사항이 있다. 바로 SQL의 where 조건에 매칭되지 않는 row는 즉각 레코드 lock이 풀린다는 것이다.  update 구문에서 InnoDB는 “반정합성” read를 한다. 즉 마지막으로 커밋된 버전을 리턴하기 때문에 update절에서 where 조건에 부합되는지를 판단할 수 있는 것이다.

 

14.3.3 Avoiding the Phantom Problem Using Next-Key Locking

phantom 읽기라고 일컫는 문제는 똑같은 쿼리를 다른 시간(T1과 T2)에 수행했을 때 결과 집합이달라지는 것을 말한다. 예를 들어 SELECT문이 두 번 수행됐을 때 첫 번째 읽어을 때는 없던 값이 두 번째 읽을 때는 보이는 것을 의미하며 이것을 “유령” row라고 한다.

child 테이블의 id 컬럼에 PK가 구성되어 있고 아래와 같이 100보다 큰 값 이상에 대해 차후 업데이트할 의향으로 조회하고 락을 걸고 싶다고 가정해보자.

SELECT * FROM child WHERE id > 100 FOR UPDATE;

이 쿼리는 id가 100보다 큰 첫 번째 값을 시작점으로 인덱스를 스캔하기 시작할 것이다. 여기서 테이블에 id 값이 90과 102가 있다고 가정해보자. 만약 스캔한 인덱스 레코드 중 gap 구간(여기서는 90부터 102까지를 의미함)에 대한 insert를 막지 않는다면, 다른 세션이 id=101을 가지고 insert를 할 수 있을 것이다. 그래서 당신이 이 트랜잭션 안에서 똑같은 SELECT를 다시 한다면 id=101이라는 새로운 row(“phantom”)을 보게 될 것이다. 이 row 결과값을 데이터 항목이라고 한다면 새로운 phantom row로 인해, 트랜잭션은 항상 실행 가능해야 하고 트랜잭션 내에서 읽은 값은 변하면 안된다는  원칙을 위반하는 것이다.

phantom 현상을 막기 위해서 InnoDB는 next-key 락킹이라고 부르는 매커니즘을 사용하는데 이것은 index-row 락킹과 gap 락킹을 합쳐 놓은 것이다. InnoDB는 테이블 인덱스를 검색하거나 스캔할 때, 마주치는 레코드에 shared 락이나 exclusive 락을 걸어놓는 방식으로 row레벨 락킹을 구현하고 있다. 그러므로 row level 락킹은 사실상 인덱스 레코드 락이라고 할 수 있다. 이 말은 next-key 락은 index-record 락 더하기 인덱스 앞 레코드와의 gap에 대한 gap 락이라고 할 수 있다.만약 어떤 세션이 index 레코드 R에 대해서 shared나 exclusive 락을 걸었을 때, 다른 세션은 인덱스 순서상 R보다 앞에 있는 구간에 대해 새로운 값을 즉시 insert할 수 없다.

InnoDB가 인덱스를 스캔하면서 인덱스의 마지막 값 다음 구간(gap)에 대해 락을 걸 수도 있다. 아까 보았던 예제에서 이런 현상이 발생할 수 있다: id가 100보다 큰 값으로 insert 하려는 시도를 막으려면 id=102 부터의 구간에 대해 락을 걸어야 한다.

어플리케이션에서 유일성 체크를 구현하기 위해 next-key 락킹을 사용할 수 있다:  만약 당신이 share 모드에서 데이터를 읽고 insert하려고 할 때 중복된 값을 발견하지 않아 안전하게 해당 row를 insert할 수 있고 내가 insert 하려는 값 이후의 구간에 대해 next-key 락이 걸려있기 때문에 아무도 insert하거나 중복시킬 수 없다는 것을 알고 있다. 그러므로 next-key 락킹은 테이블에 존재하지 않는 값에 대해 락을 거는 셈이다.

gap 락킹을 비활성화 하는 것을 앞장에서 배웠다. 이것을 비활성화하면 다른 세션이 새로운 row를 insert할 수 있기 때문에 phantom 문제가 발생할 수 있다는 것을 명심해라.

 

14.3.4 Consistent Nonlocking Reads

일관성 읽기란 InnoDB가 멀티 버저닝을 이용하여 쿼리가 발행된 시점에서의 데이터베이스의 스냅샷을 구성하여 쿼리해오는 것을 의미한다. 쿼리는 해당 시점 전에 commit 된 트랜잭션으로 인해 변경된 값을 본다. 그리고 쿼리가 날라온 시간 이후에 변경된 데이터나 아직 커밋되지 않은 데이터는 읽지 않는다. 이 법칙의 예외사항이 하나 있는데 동일 트랜잭션 안에서 먼저 수행된 SQL과 다른 변경사항을 보는 것이다. 이 예외사항은 아래와 같은 이유 때문에 발생한다: 만약 테이블에서 어떤 rows를 update하면 SELECT 구문은 update된 마지막 버전의 row를 보게된다. 그러나 다른 예전 버전을 보게 될 수도 있다. (?? 이게 몬소리?) 만약 다른 세션이 동시에 해당 테이블을 업데이트하였다면, 데이터베이스에서 존재하지 않았던 테이블의 상태를 보게 되는 것이다.

만약 트랜잭션 고립화 수준이 REPEATABLE READ로 되어 있다면, 해당 트랜잭션에서 발행된 첫 번째 read 에 대해 snapshot을 만들고 이 snapshot을 읽음으로써 트랜잭션 안에서의 일관성 읽기가 가능하다. 만약 새로운 버전의 스냅샷을 만들고 싶다면 현 트랜잭션을 commit하고 다시 새로운 쿼리를 발행하면 된다.

READ COMMITTED 고립화 수준에서는 트랜잭션 내부의 일관적 읽기는 각각의 새로운 스냅샷을 생성하고 참조한다.

일관성 읽기는 InnoDB의 기본 모드이고 READ COMMITTED나 REPEATABLE READ 고립화 수준에서 SELECT 구문을 사용할 때 항상 사용되는 모드이다. 일관성 읽기는 테이블에서 억세스되는부분에 대해 락을 걸지 않으며 일관성 읽기가 수행되는 시점에서 테이블을 수정하는 것이 가능하다.

REPEATABLE READ 읽기 모드에서 일반적인 SELECT 구문을 날렸다고 가정해보자. InnoDB는 당신이 데이터베이스를 쿼리하고 있는 그 시점에 따른 timepoint를 가지고 트랜잭션을 생성한다. 만약 다른 트랜잭션이 row를 삭제하고 commit해도 당신은 row가 삭제된 것을 보지 못한다. insert나 update도 비슷하게 작용한다.

Note
데이터베이스의 스냅샷은 트랜잭션 내부에서 SELECT 구문에서 생성되며 DML 구문에서는 생성되지 않는다. 만약 당신이 트랜잭션에서 어떤 row를 insert하거나 update하고 commit 했는데 다른 트랜잭션이 DELETE나 UPDATE를 발행한다면 방금 당신이 commit한 그 row가 영향 받을 수도 있다. 어떤 트랜잭션이 다른 트랜잭션에 의해 이미 commit된 row를 update하거나 insert 했을 때 이 변경사항을 바로 눈으로 확인할 수 있다.

현재의 트래잭션을 commit하고 다른 SELECT나 START TRANSACTION WITH CONSISTENT SNAPSHOT 문을 명시함으로써 timepoint를 갱신할 수 있다.

이것을 MVCC(멀티버전 동시성 제어)라고 한다.

아래 예제에서 세션 A는 B가 row를 insert하고 커밋함과 더불어 A 세션 역시 커밋을 해야 B가 insert한 값을 볼 수 있음을 나타낸다. 따라서 timepoint는 B가 이전에 발행한 commit 시점으로 갱신된 것이다.

             Session A              Session B

           SET autocommit=0;      SET autocommit=0;
time
|          SELECT * FROM t;
|          empty set
|                                 INSERT INTO t VALUES (1, 2);
|
v          SELECT * FROM t;
           empty set
                                  COMMIT;

           SELECT * FROM t;
           empty set

           COMMIT;

           SELECT * FROM t;
           ---------------------
           |    1    |    2    |
           ---------------------

만약 가장 최근 상태의 데이터베이스를 보려면 READ COMMITTED 고립화 수준을 사용하던지 locking read를 사용해라.

SELECT * FROM t LOCK IN SHARE MODE;

READ COMMITTED 고립화 수준에서 트랜잭션 내부의 일관성 읽기 쿼리는 각각 최신본의 스냅샷을 셋팅하고 읽는다. LOCK IN SHARE MODE에서 락 읽기는 이렇게 된다: SELECT 하면서 만든 가장 최신본의 row의 트랜잭션이 끝날 때까지 다른 세션들을 block한다.

일관적 읽기는 명확한 DDL 구문에서는 동작하지 않는다.

  • 일관성 읽기는 drop table에서 작동하지 않는다. 왜냐하면 MySQL이 테이블을 drop하고 InnoDB가 테이블을 파괴하기 때문(destroy)
  • ALTER TABLE에서도 작동하지 않는다. 왜냐면 ALTER문은 임시 테이블을 구성하고 작업이 완료되면 원본 테이블을 지워버리기 때문이다. 만약 당신이 트랜잭션 안에서 일관성 읽기를 재발행해도 스냅샷에 row가 존재하지 않기 때문에 새로운 테이블의 row가 보이지 않게 된다. 이러한 경우 트랜잭션은 에러를 보낸다: ER_TABLE_DEF_CHANGED “Table definition has changed, please retry transaction”.

FOR UPDATE나 LOCK IN SHARE MODE를 명시하지 않은 SELECT를 이용 다양한 구문들, 가령
INSERT INTO .. SELECT
UPDATE … (SELECT)
CREATE TABLE … SELECT

  • 기본적으로 InnoDB는 강한 락을 사용하며 SELECT 부분을 READ COMMITTED로 수행한다. 비록 같은 트랜잭션 내에서라도 해당 일관성 읽기는 최신본의 스냅샷을 생성하고 참조한다.
  • 이런 경우에 일관성 읽기를 사용하려면 innodb_locks_unsafe_for_binlog 파라미터를 활성화하고 트랜잭션 고립화 수준을 SERIALIZABLE 외에 3가지 중 아무거나 하나로 설정해라. 그러면 select한 테이블에서 어떤 row에도 락을 걸지 않는다.

 

14.3.5 Locking Reads (SELECT … FOR UPDATE and SELECT … LOCK IN SHARE MODE)

만약 당신이 동일 트랜잭션에서 데이터를 조회하고 관련된 값을 업데이트하고자 한다면, 단순히 SELECT 구문은 안정성을 보장하기에는 충분치 않다. 다른 트랜잭션이 당신이 쿼리했던 똑같은 row를 update하거나 delete할 수 있다. 그래서 InnoDB는 추가적인 안정장치를 위해 두 가지의 locking read를 제공하고 있다.

  • SELECT … LOCK IN SHARE MORE
    읽은 row에 shared 모드의 락을 걸어둔다. 다른 세션은 읽을 수는 있으나 당신이 commit하기 전까지는 데이터를 수정할 수 없다. 만약 다른 트랜잭션이 commit하지 않은 채 데이터를 수정했다면 이 트랜잭션이 끝날 때까지 당신이 기다려야 한다.
  • SELECT … FOR UPDATE 는 인덱스 레코드에 락을 걸어 update한 것과 마찬가지의 효과를 준다. 다른 트랜잭션은 해당 row를 업데이트할 수 없으며 SELECT … LOCK IN SHARE MODE로 읽거나 특정 고립화 수준에서 읽는 것도 막히게 된다. 그러나 일관성 읽기는 레코드의 락을 무시한다 (레코드의 구버전 데이터는 락에 걸릴 수 없다. 구버전 데이터는 메모리 상에 존재하는 레코드의 복사본인 undo log로부터 재구성된 것이다)

이 장은 tree 구조나 graph 구조의 데이터, 혹은 단일 테이블이나 데이터가 여러 개의 테이블에 걸쳐 있는 경우 특히 유용할 것이다. ??

LOCK IN SHARE MODE나 FOR UPDADE로 인해 생긴 락 세트는 commit하거나 롤백할 때 해제된다.

Note
SELECT FOR UPDATE는 autocommit이 비활성화 되었을 때만 적용된다 (혹은 START TRANSACTION으로 시작했을 때) 만약 autocommit이 활성화되면 해당 row에 락이 걸리지 않는다.

Usage Examples

테이블 child에 새로운 값을 insert 한다고 가정해보자. 그리고 child의 부모key에 해당하는 parent 테이블이 있다고 해보자. 당신의 어플리케이션 코드는 참조 완결성이라는 것을 지켜야 한다.

첫 번째로, PARENT  테이블을 읽관성 읽기를 하고 row 세트가 존재함을 확인한다. 이때 당신은 안심하고 child 테이블로 insert 할 수 있겠는가? 아니다. 왜냐하면 당신의 select와 insert 동작 사이에 누군가 parent 테이블의 값을 삭제했을 수도 있기 때문이다.

이러한 잠재적인 문제를 방지하기 위해, LOCK IN SHARE MODE로 select를 수행해라.

SELECT * FROM parent WHERE NAME = 'Jones' LOCK IN SHARE MODE;

LOCK IN SHARE MODE로 parent 테이블의 ‘Jone’을 쿼리한 후에 당신은 child 테이블에 안전하게 insert하고 commit할 수 있다. 당신이 수정하려고 하는 parent 테이블의 row를 읽거나 write하려고 하는 어떤 트랜잭션도 당신이 완료할 때까지 기다려야 한다. 즉 모든 테이블의 데이터가 일관적인 상태에 놓인 것이다.

다른 예를 들어보자. CHILD_CODES라는 테이블에 정수를 카운트하는 필드가 존재하며 이것이 CHILD 테이블에 존재하는 각각의 row에 대해 유니크한 식별자를 부여하는 역할을 하고 있다고 해보자. 이때 일관적 읽기나 SHARE MODE 읽기로 카운터의 현재 값을 읽으면 안된다. 왜냐면 두 유저가 카운터의 같은 값을 볼 가능성이 있으며 child 테이블의 row에 대해 똑같은 식별자를 중복생성할 수 있기 때문에 duplicate-key 에러가 발생하게 된다.

여기서는 LOCK IN SHARE MODE는 좋은 선택이 아니다. 왜냐면 두 유저가 카운터를 동시에 읽었을 경우 적어도 한 명은 카운터를 업데이트하려고 시도하는 데드락 상황에 빠지게 된다.

카운터를 읽고 증가시키는 것을 구현하기 위해서는 FOR UPDATE로 일단 읽고 카운터를 증가시켜야 한다.

SELECT counter_field FROM child_codes FOR UPDATE;
UPDATE child_codes SET counter_field = counter_field + 1;

SELECT … FOR UPDATE는 가장 최신의 데이터를 읽고 읽은 row에 X락을 걸어둔다. 그러므로 이것은 UPDATE가 row에 걸어둘 락과 동일한 락을 걸어두는 것이다.

위 예제는 다만 어떻게 SELECT … FOR UPDATE가 이용되는지를 보여준 것이다. 사실 MySQL에서 유일 식별자를 생성하는 것은 테이블의 단일 접근을 통해 구현할 수 있다.

UPDATE child_codes SET counter_field = LAST_INSERT_ID(counter_field + 1);
SELECT LAST_INSERT_ID();

이 SELECT 부분은 단지 식별자 정보를 받아올 뿐이며 (현재의 커넥션에 한해서) 테이블에 접근하지는 않는다.

 

14.3.7 Locks Set by Different SQL Statements in InnoDB

락모드로 읽기나 update, delete 같은 구문들은 인덱스 스캔 중에 만나는 모든 row에 index 레코드 락을 건다. 심지어 where 조건에 해당하지 않는 레코드에도 락을 건다. InnoDB는 where 조건을 기억하지 못하며 스캔한 인덱스 범위만을 알고있다. 락은 보통 next-key 락을 의미하며 이것은 레코드보다 앞에 있는 구간에 대해 insert를 하지 못하도록 즉각적으로 막는 역할을 한다. gap 락킹은 명시적으로 비활성화할 수 있으며 이 경우 next-key 락킹도 사용하지 못하게 된다. 트랜잭션 고립화 수준 역시 어떤 락이 세팅될지 영향을 준다.

만약 세컨더리 인덱스를 탐색하고 인덱스 레코드 락에 X락이 걸린다면 InnoDB는 관련된 클러스터드 인덱스 레코드를 복구하고 락을 건다.

만약 SQL 구문에 적당한 인덱스가 없어서 전체 테이블을 스캔해야 한다면 테이블의 모든 row에 락이 걸리게 되며, 다른 유저가 테이블에 insert 하는 것을 막게 된다. 따라서 테이블에 적당한 인덱스를 잘 만들어놔야 불필요하게 많은 row를 스캔하지 않게 된다.

SELECT … FOR UPDATE나 SELECT … LOCK IN SHARE MODE 같은 쿼리는 scan한 row에 대해 락을 걸며 결과 집합에서 조건에 해당하지 않는 row에 대해선 락을 해제한다. 그러나 몇몇의 케이스에서 보았듯이 쿼리 실행동안에 원본 소스와 결과 집합간의 관계가 실종되기 때문에 락이 바로 해제되지 않을 수 있다. 예를 들어, UNION 동작은 테이블을 스캔하고 (그리고 락을 건다) row를 임시 테이블에 넣어 결과세트를 평가하는 작업을 한다. 이런 환경에서 임시  테이블에 있는 row와 원본 테이블에 있는 관계가 끊어지게 되며 앞단의 row들은 쿼리 실행이 끝날 때까지 lock이 풀리지 않을 수 있다.

InnoDB는 락의 타입을 아래와 같이 정의하고 있다.

  • SELECT … FROM
    일관성 읽기이며 데이터베이스로부터 스냅샷을 만든다. 트랜잭션 고립화 수준이 SERIALIZABLE이 아닌 이상 락을 걸진 않는다. SERIALIZABLE인 경우 row에 shared next-key 락을 건다.
  • SELECT … FROM …LOCK IN SHARE MODE
    검색하는 과정에서 마주친 모든 레코드에 shared next-key 락을 건다.
  • SELECT … FROM … FOR UPDATE 구문은 LOCK IN SHARE MODE나 특정 트랜잭션 고립화 수준에서의 읽기와 호환되지 않는다. 일관성 읽기는 락 셋팅을 무시한다.
  • UPDATE … WHERE … 는 exclusive next-key 락을 건다.
  • DELETE FROM … WHERE … 도 마찬가지로 exclusive next-key 락을 건다.
  • INSERT는 insert된 row에 exclusive 인덱스레코드 락을 건다. (next-key 락이 아니며 이말은 gap lock도 없다는 것을 의미) 다른 세션이 insert된 row 보다 앞에 insert하는 것을 막지 않는다.insert 하기에 앞서 insertion intention gap 락이라 불리는 gap 락이 셋팅된다. 이것은 다른 여러 트랜잭션이 구간내에 똑같은 값으로 insert 하지만 않는다면 대기할 필요없이 진행할 수 있다는 것을 알려주는 일종의 시그널이다. 인덱스 레코드가 4와 7이 있다고 생각해보자. 서로 다른 트랜잭션이 5와 6일 insert 하려고 하며 insert 하기 위한 X락을 얻기 전에 각각 4와 7 구간을 lnsert intention lock을 걸게 된다. 이 경우 서로 insert 하려는 값이 다르기 때문에 서로 막지 않게 된다.

    만약 중복키 에러가 발생한다면 해당 중복된 인덱스 레코드에 S락이 걸린다. S락은 건 후 여러 개의 세션이 똑같은 값을 insert하려고 시도하면서 서로 X락을 대기하는 현상 때문에 S락은 데드락을 유발할 수도 있다. 데드락은 어떤 세션이 값을 삭제하면서도 생길 수 있다. 테이블 T1이 아래와 같은 구조를 가지고 있다고 생각해보자.

    CREATE TABLE t1 (i INT, PRIMARY KEY (i)) ENGINE = InnoDB;

    3개의 세션이 차례대로 아래와 같은 작업을 하고 있다.

    session 1:

    START TRANSACTION;
    INSERT INTO t1 VALUES(1);

    session 2:

    START TRANSACTION;
    INSERT INTO t1 VALUES(1);
    session 3:
    START TRANSACTION;
    INSERT INTO t1 VALUES(1);

    session 1:

    ROLLBACK;

    세션 1에 의한 첫 번째 동작에 의해 해당 row에 대해 X락을 걸린다. 세션 2와 세션 3에 의한 동작은 중복키 에러가 발생하게 되며 두 세션 모두 row에 대해 shared 락을 요구하게 된다. 세션 1이 rollback했을 때 row에 대해 X락을 풀게 되어 세션2와 3이 S락을 얻을 수 있게 된다. 이제 세션 2와 3이 데드락 상황에 빠지게 된다. 둘 다 S락을 얻은 상태이기 때문에 X락을 얻을 수 없다.

    비슷한 상황으로 테이블이 이미 1을 가지고 있고 세 개의 세션이 아래와 같은 동작으로 수행하려 한다고 가정해보자.

    session 1:

    START TRANSACTION;
    DELETE FROM t1 WHERE i = 1;
    
    session 2:
    START TRANSACTION;
    INSERT INTO t1 VALUES(1);
    
    session 3:

    START TRANSACTION; INSERT INTO t1 VALUES(1);

    session 1:
    COMMIT;

    세션 1은 row에 대해 X락을 얻게된다. 세션 2와 3의 동작은 중복키 에러를 발생하면서 row 대해 S락을 기다리게 된다. 세션1이 commit을 하면서 X락이 풀리고 세션 2와 3은 큐에 대기하고 있던 S락을 획득하게 된다. 이 시점에서 세션 2와 3에 데드락이 걸리게 된다. 두 세션 모두 서로가 갖고 있는 S락으로 인해 X락을 획득할 수 없다.

 

  • INSERT … ON DUPLICATE KEY UPDATE
    X락을 거는 단순한 INSERT와는 좀 다르다. 중복키로 인해 update가 되는 부분에서는 S락이걸린다.
  • REPLACE
    유일키에 대해 충돌되지 않으면 INSERT와 동일하며 중복키에 대해 X락이 걸린다.
  • INSERT INTO T SELECT … FROM S WHERE …
    테이블 T에 대해 insert 되는 row에 대해서는 exclusive 인덱스 레코드 락이 걸린다(gap lock이 아님) 만약 트랜잭션 고립화 수준이 READ COMMITTED이거나 innodb_locks_unsafe_for_binlog가 활성화되고 고립화 수준이 SERIALIZABLE이 아니면 S에 대해서는 일관성 읽기를 한다(락을 걸지 않음) 그렇지 않으면 S에 대해 S next-key 락을 건다.

 

14.3.8 Implicit Transaction Commit and Rollback

기본적으로 MySQL은 autocommit 이 활성화된 상태로 커넥션을 맺어준다. 그래서 쿼리 결과에 오류만 없으면 항상 커밋된 결과를 돌려받게 된다. 만약 에러가 있다면 커밋 또는 롤백하게 된다.

그리고 만약 autocommit 이 비활성화된 상태에서 명시적인 commit없이 종료된 세션에 대해서자동 롤백된다.

 

14.3.9 Deadlock Detection and Rollback

InnoDB는 데드락을 자동적으로 감지하여 한개 혹은 다수의 트랜잭션을 롤백시킬 수도 있다. 먼저 규모가 작은 트랜잭션을 골라 롤백시키는데 규모의 기준은 insert혹은 update 혹은 delete 하는 row의 숫자를 보고 판단한다.

InnoDB는 innodb_table_locks = 1 / autocommit = 0 이 설정된 경우 테이블의 락을 감지하게 되며 MySQL 레이어 윗단에서는 row-lelvel의 락 여부도 알 수 있다. 반면에 InnoDB는 MySQL이 설정한 LOCK TABLES구문이나 InnoDB가 아닌 다른 스토리지 엔진에 거린 데드락의 현황에 대해서는 알지 못한다. 이 문제를 해결하기 위해선 innodb_lock_wait_timeout 파라미터를 조절해야 한다.

InnoDB가 트랜잭션에서 롤백을 완료하면 트랜잭션에 의해 잡혔던 모든 락이 해제된다. 그러나 단일 SQL구문이 에러에 의해 롤백되었다면 이 구문에 의한 몇몇의 락은 여전히 걸려있을 수도 있다. 이 현상은 락이 어떤 구문에 의해 걸렸는지 나중에 알 수 없는 형식으로 InnoDB가 락 정보를 저장하기 때문이다.

만약 SELECT 구문이 스토어드 펑션을 call하였는데 해당 펑션 내부에 있는 SQL구문에서 fail이 발생했다면 해당 SQL 구문은 롤백된다. 게다가 만약 롤백이 그 이후에 수행된다면 전체 트랜잭션이 롤백된다.

 

14.3.10 How to Cope with Deadlocks