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
tx1mysql> select * from test.students;
+----+-------+------------------+
| id | name | major |
+----+-------+------------------+
| 1 | Tom | Computer Science |
| 2 | Amy | Math |
| 3 | Steve | Art |
| 4 | John | Movie |
+----+-------+------------------+
tx2mysql> 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
tx1mysql> select * from test.students;
+----+-------+------------------+
| id | name | major |
+----+-------+------------------+
| 1 | Tom | Computer Science |
| 2 | Amy | Math |
| 3 | Steve | Art |
| 4 | John | Movie |
+----+-------+------------------+
tx2mysql> select * from test.students;
+----+-------+------------------+
| id | name | major |
+----+-------+------------------+
| 1 | Tom | Computer Science |
| 2 | Amy | Mechanical |
| 3 | Steve | Art |
| 4 | John | Movie |
+----+-------+------------------+
tx1에서 commit을 실행한다.
결과
tx1, tx2mysql> 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을 진행한다.tx1mysql> select * from test.students;
+----+-------+------------------+
| id | name | major |
+----+-------+------------------+
| 1 | Tom | Computer Science |
| 2 | Amy | Mechanical |
| 3 | Steve | Art |
| 4 | John | Movie |
| 5 | Emma | Music |
+----+-------+------------------+
tx2mysql> 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 |
+----+-------+------------------+
tx2mysql> 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으로 데이터는 보호하지만, 동시에 많은 요청을 처리하는데는 지연을 발생시킨다.