START TRANSACTION;
# 쿼리
COMMIT;
#또는
ROLLBACK;
데이터베이스 트랜잭션이 안전하게 수행된다는 것을 보장하기 위한 성질을 가르키는 약어이다.
: 트랜잭션은 DB에 모두 반영되거나, 전혀 반영되지 않아야 한다.
(= 완료되지 않은 트랜잭션의 중간 상태를 DB에 반영해서는 안된다.)
: 트랜잭션 작업처리결과는 항상 일관성 있어야 한다.
(= 데이터베이스는 항상 일관된 상태로 유지되어야 한다.)
: 둘 이상의 트랜잭션이 동시 실행되고 있을 때, 어떤 트랜잭션도 다른 트랜잭션 연산에 끼어들 수 없다.
(= 각각의 트랜잭션은 서로 간섭 없이 독립적으로 이뤄져야한다.)
: 트랜잭션이 성공적으로 완료되었으면 결과는 영구 반영되어야 한다.
SHOW VARIABLES WHERE VARIABLE_NAME='transaction_isolation';
InnoDB 스토리지 엔진에서 PHANTOM READ 해결
InnoDB 스토리지 엔진은 레코드 락과 갭 락을 합친 넥스트 키 락을 사용한다. t 테이블에 c1 = 13 , c = 17 인 두 레코드가 있다고 가정하자. 이때 SELECT c1 FROM t WHERE c1 BETWEEN 10 AND 20 FOR UPDATE 쿼리를 수행하면, 10 <= c1 <= 12, 14 <= c1 <= 16, 18 <= c1 <= 20 인 영역은 전부 갭 락에 의해 락이 걸려서 해당 영역에 레코드를 삽입할 수 없다. 또한 c = 13, c = 17인 영역도 레코드 락에 의해 해당 영역에 레코드를 삽입할 수 없다. 참고로 INSERT 외에 UPDATE, DELETE 쿼리도 마찬가지이다.
이러한 방식으로 InnoDB 스토리지 엔진은 넥스트 키 락을 이용하여 PHANTOM READ 문제를 해결한다.
CREATE TABLE IF NOT EXISTS `students` (
id int(6) AUTO_INCREMENT,
name varchar(255) NOT NULL,
major varchar(255) NOT NULL,
PRIMARY KEY (id)
);
show table status;
$ mysql -u사용자ID -p
테스트를 진행하기 전의 데이터 상태는 아래와 같다.
mysql> select * from test.students;
+----+-------+------------------+
| id | name | major |
+----+-------+------------------+
| 1 | Tom | Computer Science |
| 2 | Amy | Mechanical |
| 3 | Steve | Art |
| 4 | John | Movie |
+----+-------+------------------+
SET @@session.transaction_isolation = 'READ-UNCOMMITTED';
start transaction;
tx1
에서 아래와 같이 데이터를 변경하되, commit은 실행하지 않는다.mysql> update students set major = 'Math' where id = 2;
Query OK, 1 row affected (0.03 sec)
Rows matched: 1 Changed: 1 Warnings: 0
tx1
mysql> select * from test.students;
+----+-------+------------------+
| id | name | major |
+----+-------+------------------+
| 1 | Tom | Computer Science |
| 2 | Amy | Math |
| 3 | Steve | Art |
| 4 | John | Movie |
+----+-------+------------------+
tx2
mysql> select * from test.students;
+----+-------+------------------+
| id | name | major |
+----+-------+------------------+
| 1 | Tom | Computer Science |
| 2 | Amy | Math |
| 3 | Steve | Art |
| 4 | John | Movie |
+----+-------+------------------+
tx1
에서 rollback을 하느 순간, tx2
에서도 반영이 되지만, tx2
에서 데이터를 읽어서 어떤 비즈니스 로직을 처리하고자 했다면 문제를 일으킨다.테스트를 진행하기 전의 데이터 상태는 아래와 같다.
mysql> select * from test.students;
+----+-------+------------------+
| id | name | major |
+----+-------+------------------+
| 1 | Tom | Computer Science |
| 2 | Amy | Mechanical |
| 3 | Steve | Art |
| 4 | John | Movie |
+----+-------+------------------+
SET @@session.transaction_isolation = 'READ-COMMITTED';
start transaction;
tx1
에서 아래와 같이 데이터를 변경하되, commit은 실행하지 않는다.mysql> update students set major = 'Math' where id = 2;
Query OK, 1 row affected (0.03 sec)
Rows matched: 1 Changed: 1 Warnings: 0
tx1
mysql> select * from test.students;
+----+-------+------------------+
| id | name | major |
+----+-------+------------------+
| 1 | Tom | Computer Science |
| 2 | Amy | Math |
| 3 | Steve | Art |
| 4 | John | Movie |
+----+-------+------------------+
tx2
mysql> select * from test.students;
+----+-------+------------------+
| id | name | major |
+----+-------+------------------+
| 1 | Tom | Computer Science |
| 2 | Amy | Mechanical |
| 3 | Steve | Art |
| 4 | John | Movie |
+----+-------+------------------+
tx1
에서 commit을 실행한다.
결과
tx1
, tx2
mysql> select * from test.students;
+----+-------+------------------+
| id | name | major |
+----+-------+------------------+
| 1 | Tom | Computer Science |
| 2 | Amy | Math |
| 3 | Steve | Art |
| 4 | John | Movie |
+----+-------+------------------+
tx2
에서 같은 select가 다른 결과를 보여줄 수 있는 문제는 해결되지 않았다.테스트를 진행하기 전의 데이터 상태는 아래와 같다.
mysql> select * from test.students;
+----+-------+------------------+
| id | name | major |
+----+-------+------------------+
| 1 | Tom | Computer Science |
| 2 | Amy | Mechanical |
| 3 | Steve | Art |
| 4 | John | Movie |
+----+-------+------------------+
SET @@session.transaction_isolation = 'REPEATABLE-READ';
start transaction;
tx1
에서 하나의 레코드를 추가하고, commit은 하기 전이라면 tx1
에만 해당 데이터가 나타난다.mysql> select * from test.students;
+----+-------+------------------+
| id | name | major |
+----+-------+------------------+
| 1 | Tom | Computer Science |
| 2 | Amy | Mechanical |
| 3 | Steve | Art |
| 4 | John | Movie |
| 5 | Emma | Music |
+----+-------+------------------+
tx1
commit을 진행한다.tx1
mysql> select * from test.students;
+----+-------+------------------+
| id | name | major |
+----+-------+------------------+
| 1 | Tom | Computer Science |
| 2 | Amy | Mechanical |
| 3 | Steve | Art |
| 4 | John | Movie |
| 5 | Emma | Music |
+----+-------+------------------+
tx2
mysql> select * from test.students;
+----+-------+------------------+
| id | name | major |
+----+-------+------------------+
| 1 | Tom | Computer Science |
| 2 | Amy | Mechanical |
| 3 | Steve | Art |
| 4 | John | Movie |
+----+-------+------------------+
테스트를 진행하기 전의 데이터 상태는 아래와 같다.
mysql> select * from test.students;
+----+-------+------------------+
| id | name | major |
+----+-------+------------------+
| 1 | Tom | Computer Science |
| 2 | Amy | Mechanical |
| 3 | Steve | Art |
| 4 | John | Movie |
+----+-------+------------------+
SET @@session.transaction_isolation = 'SERIALIZABLE';
start transaction;
mysql> select * from test.students;
+----+-------+------------------+
| id | name | major |
+----+-------+------------------+
| 1 | Tom | Computer Science |
| 2 | Amy | Mechanical |
| 3 | Steve | Art |
| 4 | John | Movie |
| 5 | Emma | Music |
+----+-------+------------------+
tx2
mysql> select * from test.students;
+----+-------+------------------+
| id | name | major |
+----+-------+------------------+
| 1 | Tom | Computer Science |
| 2 | Amy | Mechanical |
| 3 | Steve | Art |
| 4 | John | Movie |
+----+-------+------------------+
tx2
에서 임의의 레코드를 업데이트 시도해본다.mysql> update students set major = 'Music' where id = 2;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
tx1
에서 먼저 잡은 lock으로 인해 일정시간이 경과한 후에, 에러가 발생해 버린다. tx2
에서 timeout이 발생하기 전에 tx1
에서 commit 이나 rollback이 수행되었다면 tx2
의 명령은 정상실행된다. lock으로 데이터는 보호하지만, 동시에 많은 요청을 처리하는데는 지연을 발생시킨다.