3.4. 트랜잭션

트랜잭션 transaction 이란 모든 데이터베이스 시스템에서 기본적인 한 개념입니다. 트랜잭션의 핵심은 여러개의 작업이 최종적으로는 하나로 취급된다는 것입니다. 이것을 전부 적용 아니면 전부 취소 작업(all-or-nothing operation)이라고 부릅니다. 각각의 트랜잭션은 독립적이여서 동시에 발생한 트랜잭션에 대해서는 그 트랜잭션 안에서의 데이터만 적용됩니다. 또, 하나의 트랜잭션 안에서 어떤 작업 도중 오류가 발생 되었다면, 이전에 적용되었던 모든 작업들에 대해서는 모두 취소됩니다.

예를 들어서, 간단한 은행 데이터베이스를 생각해봅시다. 이 은행 데이터베이스에는 각 계좌의 현 잔액을 보관하는 accounts 테이블과, 각 계좌의 거래내역을 보관하는 branches 테이블로 구성되어있습니다. 이때, Alice 계좌에서 $100.00 빼서 Bob 계좌로 이체하려고 합니다. 이것을 구현한다면, 가장 간단하게 아래와 같이 처리하면 되겠지요:

UPDATE accounts SET balance = balance - 100.00
    WHERE name = 'Alice';
UPDATE branches SET balance = balance - 100.00
    WHERE name = (SELECT branch_name FROM accounts WHERE name = 'Alice');
UPDATE accounts SET balance = balance + 100.00
    WHERE name = 'Bob';
UPDATE branches SET balance = balance + 100.00
    WHERE name = (SELECT branch_name FROM accounts WHERE name = 'Bob');

물론 계좌 이체가 이렇게 간단하게 움직여지지는 않겠지만, 여기서 중요한 것은 계좌 이체 작업을 하려면, 적어도 네 개의 독립된 update 구문으로 작업해야한다는 것입니다. 이런 작업을 하는데, 만일 위의 네 개의 작업 도중 부분적으로 작업을 성공하고, 그 나머지는 실패한다면, 각 계좌에 남아있는 금액이 이상해지겠지요. 이런 사태를 막기위해서, 하나의 작업군(여러개의 작업)이 전부 처리가 되든지, 아니면, 전부 취소가 되는 이런 작업이 필요합니다. 이런 작업을 할 때, 트랜잭션을 이용합니다. 이런 하나의 트랜잭션 내에서의 일군의 작업이 하나로 처리되는 것을 데이터베이스 용어로 트랜잭션의 원자성 atomic이라고 합니다.

또한 트랜잭션 작업이 정상적으로 끝났을 경우에는 그 변경된 자료가 어떠한 간섭 없이 저장되어야하고, 저장된 자료는 손상되지 않아야합니다. 예를 들어 Bob의 현금 인출 작업이 진행되고 있는 사이에도 Bob에게 현금을 주기전까지 어떠한 오류도 발생되어서는 안되겠지요. 이것을 구현하기 위해서 트랜잭션 기능을 제공하는 데이터베이스에서는 트랜잭션이 정상적으로 종료되었다고 알려주기 전에도 이미 하나의 트랜잭션에서 발생하는 모든 작업들은 영구저장장치(예, 하드디스크)에 기록을 해둡니다. (주, 이를 내구성(durability)이라고 합니다)

트랙잭션에서 중요한 또 하나의 속성은 하나의 트랜잭션은 다른 트랜잭션에 의해서 간섭받지 않아야합니다. 즉, 동시에 여러개의 트랜잭션이 발생했을 때, 각각의 트랜잭션은 다른 트랜잭션에서 변경되고 있는 자료들에 대해서는 참조하거나 간섭할 수 없어야합니다. (주, 이것을 고립성(isolation) 이라고 합니다) 예를 들어서, 모든 계좌의 현잔액 합계를 구하는 트랜잭션이 작업 중인데, Alice나 Bob의 현 잔액을 바꾸는 다른 트랜잭션에 의해서 그 계좌의 현 잔액이 바뀌게 된다면, 정확한 그 시점의 현 잔액 합계를 구할 수가 없겠지요. 그래서, 트랜잭션은 각각의 명령이 수행 될 때 마다 그 변경 사항이 데이터베이스의 원래 자료에 영향을 주는 것이 아니라, 트랜잭션 영역안에 있는 모든 작업이 끝났을 때, 한꺼번에 그 변경 사항이 데이터베이스에 적용됩니다. 이때부터 다른 트랜잭션이 그 변경된 데이터를 참조 할 수 있게 됩니다. (주, 이것을 정합성(consistency)이라고 합니다)

