MySQL의 Transaction Isolation에 대해

MySQL에는 네가지 transaction isolation 레벨이 있습니다.
transaction isolation 에 따라 쿼리의 결과가 달라지고 Lock의 범위가 달라지므로 간단히라도 꼭 알아둬야 하는 개념인데요
이번 글에서는 MySQL의 transaction isolation에 대해 간단히 알아보겠습니다.
repeatable-read 의 gap lock, next key lock 은 다음에………..

READ-UNCOMMITTED

특징

  • lock 잡지 않음
  • dirty read , unrepeatable read, phantom read 발생

테스트

  • session 1
    mysql> set session transaction_isolation='read-uncommitted';
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> begin;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> update tb_test set b='hi' where a=1;
    Query OK, 1 row affected (0.00 sec)
    Rows matched: 1  Changed: 1  Warnings: 0
  • session 2
    mysql>  set session transaction_isolation='read-uncommitted';
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> select * from tb_test;
    +---+----------+
    | a | b        |
    +---+----------+
    | 1 | hi       |
    | 2 | leedubi  |
    | 3 | parkdubi |
    +---+----------+
    3 rows in set (0.00 sec)

=> 다른 세션에서 commit 을 찍지 않아도 변경된 데이터를 조회할 수 있는 dirty read 가 발생함

READ-COMMITTED

특징

  • dirty read 방지
  • unrepeatable readd, phantom read 발생

테스트

  • session 1
    mysql> set session transaction_isolation='read-committed';
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> begin;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> update tb_test set b='hi' where a=1;
    Query OK, 1 row affected (0.00 sec)
    Rows matched: 1  Changed: 1  Warnings: 0
  • session 2
    mysql> set session transaction_isolation='read-committed';
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> begin;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> select * from tb_test;
    +---+----------+
    | a | b        |
    +---+----------+
    | 1 | kimdubi  |
    | 2 | leedubi  |
    | 3 | parkdubi |
    +---+----------+
    3 rows in set (0.00 sec)

=> 다른 세션에서 commit을 찍지 않으면 변경된 데이터가 보이지 않음, dirty read 방지

REPEATABLE-READ

특징

  • dirty read, unrepeatable read 방지
  • phantom read 발생
    • phantom read 를 막기 위해 gap lock, next key lock을 사용함
  • insert ~ select , create table as select ~ 할 때 select 하는 테이블에 lock 걸림

테스트

  • session 1
    mysql> set session transaction_isolation='repeatable-read';
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> begin;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> update tb_test set b='hi' where a=1;
    Query OK, 1 row affected (0.00 sec)
    Rows matched: 1  Changed: 1  Warnings: 0
    
    mysql> commit;
    Query OK, 0 rows affected (0.00 sec)
  • session 2
    mysql> set session transaction_isolation='repeatable-read';
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> begin;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> select * from tb_test;
    +---+----------+
    | a | b        |
    +---+----------+
    | 1 | kimdubi  |
    | 2 | leedubi  |
    | 3 | parkdubi |
    +---+----------+
    3 rows in set (0.00 sec)
    
    mysql> select * from tb_test;
    +---+----------+
    | a | b        |
    +---+----------+
    | 1 | kimdubi  |
    | 2 | leedubi  |
    | 3 | parkdubi |
    +---+----------+
    3 rows in set (0.00 sec)
    
    mysql> select * from tb_test;
    +---+----------+
    | a | b        |
    +---+----------+
    | 1 | kimdubi  |
    | 2 | leedubi  |
    | 3 | parkdubi |
    +---+----------+
    3 rows in set (0.00 sec)

=> 다른 세션에서 commit 을 찍어도, 트랜잭션이 시작했을 때의 snapshot 을 그대로 유지하여 unrepeatable read를 방지함

