주로 사용하는 trasaction isolation 에는 read-commited / repeatable-read 가 있습니다.
그 중 repeatable-read 는 한 트랜잭션에서 수행하는 select 에 대해 항상 동일한 결과값을 보장하는 트랜잭션 레벨인데
이를 위해 repeatable read 에는 두가지 특성이 있습니다.
1) MVCC - 트랜잭션에서 수행한 select 결과값들을 undo 에 백업하고 트랜잭션에 대해 번호를 부여함.
트랜잭션 안에서 실행 된 모든 select 쿼리는 트랜잭션 번호가 작은 트랜잭션에서 변경된 값만 볼 수 있음
2) gap lock , next key lock - 변경 하는 데이터들의 레코드 사이사이에 거는 lock , 갭락 범위 직전, 직후의 레코드에 거는 lock
이번 글에서는 repeatable read 의 gap lock 과 next key lock을 살펴보겠습니다.
-1. data 준비
MariaDB [mysql]> delimiter ;; MariaDB [mysql]> create procedure test.lock_test() -> begin -> declare i int default 1; -> declare j int default 1; -> create table test.lock_test_no_pk ( key int, value varchar(10) ); -> while i < 30 do -> insert into test.lock_test_no_pk values ( i, concat('a0',j ) ); -> set i:=i+1; -> set j:=j+1; -> end while; -> end -> ;; Query OK, 0 rows affected (0.005 sec) MariaDB [(none)]> delimiter ; MariaDB [(none)]> MariaDB [(none)]> call test.lock_test; Query OK, 29 rows affected (1.042 sec)
-2. 테스트 환경 확인
MariaDB [(none)]> select * from test.lock_test_no_pk;
+------+-------+
| num | value |
+------+-------+
| 1 | a01 |
| 2 | a02 |
| 3 | a03 |
| 4 | a04 |
| 5 | a05 |
| 6 | a06 |
| 7 | a07 |
| 8 | a08 |
| 9 | a09 |
| 10 | a010 |
| 11 | a011 |
| 12 | a012 |
| 13 | a013 |
| 14 | a014 |
| 15 | a015 |
| 16 | a016 |
| 17 | a017 |
| 18 | a018 |
| 19 | a019 |
| 20 | a020 |
| 21 | a021 |
| 22 | a022 |
| 23 | a023 |
| 24 | a024 |
| 25 | a025 |
| 26 | a026 |
| 27 | a027 |
| 28 | a028 |
| 29 | a029 |
+------+-------+
29 rows in set (0.001 sec)
MariaDB [(none)]> select @@autocommit; +--------------+ | @@autocommit | +--------------+ | 0 | +--------------+ 1 row in set (0.000 sec) MariaDB [(none)]> select @@tx_isolation; +-----------------+ | @@tx_isolation | +-----------------+ | REPEATABLE-READ | +-----------------+ 1 row in set (0.001 sec) MariaDB [(none)]> select * from mysql.innodb_index_stats where table_name='lock_test_no_pk'; +---------------+-----------------+-----------------+---------------------+--------------+------------+-------------+-----------------------------------+ | database_name | table_name | index_name | last_update | stat_name | stat_value | sample_size | stat_description | +---------------+-----------------+-----------------+---------------------+--------------+------------+-------------+-----------------------------------+ | test | lock_test_no_pk | GEN_CLUST_INDEX | 2019-06-02 12:01:56 | n_diff_pfx01 | 29 | 1 | DB_ROW_ID | | test | lock_test_no_pk | GEN_CLUST_INDEX | 2019-06-02 12:01:56 | n_leaf_pages | 1 | NULL | Number of leaf pages in the index | | test | lock_test_no_pk | GEN_CLUST_INDEX | 2019-06-02 12:01:56 | size | 1 | NULL | Number of pages in the index | +---------------+-----------------+-----------------+---------------------+--------------+------------+-------------+-----------------------------------+ 3 rows in set (0.001 sec)
=> 테이블 생성 시 primary key를 생성하지 않으면 내부적으로 임의의 pk 를 생성함
-3. TEST - PK 없을 때
--- A session ---
MariaDB [(none)]> MariaDB [(none)]> start transaction; Query OK, 0 rows affected (0.016 sec) MariaDB [(none)]> update test.lock_test_no_pk set value = 'test' where num between 10 and 20; Query OK, 11 rows affected (0.003 sec) Rows matched: 11 Changed: 11 Warnings: 0
--- B session ---
MariaDB [(none)]> start transaction; Query OK, 0 rows affected (0.000 sec) MariaDB [(none)]> update test.lock_test_no_pk set value='test2' where num=3; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction MariaDB [(none)]> MariaDB [(none)]> update test.lock_test_no_pk set value='test2' where num=25; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction MariaDB [(none)]> insert into test.lock_test_no_pk values (30,'a030'); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction MariaDB [(none)]> insert into test.lock_test_no_pk values (40,'a040'); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
=> PK 가 없으면 A session 에서 수행한 10~20 까지의 레코드에 대해서만 락이 걸리는 게 아니라 전체 record 들이 락 걸려버림
-4. TEST - PK 있을 때
MariaDB [(none)]> select * from mysql.innodb_index_stats where table_name='lock_test_pk'; +---------------+--------------+------------+---------------------+--------------+------------+-------------+-----------------------------------+ | database_name | table_name | index_name | last_update | stat_name | stat_value | sample_size | stat_description | +---------------+--------------+------------+---------------------+--------------+------------+-------------+-----------------------------------+ | test | lock_test_pk | PRIMARY | 2019-06-02 12:17:13 | n_diff_pfx01 | 29 | 1 | num | | test | lock_test_pk | PRIMARY | 2019-06-02 12:17:13 | n_leaf_pages | 1 | NULL | Number of leaf pages in the index | | test | lock_test_pk | PRIMARY | 2019-06-02 12:17:13 | size | 1 | NULL | Number of pages in the index | +---------------+--------------+------------+---------------------+--------------+------------+-------------+-----------------------------------+ 3 rows in set (0.001 sec)
--- A session ---
MariaDB [(none)]> start transaction; Query OK, 0 rows affected (0.002 sec) MariaDB [(none)]> update test.lock_test_pk set value='test' where num between 10 and 20; Query OK, 11 rows affected (0.001 sec) Rows matched: 11 Changed: 11 Warnings: 0
--- B session ---
MariaDB [(none)]> start transaction; Query OK, 0 rows affected (0.000 sec) MariaDB [(none)]> update test.lock_test_pk set value='test2' where num=3; Query OK, 1 row affected (0.000 sec) Rows matched: 1 Changed: 1 Warnings: 0 => A session 에서 변경한 10~20 레코드들만 락이 걸리기 때문에 PK 없을 때와는 달리 변경 가능 MariaDB [(none)]> update test.lock_test_pk set value='test2' where num=25; Query OK, 1 row affected (0.000 sec) Rows matched: 1 Changed: 1 Warnings: 0 MariaDB [(none)]> update test.lock_test_pk set value='test2' where num=21; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction => 10~20 직후 레코드인 21에는 next key lock MariaDB [(none)]> update test.lock_test_pk set value='test2' where num=15; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction => 15에는 record lock MariaDB [(none)]> insert into test.lock_test_pk values (30,'a030'); Query OK, 1 row affected (0.000 sec)