▶ 데이터의 무결성과 일관성
무결성 : 데이터의 정확성, 일관성, 유효성이 유지
일관성 : 원인과 결과의 의미가 연속적으로 보장되어 변하지 않는 상태
'부모님의 계좌에서 자식의 계좌로 백 만원을 계좌이체'하는 작업을 처리한다고 가정 해보자.
1. 부모님, 자식의 계좌의 잔액을 확인
2. 부모님 계좌에서 (잔액 - 백만원)으로 업데이트
3. 자식 계좌에 (잔액 + 백만원)으로 업데이트
4. 점검
만약 2번의 UPDATE 문이 실행되고 시스템에 장애가 발생하여 3번의 UPDATE 문이 실행되지 않는다면?
부모님의 백 만원은 사라져버리게 되는 모순된 상황이 발생하게 된다.
따라서 트랜잭션은 다음과 같은 연산 중 하나가 실행되어야 종료된다.
한 트랜잭션의 모든 쿼리문을 성공적으로 수행 -> 데이터를 최종적으로 데이터베이스에 영구적으로 반영 == commit 연산
작업 중 문제가 발생하여 한 연산이라도 실패하면 트랜잭션의 처리과정에서 발생한 변경사항을 취소 == rollback 연산
InnoDB 스토리지 엔진은 트랜잭션을 지원하고 MyISAM 이나 MEMORY 와 같은 스토리지 엔진은 트랜잭션을 지원하지 않는다고 한다. 두 스토리지 엔진을 비교해보자.
// MyISAM 테이블 생성 및 PK insert
mysql> create table tb_tab_myisam(col int primary key) ENGINE=MyISAM;
mysql> insert into tb_tab_myisam values (3);
// InnoDB 테이블 생성 및 PK insert
mysql> create table tb_tab_innodb(col int primary key) ENGINE=InnoDB;
mysql> insert into tb_tab_innodb values (3);
mysql> insert into tb_tab_myisam values(1),(2),(3);
Error Code: 1062. Duplicate entry '3' for key 'tb_tab_myisam.PRIMARY'
mysql> insert into tb_tab_innodb values(1),(2),(3);
Error Code: 1062. Duplicate entry '3' for key 'tb_tab_innodb.PRIMARY'
-- MyISAM
mysql> select * from tb_tab_myisam;
+-----+
| col |
+-----+
| 1 |
| 2 |
| 3 |
+-----+
-- InnoDB
mysql> select * from tb_tab_innodb;
+-----+
| col |
+-----+
| 3 |
+-----+
Insert 과정에서 2개의 구문 모두 1062 에러(PK 중복) 으로 에러가 발생
But 테이블을 조회하면 결과가 다르다.
→ MyISAM 테이블은 1,2가 INSERT된 상태, 즉 INSERT문 실행시 1,2를 저장하고 3에서 에러가 발생하여 쿼리 종료 == Partial Update
→ 데이터의 정합성(어떤 데이터들이 값이 서로 일치하는 상태)을 맞추는데 어렵게 된다.
→ Partial Update_을 방지 하기 위해서 조건문으로 데이터에 대한 확인 및 클렌징 코드(Rollback) 까지를 준비..
InnoDB는 다음 로직이면 충분하다.
try {
start transaction;
insert into tab_a ..;
insert into tab_b ..;
commit;
} catch(exception {
rollback;
}
▶ DB에서 보존 되어야 하는 일관성
DB의 상태, DB 내의 계층 관계, 컬럼의 속성, 제약조건 등등
▶ Isolation level(격리수준)
- Isolation level(격리수준) 설정에 따라 독립성의 높낮이 및 트랜잭션 진행간 발생될 수 있는 상황들도 달라지게 됩니다.
- 각 DBMS 마다 기본으로 채택하고 있는 Isolation level(격리수준) 이 각각 다르며 MySQL 에서는 REPEATABLE READ 을 기본 값으로 하고 있습니다.
- Isolation level 이 높다면?
-> 트랜잭션의 격리성/독립성은 높아짐
-> 동시 처리 성능이 떨어지며 시스템 자원도 많이 사용
-- GLOBAL LOCK
FLUSH TABLES WITH READ LOCK;
-- UNLOCK
UNLOCK TABLES;
-- BACKUP LOCK -> 새로운 백업 잠금
LOCK INSTANCE FOR BACKUP;
-- UNLOCK
UNLOCK INSTANCE;
-- TABLE LOCK
LOCK TABLES table_name [ READ | WRITE ]
-- UNLOCK
UNLOCK TABLES;
-- jade-1 이라는 문자열로 잠금을 획득하며
-- 이미 잠금이 있다면 5초간 대기
mysql> SELECT GET_LOCK('jade-1',5);
+----------------------+
| GET_LOCK('jade-1',5) |
+----------------------+
| 1 |
+----------------------+
-- 문자열 'jade-1' 에 대해서 획득 가능한지를 확인
mysql> SELECT IS_FREE_LOCK('jade-1');
+------------------------+
| IS_FREE_LOCK('jade-1') |
+------------------------+
| 0 |
+------------------------+
-- 문자열 jade-1 이 잠금이 설정되어 있는지 확인
mysql> SELECT IS_USED_LOCK('jade-1');
+------------------------+
| IS_USED_LOCK('jade-1') |
+------------------------+
| 8 |
+------------------------+
-- 위 3개 함수 모두 정상적으로 락을 획득하거나 해제한 경우에 1을, 아니면 0을 반환한다.
-- connection id 확인
mysql> SELECT CONNECTION_ID();
+-----------------+
| CONNECTION_ID() |
+-----------------+
| 8 |
+-----------------+
-- 잠금해제
mysql> SELECT RELEASE_LOCK('jade-1');
+------------------------+
| RELEASE_LOCK('jade-1') |
+------------------------+
| 1 |
+------------------------+
-- 모든 문자열에 대한 잠금을 해제한다. 해제된 잠금 수를 반환한다.
SELECT RELEASE_ALL_LOCKS();
-- 배치 프로그램에서 별도의 임시 테이블에 서비스용 랭킹 데이터 생성 후 기존 테이블을 백업하는 경우
-- 아래 구문 실행 시 메타데이터 락을 자동으로 획득한다.
RENAME TABLE rank TO rank_backup, rank_new TO rank;
-- 트랜잭션을 명시적으로 시작 후에 데이터를 조회
-- 다른 세션에서 테이블의 구조를 변경하게 되면 Metadata Lock으로 대기
-- Session 1
select CONNECTION_ID();
+-----------------+
| CONNECTION_ID() |
+-----------------+
| 4 |
+-----------------+
<-- Session 1의 Process ID 는 4 로 확인됨
-- 트랜잭션 시작
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from user_info;
+----+------+
| id | name |
+----+------+
| 1 | jade |
| 2 | Tom |
+----+------+
2 rows in set (0.00 sec)
-- Session 2 : 컬럼 추가 시도
mysql> alter table user_info add column col2 varchar(100);
<!!--- 락 획득 실패 및 락에 의해 대기중
-- Session 1 : Full processlist
mysql> show full processlist;
+----+------------+------+------+---------------------------------+----------------------------------------------------+
| Id | User | db | Time | State | Info |
+----+------------+------+------+---------------------------------+----------------------------------------------------+
| 2 | system user| NULL | 1321 | Connecting to master | NULL |
| 4 | root | npm | 0 | starting | show full processlist |
| 8 | root | npm | 10 | Waiting for table metadata lock | alter table user_info add column col2 varchar(100) |
+----+------------+------+------+---------------------------------+----------------------------------------------------+
<!!-- Metadata lock 에 의해 Session 2가 대기 하는 상황
▶ 비관적 동시성 제어(PCC, Pessimistic Concurrency Control)
비관적 락이라고도 하며 트랜잭션이 충돌하는 가정하에 잠금을 거는 방식
일반적으로 Shared Lock, Exclusive Lock을 통해 이를 구현한다.
▶ 세 가지 부정합의 문제
- 데이터베이스의 격리 수준을 이야기하면 항상 같이 언급되는 것으로 격리 수준의 레벨에 따라서 발생 여부가 달라 진다
- DIRTY READ
: 한 세션에서 진행 중인 트랜잭션이 완료되지 않았는데도 다른 세션에서 진행 중인 트랜잭션에서 해당 변경된 데이터를 볼 수 있는 현상
: 즉 commit되지 않은 정보를 볼 수 있는 현상- NON-REPEATABLE READ
: 한 트랜잭션에서 같은 쿼리를 두번 실행 했을 때 다른 값이 나오는 현상
: 특정 데이터에 대한 수정이 발생하여 나타나는 현상이다.- PHANTOM READ
: 다른 트랜잭션에서 수행한 변경 작업에 의해 레코드(테이블의 한 행)가 보였다가 안 보였다가 하는 현상
: 결과 범위에 속하지 않은 레코드가 외부 작업에 의해 있을 수도 있고 없어질 수도 있다.
: 한 트랜잭션 안에서 첫 번째 쿼리 수행 결과와 두 번째 쿼리 수행 결과가 다른 것, 이때 외부에 동시에 실행중인 트랜잭션의 INSERT 작업에 의해 발생하는 현상
: 이를 방지하기 위해 쓰기 잠금을 걸어야 한다.
: 트랜잭션-1이 Commit한 이후 아직 끝나지 않는 트랜잭션-2가 다시 테이블 값을 읽으면 값이 BUSAN에서 JEJU로 변경됨을 알 수 있다.
: 하나의 트랜잭션내(BEGIN을 하여 트랜잭션-2를 시작한 이후)에서 똑같은 SELECT 쿼리를 실행했을 때는 항상 같은 결과를 가져와야 하는 REPEATABLE READ의 정합성에 어긋난다.
: 이러한 문제는 주로 입금, 출금 처리가 진행되는 금전적인 처리에서 주로 발생한다.
(ex. 만약 입출금 처리가 계속 진행될 때 다른 트랜잭션에서 오늘의 입금 총합을 조회한다고 하면 REPEATABLE READ가 보장되지 않아 해당 쿼리가 실행될 때마다 총합은 다른 결과가 나옴)
MySQL의 InnoDB 스토리지 엔진에서 기본적으로 사용되는 격리 수준
NON-REPEATABLE READ 부정합이 발생하지 않는다.
MVCC(Multi Version Concurrency Control)
: 트랜잭션이 COMMIT을 하기 전에 다른 트랜잭션에서 해당 데이터를 조회시 Undo 로그 파일을 참조하여 이전 값을 보여주는 것
: InnoDB 스토리지 엔진은 트랜잭션이 Rollback될 경우를 대비하여 Undo 공간에 백업해두고 실제 레코드 값을 변경한다.
: Undo 영역에 백업된 이전 데이터를 이용해 동일 트랜잭션 내에서 동일한 결과를 계속 보여 줄수 있게 보장
MySQL에서는 트랜잭션마다 트랜잭션 ID를 부여하여 트랜잭션 ID보다 작은 트랜잭션 번호에서 변경한 것만 읽게 된다.
백업된 데이터는 InnoDB 스토리지 엔진이 불필요하다고 판단하는 시점에 주기적으로 삭제한다. 이때 MVCC를 보장하기 위해서 실행 중인 트랜잭션 가운데 가장 오래된 트랜잭션 번호 보다 앞선 Undo 영역의 데이터는 삭제 할 수 없다.
: Undo에 백업된 레코드가 많아지면 MySQL 서버의 처리 성능이 떨어질 수 있다.
→ 한 사용자가 BEGIN 으로 트랜잭션을 시작하고 조회하고 장시간 트랜잭션을 종료 하지 않았다면 Undo 영역이 백업 된 데이터로 인하여 무한정 커질수도 있기 때문
lock에 대해 코드와 함께 더 수정
헤시넷 위키_트랜잭션
블로그_트랜잭션과 잠금
블로그2_트랜잭션과 잠금
블로그3_트랜잭션의 격리 수준
블로그4_isolation level
블로그4_MySQL의 잠금
블로그5_InnoDB 스토리지 엔진 잠금
블로그6_Lock
유튜브_트랜잭션
유튜브2_트랜잭션
도움 많이 되었습니다!