phantom read 테스트

  • session 1
    mysql> begin;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> insert into tb_test(b) values('choidubi');
    Query OK, 1 row affected (0.00 sec)
    
    mysql> commit;
    Query OK, 0 rows affected (0.00 sec)
  • session 2
    mysql> begin;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> select * from tb_test;
    +---+----------+
    | a | b        |
    +---+----------+
    | 1 | kimdubi  |
    | 2 | leedubi  |
    | 3 | parkdubi |
    +---+----------+
    3 rows in set (0.00 sec)
    
    mysql> update tb_test set b='limdubi' where a=4;
    
    
    
    Query OK, 1 row affected (3.76 sec)
    Rows matched: 1  Changed: 1  Warnings: 0
    
    mysql> select * from tb_test;
    +---+----------+
    | a | b        |
    +---+----------+
    | 1 | kimdubi  |
    | 2 | leedubi  |
    | 3 | parkdubi |
    | 4 | limdubi  |
    +---+----------+
    4 rows in set (0.00 sec)

phantom read 테스트 - next key lock

  • session 1
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from tb_test for update;
+---+----------+
| a | b        |
+---+----------+
| 1 | kimdubi  |
| 2 | leedubi  |
| 3 | parkdubi |
+---+----------+
3 rows in set (0.00 sec)
  • session 2
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into tb_test(b) values('choidubi');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
  • lock 조회
mysql> SELECT straight_join         dl.THREAD_ID        , est.SQL_TEXT        , dl.OBJECT_SCHEMA        , dl.OBJECT_NAME        , dl.INDEX_NAME        , dl.LOCK_TYPE        , dl.LOCK_MODE        , dl.LOCK_STATUS        , dl.LOCK_DATA      FROM        performance_schema.data_locks dl inner join performance_schema.events_statements_current est on dl.THREAD_ID = est.THREAD_ID      ORDER BY est.TIMER_START,dl.OBJECT_INSTANCE_BEGIN;
+-----------+-------------------------------------------+---------------+-------------+------------+-----------+--------------------+-------------+------------------------+
| THREAD_ID | SQL_TEXT                                  | OBJECT_SCHEMA | OBJECT_NAME | INDEX_NAME | LOCK_TYPE | LOCK_MODE          | LOCK_STATUS | LOCK_DATA              |
+-----------+-------------------------------------------+---------------+-------------+------------+-----------+--------------------+-------------+------------------------+
|   1532790 | select * from tb_test for update          | test2         | tb_test     | PRIMARY    | RECORD    | X                  | GRANTED     | supremum pseudo-record |
|   1532790 | select * from tb_test for update          | test2         | tb_test     | PRIMARY    | RECORD    | X                  | GRANTED     | 1                      |
|   1532790 | select * from tb_test for update          | test2         | tb_test     | PRIMARY    | RECORD    | X                  | GRANTED     | 2                      |
|   1532790 | select * from tb_test for update          | test2         | tb_test     | PRIMARY    | RECORD    | X                  | GRANTED     | 3                      |
|   1532790 | select * from tb_test for update          | test2         | tb_test     | NULL       | TABLE     | IX                 | GRANTED     | NULL                   |
|   1532788 | insert into tb_test(b) values('choidubi') | test2         | tb_test     | PRIMARY    | RECORD    | X,INSERT_INTENTION | WAITING     | supremum pseudo-record |
|   1532788 | insert into tb_test(b) values('choidubi') | test2         | tb_test     | NULL       | TABLE     | IX                 | GRANTED     | NULL                   |
+-----------+-------------------------------------------+---------------+-------------+------------+-----------+--------------------+-------------+------------------------+
7 rows in set (0.00 sec)

=> repeatable read 일때 select ~ for update 사용 시 , gap lock, next key lock으로 인해 팬텀리드가 방지됨

serializable

특징

  • dirty read, unrepeatable read, phantom read 없음
  • 트랜잭션 수행 시 select 문에 SELECT FOR SHARE LOCK을 걸어 다른 세션에서 DML 수행 불가

테스트

  • session 1
mysql>  set session transaction_isolation='serializable';
Query OK, 0 rows affected (0.00 sec)

mysql> select * from tb_test;
+---+----------+
| a | b        |
+---+----------+
| 1 | kimdubi  |
| 2 | leedubi  |
| 3 | parkdubi |
| 4 | limdubi  |
+---+----------+
4 rows in set (0.00 sec)
  • session 2
mysql>  set session transaction_isolation='serializable';
Query OK, 0 rows affected (0.00 sec)

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

mysql> insert into tb_test(b) values('choidubi');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction