TCL(트랜잭션 컨트롤 랭귀지)
트랜잭션이란? 작업의 완전성을 보장해주기 위해 사용되는 개념이다. 특정한 작업을 전부 처리하거나, 전부 실패하게 만들어 데이터의 일관성을 보장해준다.
1) A계좌에서 1000원을 차감한다.
2) B계좌에 1000원이 추가된다.
1번 작업 이후 2번 작업을 수행하던 중 에러가 발생되면 안 된다.
결국, 트랜잭션을 이용한다면 사용자가 항상 실행을 완료하도록 구성할 수 있게 되고, 실행을 중단할 만한 치명적인 오류가 발생해도, DB에 피해가 가지 않게 할 수 있다.
트랜잭션의 특징(ACID)
1)원자성: 전부 성공하거나, 전부 실패(나눠질 수 없는 단일 작업)
2)일관성: 잘못됐으면 다 잘못되어야한다.
3)격리성(고립성): 트랜잭션이 실행 중이면 다른 트랜잭션이 들어와서는 안된다.
4)지속성: 커밋됐으면, DB에는 영구저장되어야한다.
5)동시성: 동시에 여러 트랜잭션이 동일한 데이터에 접근할 때는 데이터의 일관성을 유지하기 어려울 수 있다.
EX) 동시에 계좌에서 출금할 경우
동시성을 해결하기 위해, 자원을 사용하는 하나의 클라이언트만 해당 자원을 점유할 수 있도록 하여, 다른 사용자가 접근할 수 없도록 만들어 자원을 공유하는 원인을 제거하면 된다. 락이라고 한다.
락의 종류
# 트랜잭션을 시작합니다.
START TRANSACTION;
# SPARTA 테이블을 조회할 때, 해당 데이터들에 공유 락을 설정합니다.
SELECT * FROM SPARTA LOCK IN SHARE MODE;
# 트랜잭션을 시작합니다.
START TRANSACTION;
# SPARTA 테이블을 조회할 때, 해당 데이터들에 배타 락을 설정합니다.
SELECT * FROM SPARTA FOR UPDATE;
락킹 수준(Locking Level)
# 글로벌 락을 획득합니다.
# MySQL 서버에 존재하는 모든 테이블에 락을 겁니다.
FLUSH TABLES WITH READ LOCK;
# SPARTA 테이블에 테이블 락을 설정합니다.
LOCK TABLES SPARTA READ;
# sparta_name 문자열을 획득합니다.
# 만약, 10초 동안 획득 하지 못한다면, NULL을 반환합니다.
SELECT GET_LOCK('sparta_name', 10);
# 테이블 구조를 변경할 때, MySQL은 내부적으로 메타데이터 락을 설정합니다.
ALTER TABLE SPARTA ADD COLUMN Age Int;
데드락
잘못된 락 설정을 하게 될 경우 모든 API가 동작하지 않는 교착 상태(Dead Lock)가 발생하게 된다. 그러면 프로그램이 멈춘다.
트랜잭션에 서로 락을 걸었는데, 다른 트랜잭션을 사용할 때까지 트랜잭션을 점유하고 있으면, 교착상태가 된다.
락의 수준을 명확하게 이해하고, 락의 수준을 설정하여 트랜잭션을 구성해야한다.
트랜잭션의 격리 수준
여러 트랜잭션이 동시에 처리될 때 다른 트란잭션에서 변경 및 조회하는 데이터를 읽을 수 있도록 허용하거나 거부하는 것을 결정하기 위해 사용하는 것
READ UNCOMMITTED
1) 커밋 되지 않은 읽기를 허용하는 격리 수준
2) 가장 낮은 수준의 격리수준, 락을 걸지 않아 동시성이 높지만 일관성이 쉽게 깨진다.
READ COMMITTED
1) 커밋 된 읽기만 허용, SELECT문을 실행할 때 공유락을 건다.
2) 다른 트랜잭션이 데이터를 수정하고 있는 중에 데이터를 읽을 수 없어 커밋되지 않은 읽기현상이 발생하지 않는다.
REPEATABLE READ
1) 읽기를 마치더라도 공유락을 풀지 않으며, 트랜잭션이 완전히 종료될 때까지 락을 유지한다.
2) 공유락이 걸린 상태에서 데이터를 수정하는 것은 불가능하지만, 데이터를 삽입하는 것이 가능해진다. 그로인해 팬텀 읽기가 발생할 수도 있다.
3) 팬텀 읽기란?
트랜잭션을 수행하던 중 다른 트랜잭션에 의해 삭제된 데이터를 팬텀행이라고 한다. 여기서, 팬텀행에 해당하는 데이터를 읽는 것
SERIALIZABLE
1) 데이터를 읽는 동안 다른 트랜잭션이 해당 데이터를 읽거나 삽입할 수 없고, 새로운 데이터를 추가하는 것 또한 불가능하다.
2) 가장 높은 수준의 격리 수준이므로, 동시성이 떨어지는 문제점이 존재한다.
트랜잭션 SQL
-- 트랜잭션을 시작합니다.
START TRANSACTION;
-- 성공시 작업 내역을 DB에 반영합니다.
COMMIT;
-- 실패시 START TRANSACTION이 실행되기 전 상태로 작업 내역을 취소합니다.
ROLLBACK;
-- SPARTA 테이블을 생성합니다.
CREATE TABLE IF NOT EXISTS SPARTA
(
spartaId INT(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
spartaName VARCHAR(255) NOT NULL,
spartaAddress VARCHAR(255) NOT NULL
);
-- 1번째 트랜잭션을 실행합니다.
START TRANSACTION;
-- SPARTA 테이블에 더미 데이터 3개를 삽입합니다.
INSERT INTO SPARTA (spartaName, spartaAddress)
VALUES ('SPARTA1', 'SEOUL'),
('SPARTA2', 'BUSAN'),
('SPARTA3', 'DAEGU');
-- 1번째 트랜잭션을 DB에 적용합니다.
COMMIT;
-- 2번째 트랜잭션을 실행합니다.
START TRANSACTION;
-- SPARTA 테이블에 더미 데이터 3개를 삽입합니다.
INSERT INTO SPARTA (spartaName, spartaAddress)
VALUES ('SPARTA4', 'SEOUL'),
('SPARTA5', 'BUSAN'),
('SPARTA6', 'DAEGU');
-- 2번째 트랜잭션을 롤백합니다.
ROLLBACK;
-- 테이블의 상태를 확인합니다.
SELECT * FROM SPARTA;