데이터베이스에서는 트랜잭션이라는 개념을 지원하는데 트랜잭션이란 말 그대로 번역하면 거래라는 뜻이다. DB에서 하나의 거래를 안전하게 처리하도록 보장해주는 것을 뜻한다.
에를 들어 작업이 여러개인 거래가 있는데 이중 몇개는 성공하고 몇개는 실패하면 이는 큰 문제가 될 것이다. 이러한 일을 방지하기 위해 DB에서 제공하는 트랜잭션 기능을 이용하면 문제 발생시 롤백(Rollback)하여 거래 이전으로 돌아갈 수 있고 작업이 정상 작동하여 DB에 반영(Commit)할 수도 있다.
트랜잭션은 ACID를 보장해야 한다.
어플리케이션 서버에서 DB 서버간의 커넥션을 생성하면 DB 서버는 내부에 세션이라는 것을 만든다. 그리고 해당 커넥션을 통한 모든 요청이 이 세션을 통해 실행된다.
세션은 트랜잭션을 시작하고 트랜잭션이 종료한다. 사용자가 커넥션을 닫거나 또는 DBA(DB 관리자)가 세션을 강제로 종료하면 세션은 종료된다.
데이터를 변경하고 저장하는 것을 commit이라하고 변경한 데이터를 다시 원래 상태로 돌이키는 것을 rollback이라 한다.
만약 데이터를 변경하고 commit 명령을 실행하지 않으면 그 데이터는 임시로 변경된 것으로 저장되지 않는다. 등록, 삭제, 수정 모두 위와 같은 원리이다.
커밋하지 않는 데이터를 다른 곳에서 조회하면 변경된 데이터가 저장된다는 것을 가정하고 로직을 실행할 것이다. 이는 위험한 문제가 될 수 있기 때문에 커밋이 되지 않은 데이터는 다른 곳에서 조회할 수 있어서는 안된다.
자동 커밋으로 설정이 되어 있으면 쿼리 실행 후 자동으로 커밋을 실행해준다. 그러나 이는 트랜잭션 기능을 제대로 사용할 수 없기 때문에 수동 커밋으로 설정하는 것이 트랜잭션을 시작한다라 할 수 있다.
set autocommit false; //수동 커밋 모드 설정
insert into ~
update ~
commit; // 수동 커밋
만약 오토 커밋으로 작업을 하다 보면 계좌 이체를 예를 들면 B가 A에게 1000원을 보내면 A의 돈이 1000 증가하는 작업과 B의 돈이 1000원 줄어드는 작업 둘다 실행되어야 하는데 오토 커밋은 A의 돈을 증가시키고 커밋을 하기 때문에 B의 돈이 줄어들지 않는 오류가 생길 수 있어 큰 문제가 될 수 있다.
트랜잭션을 시작하고 아직 커밋을 수행하지 않았는데 다른 세션에서 같은 데이터를 수정하게 되면 여러문제가 발생한다. 원자성이 깨지는 문제와 서로 다른 세션에서 한 데이터를 수정하기 때문에 잘못 수정하는 문제가 생긴다.
이러한 문제를 해결하기 위해 DB에서는 락이라는 기능을 제공하는데, 세션이 트랜잭션을 시작하고 데이터를 수정하는 동안에는 커밋이나 롤백 전까지 다른 세션에서 해당 데이터를 수정할 수 없게 한다.
세션 1이 어떤 데이터를 수정하기 위해 락을 획득하면 세션 2는 같은 데이터를 수정하고 싶어도 락이 아직 반환되지 않았음으로 수정이 불가능하다. 따라서 세션2는 락을 얻기까지 기다리게 되는데 계속 기다리는 것이 아니라 DB에 설정된 시간동안 기다리다가 시간이 초과되면 타임아웃 오류를 발생시킨다.
DB마다 다르지만 일반적으로 조회에서는 락을 사용하지 않는다. 그러나 조회할 때도 락을 획득하는 경우가 있는데 이럴 경우 select for update
구문을 사용하면 된다. 이렇게 하면 락을 가져가버리기 때문에 다른 세션이 해당 데이터를 수정할 수 없다.
조회 시점에 락이 필요한 경우는 트랜잭션이 종료될 때까지 다른 세션이 해당 데이터를 수정할 수 없게 할 때 사용된다. 매우 중요한 계산이나 바꾸면 안될 데이터를 보존하기 위해 사용된다.
트랜잭션은 비즈니스 로직이 있는 서비스 계층에서 시작해야 한다. 비즈니스 로직이 잘못되면 해당 비즈니스 로직으로 인해 문제가 되는 부분을 롤백해야 하기 때문이다.
애플리케이션에서 DB 트랜잭션을 사용하려면 트랜잭션을 사용하는 동안 같은 커넥션을 유지해야한다. 이는 커넥션을 파라미터로 전달해서 같은 커넥션이 사용되도록 하는 방법을 통해 커넥션을 유지할 수 있다.
그러나 이렇게 서비스 계층에서 DB 트랜잭션을 적용하는 코드가 있으면 코드가 지저분하고 복잡해준다. 또한 커넥션을 유지하는 코드 또한 어려운 일이다.