잠금과 트랜잭션은 서로 비슷한 개념 같지만 사실 잠금은 동시성을 제어하기 위한 기능이고 트랜잭션은 데이터의 정합성을 보장하기 위한 기능이다.
트랜잭션은 작업의 완전성을 보장해주는 것이다. 즉 논리적인 작업 셋을 모두 완벽하게 처리하거나, 처리하지 못할 경우에는 원 상태로 복구해서 작업의 일부만 적용되는 현상(Partial update)이 발생하지 않게 만들어준다.
→ 트랜잭션 4가지 속성 중 Atomicity(원자성)에 해당하며 All or Nothing 형태를 의미
잠금은 여러 커넥션에서 동시에 동일한 자원(레코드나 테이블)을 요청할 경우 순서대로 한 시점에는 하나의 커넥션만 변경할 수 있게 해주는 역할을 한다.
격리 수준이라는 것은 하나의 트랜잭션 내에서 또는 여러 트랜잭션 간의 작업 내용을 어떻게 공유하고 차단할 것인지를 결정하는 레벨을 의미한다.
5.1 트랜잭션
5.1.1 MySQL 에서의 트랜잭션
트랜잭션은 여러 개의 변경 작업을 묶어서 수행하는 경우만 의미 있는 개념은 아니다.
트랜잭션은 하나의 논리적인 작업 셋에 하나의 쿼리가 있거나 두 개 이상의 쿼리가 있거나 관계 없이 논리적인 작업 셋 자체가 100% 적용 되거나, 아무것도 적용되지 않아야 함을 보장 해주는 것이다.
InnoDB 스토리지 엔진은 사용하기 복잡하고 번거롭다고 생각할 수도 있지만, MyISAM 이나 MEMORY 와 같은 트랜잭션을 지원하지 않는 스토리지 엔진의 테이블을 사용할 경우 더 많은 고민거리를 만들어 낸다.
MyISAM 과 InnoDB 스토리지 엔진의 테이블의 트랜잭션 차이를 확인 해보자.
MyISAM 테이블은 PK가 중복이 되어서 에러가 발생되었지만 1,2는 INSERT된 상태로 남아 있다.
MyISAM은 INSERT문 실행시 차례대로 1,2를 저장하고 그 다음 3을 저장 하려는 순간 에러가 발생된 것이다. 이미 Insert 된 1,2는 그대로 두고 3에서 에러가 발생되면서 쿼리가 그대로 종료가 된다.
MyISAM 이러한 현상을 Partial Update 라고 표현하며 데이터 정합성을 맞추는데 많은 어려움을 만든다.
MyISAM 에서 이러한 현상을 방지 하기 위해서 IF..ELSE에 데이터에 대한 확인 및 클렌징 코드(Rollback)까지 준비해야 한다.
하지만 InnoDB는 다음과 같이 간단한 코드로 구현이 가능하다.
try {
start transaction;
insert into tab_a ..;
insert into tab_b ..;
commit;
} catch(exception {
rollback;
}
SQL
복사
5.1.2 주의사항
트랜잭션도 DBMS 커넥션과 유사하게 꼭 필요하고 최소한의 코드에만 적용하는 것이 좋다. 이는 프로그램 코드에서 트랜잭션의 범위를 최소화 하라는 의미이다.
게시판에서 게시물 작성 완료 버튼을 눌렀을때 일련의 처리 로직 중 에서의 간략한 예시
1) 처리시작
→ 데이터베이스 커넥션 생성
→ 트랜잭션 시작
2) 사용자 로그인 여부 확인
3) 사용자의 글쓰기 내용의 오류 여부 확인
4) 첨부로 업로드된 파일 확인 및 저장
5) 사용자 입력 내용을 DBMS에 저장
6) 첨부 파일 정보를 DBMS에 저장
7) 저장된 내용 또는 기타 정보를 DBMS에서 조회
8) 게시물 등록에 대한 알림 메일 발송
9) 알림 메일 발송 이력을 DBMS에 저장
← 트랜잭션 종료(COMMIT)
← 데이터베이스 커넥션 반납
10) 처리 완료
위의 내용에서 트랜잭션 처리에 좋지 않은 영향을 미치는 부분을 살펴보자
•
실제로 DBMS에서 데이터를 저장하는 작업(트랜잭션)은 5번 부터 시작된다. 그렇기 떄문에 2~4번의 절차가 아무리 빨리 처리된다고 해도 DBMS의 트랜잭션에 포함시킬 필요는 없다.
일반적으로 데이터베이스 커넥션은 개수가 제한적이기 때문에 각 단위 프로그램이 커넥션을 소유하는 시간이 길어질수록 사용 가능한 여유 커넥션의 개수는 줄어들 것이다. 그리고 어느 순간에는 각 단위 프로그램에서 커넥션을 기다려야 하는 상황이 발생할 수도 있다.
•
더 큰 위험은 8번 작업이라고 할 수 있다.
메일 전송이나 FTP 파일 전송 작업 또는 네트워크를 통해 원격 서버와 통신하는 등과 같은 작업은 트랜잭션 내에서 제거하는 것이 좋다. 프로그램이 실행되는 동안 메일 서버와 통신할 수 없는 상황이 발생한다면 웹 서버뿐 아니라 DBMS 서버까지 위험해지는 상황이 발생한다.
•
위 처리 절차에는 DBMS의 작업이 크게 4개가 있다.
◦
사용자가 입력한 정보를 저장하는 5번과 6번은 반드시 하나의 트랜잭션으로 묶어야 한다.
◦
7번 작업은 저장된 데이터의 단순 확인 및 조회이므로 트랜잭션에 포함할 필요는 없다. 단순 조회라고 본다면 별도로 트랜잭션을 사용하지 않아도 무방해 보인다.
◦
9번 작업은 작업의 성격이 조금 다르기 때문에 이전 트랜잭션과 함께 묶지 않아도 무방하다.
위 내용을 토대로 다시 설계해보면,
1) 처리시작
2) 사용자의 로그인 여부 확인
3) 사용자의 글쓰기 내용의 오류 발생 여부 확인
4) 첨부로 업로드된 파일 확인 및 저장
→ 데이터베이스 커넥션 생성(또는 커넥션 풀에서 가져오기)
→ 트랜잭션 시작
5) 사용자 입력 내용을 DBMS에 자정
6) 첨부 파일 정보를 DBMS에 저장
← 트랜잭션 종료(COMMIT)
7) 저장된 내용 또는 기타 정보를 DBMS에서 조회
8) 게시물 등록에 대한 알림 메일 발송
→ 트랜잭션 시작
9) 알림 메일 발송이력을 DBMS에 저장
← 트랜잭션 종료(COMMIT)
← 데이터베이스 커넥션 종료(또는 커넥션 풀에 반납)
10) 처리완료
위의 예제가 최적의 트랜잭션 설계는 아닐 수 있으며, 업무의 특성과 구현이 필요한 로직 등에 따라서 트랜잭션 설계는 여러가지 면으로 작성될 수 있다.
기본적으로 생각해야 할 부분으로 애플리케이션 코드에서 데이터베이스의 커넥션을 가지고 있는 범위 그리고 트랜잭션이 활성화 되어 있는 프로그램의 범위는 최소화하고, 네트워크 작업이 있는 경우 반드시 트랜잭션에서 배제해야 한다는 것이다.