PostgreSQL에서 트랜잭션 작업을 하려면, 그 해당 작업 앞뒤로 BEGIN 명령과 COMMIT 명령을 지정합니다. 다음과 같은 형태가 트랙잭션을 사용하는 예입니다:

BEGIN;
UPDATE accounts SET balance = balance - 100.00
    WHERE name = 'Alice';
-- etc etc
COMMIT;

만일, 작업 도중 해당 작업에 문제가 생겨 다음 작업을 더이상 진행하지 말아야할 경우가 생깁니다. (윗 예로 든 경우라면, Alice 잔고가 없는 경우) 이때는 COMMIT 작업 대신에, ROLLBACK 명령을 사용해서 지금까지 작업한 내역들을 모두 취소하고 트랜잭션을 종료합니다.

PostgreSQL에서는 모든 SQL 구문은 모두 트랜잭션 안에서 실행되는 것으로 간주합니다. BEGIN 명령을 명시적으로 실행하지 않았다고 하더라도 실행하고자 하는 명령 앞 뒤에, BEGIN과 (그 명령이 성공했다면) COMMIT 명령을 함께 사용합니다. 이 때 BEGINCOMMIT 사이의 영역을 트랜잭션 블록이라고 합니다.

참고: 몇몇 클라이언트 라이브러리는 자동으로 BEGIN, COMMIT 명령을 포함해서 실행되기 때문에, 사용자가 트랜잭션 지정하면 오류를 내는 경우도 있습니다. 자세한 것은 해당 라이브러리 문서를 참조하십시오.

트랜잭션 안에서 savepoint를 지정해서 작업을 좀 더 유연하게 처리할 수도 있습니다. savepoint란 여기까지 작업이 정상 처리 되었다면, ROLLBACK TO 명령을 이용해서 그 지점 전까지 작업한 것에 대해서는 작업한 내용을 보장하겠다는 표시하는 지점입니다. 이 지점을 표시하는 방법은 SAVEPOINT 명령을 이용합니다.

savepoint로 취소 작업을 진행 한 뒤에도 계속 트랜잭션 내 작업을 진행할 수 있습니다. 그렇게 작업을 하다가도 언제든지 해당 savepoint로 돌아 갈 수 있습니다. 또한 더 이상 이 savepoint가 필요 없다고 판단되면 그것을 삭제해서 시스템 자원을 늘릴 수도 있습니다. 하나 주의 할 사항은 특정 savepoint로 돌아갈 경우 그 지점 이후에 지정해 두었던 다른 savepoint들도 모두 취소 되어 사라져버린다는 점입니다.

트랜잭션 내에서 일어나 모든 작업은 그 트랜잭션이 커밋되기 전까지 다른 사용자들이 볼 수 없습니다. 물론 해당 트랜잭션이 취소된다면 다른 사용자들은 절대로 그 변경했던 내용은 볼 수 없겠죠.

다음 예제는 윗 은행 거래 예제를 다시 savepoint 사용과 함께 구현해 본 것입니다:

BEGIN;
UPDATE accounts SET balance = balance - 100.00
    WHERE name = 'Alice';
SAVEPOINT my_savepoint;
UPDATE accounts SET balance = balance + 100.00
    WHERE name = 'Bob';
-- 에구 ... Wally한테 가야하는 거였는데...
ROLLBACK TO my_savepoint;
UPDATE accounts SET balance = balance + 100.00
    WHERE name = 'Wally';
COMMIT;

이 예제는 처음처럼 Alice 계좌에서 출금을 하고, Bob 계좌로 잘못 입금해서 ROLLBACK TO 명령으로 입금 전 상태로 되돌리고, 다시 바른 입금 처리를 하고, 트랜잭션을 커밋하는 것을 보여주는 것입니다. (물론 commit 명령이 아니라, rollback 명령이었다면, 전체 작업이 취소됩니다.)