소개
이 실습에서는 데이터 무결성을 보장하는 데 필수적인 PostgreSQL 트랜잭션 관리를 탐색합니다. 일련의 작업을 하나의 작업 단위로 처리하는 트랜잭션 시작 및 커밋 방법을 배우게 됩니다. 또한 실패한 트랜잭션을 되돌리고, 격리 수준을 설정하고, 동시 업데이트로 잠금을 시뮬레이션하는 방법도 학습합니다.
이 실습에서는 데이터 무결성을 보장하는 데 필수적인 PostgreSQL 트랜잭션 관리를 탐색합니다. 일련의 작업을 하나의 작업 단위로 처리하는 트랜잭션 시작 및 커밋 방법을 배우게 됩니다. 또한 실패한 트랜잭션을 되돌리고, 격리 수준을 설정하고, 동시 업데이트로 잠금을 시뮬레이션하는 방법도 학습합니다.
이 단계에서는 PostgreSQL 에서 트랜잭션을 시작하고 커밋하는 방법을 배웁니다. 트랜잭션은 일련의 작업을 하나의 작업 단위로 처리하여 데이터 무결성을 보장합니다. 트랜잭션 내의 어떤 작업이 실패하면 전체 트랜잭션이 되돌려지므로 부분적인 업데이트가 방지되고 일관성이 유지됩니다.
먼저 postgres 사용자로 PostgreSQL 데이터베이스에 연결합니다. 터미널을 열고 다음 명령어를 사용합니다.
sudo -u postgres psql
이제 postgres=# 프롬프트가 나타납니다.
다음으로, 트랜잭션을 보여주기 위해 accounts라는 테이블을 만듭니다.
CREATE TABLE accounts (
id SERIAL PRIMARY KEY,
name VARCHAR(50),
balance DECIMAL(10, 2)
);
accounts 테이블에 초기 데이터를 삽입합니다.
INSERT INTO accounts (name, balance) VALUES ('Alice', 100.00);
INSERT INTO accounts (name, balance) VALUES ('Bob', 50.00);
이제 BEGIN 명령어를 사용하여 트랜잭션을 시작합니다.
BEGIN;
트랜잭션 내에서 Alice 계좌에서 Bob 계좌로 20 달러를 이체합니다.
UPDATE accounts SET balance = balance - 20.00 WHERE name = 'Alice';
UPDATE accounts SET balance = balance + 20.00 WHERE name = 'Bob';
이러한 변경 사항을 영구적으로 적용하려면 COMMIT 명령어를 사용하여 트랜잭션을 커밋합니다.
COMMIT;
accounts 테이블을 쿼리하여 트랜잭션이 성공적으로 수행되었는지 확인합니다.
SELECT * FROM accounts;
Alice 의 잔액이 20 달러 감소하고 Bob 의 잔액이 20 달러 증가했는지 확인해야 합니다.

마지막으로 psql 쉘을 종료합니다.
\q
이 단계에서는 PostgreSQL 에서 실패한 트랜잭션을 되돌리는 방법을 배웁니다. 데이터베이스 연산 시퀀스 중 오류가 발생하면 트랜잭션을 되돌리는 것은 데이터베이스가 일관된 상태를 유지하는 데 중요합니다.
이전 단계에서 PostgreSQL 데이터베이스에 연결되어 있어야 합니다. 그렇지 않으면 다음 명령어를 사용하여 다시 연결합니다.
sudo -u postgres psql
새로운 트랜잭션을 시작합니다.
BEGIN;
이 트랜잭션 내에서 의도적으로 실패하는 작업을 시도합니다. 중복된 기본 키를 삽입해 보겠습니다. 먼저 사용 가능한 다음 id 값을 찾습니다.
SELECT MAX(id) FROM accounts;
결과가 2 라고 가정합니다. 이제 이미 존재하는 id = 1을 가진 새 계정을 삽입해 봅니다.
INSERT INTO accounts (id, name, balance) VALUES (1, 'Eve', 25.00);
이 명령은 ERROR: duplicate key value violates unique constraint "accounts_pkey" 오류를 발생시킵니다.
트랜잭션 내에서 오류가 발생했으므로 수행된 모든 변경 사항을 버리기 위해 트랜잭션을 되돌립니다. ROLLBACK 명령을 사용합니다.
ROLLBACK;
accounts 테이블을 쿼리하여 ROLLBACK이 성공했는지 확인합니다.
SELECT * FROM accounts;
테이블에는 여전히 1 단계의 끝에서 Alice 와 Bob 의 계정과 잔액만 포함되어 있어야 합니다. 실패한 INSERT 작업이 성공적으로 되돌려졌습니다.

마지막으로 psql 쉘을 종료합니다.
\q
이 단계에서는 PostgreSQL 의 트랜잭션 격리 수준과 이를 설정 및 테스트하는 방법에 대해 알아봅니다. 격리 수준은 동시 트랜잭션이 서로 얼마나 격리되는지 제어합니다. 높은 격리 수준은 데이터 손상으로부터 더 큰 보호를 제공하지만, 동시성을 감소시킬 수 있습니다.
이전 단계에서 PostgreSQL 데이터베이스에 연결되어 있어야 합니다. 그렇지 않으면 다음 명령어를 사용하여 다시 연결합니다.
sudo -u postgres psql
두 개의 별도 터미널 창을 엽니다. 각 터미널에서 postgres 사용자로 PostgreSQL 데이터베이스에 연결합니다. 두 개의 postgres=# 프롬프트가 나타납니다.
터미널 1:
sudo -u postgres psql
터미널 2:
sudo -u postgres psql
터미널 1에서 격리 수준을 READ COMMITTED로 설정합니다 (기본값이지만, 설명을 위해 명시적으로 설정합니다).
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
그런 다음 트랜잭션을 시작합니다.
BEGIN;
터미널 1에서 Alice 의 잔액을 읽습니다.
SELECT balance FROM accounts WHERE name = 'Alice';
잔액을 기록합니다. 이제 터미널 2에서 트랜잭션을 시작하고 Alice 의 잔액을 업데이트합니다.
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN;
UPDATE accounts SET balance = 90.00 WHERE name = 'Alice';
COMMIT;
터미널 1에서 Alice 의 잔액을 다시 읽습니다.
SELECT balance FROM accounts WHERE name = 'Alice';
격리 수준이 READ COMMITTED이므로 터미널 2 에서 커밋된 업데이트된 잔액 (90.00) 이 표시됩니다.

이제 REPEATABLE READ 격리 수준을 테스트해 보겠습니다. 터미널 1에서 현재 트랜잭션을 되돌리고 격리 수준을 REPEATABLE READ로 설정합니다.
ROLLBACK;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN;
터미널 1에서 Alice 의 잔액을 다시 읽습니다.
SELECT balance FROM accounts WHERE name = 'Alice';
잔액을 기록합니다. 이제 터미널 2에서 트랜잭션을 시작하고 Alice 의 잔액을 다시 업데이트합니다.
BEGIN;
UPDATE accounts SET balance = 100.00 WHERE name = 'Alice';
COMMIT;
터미널 1에서 Alice 의 잔액을 다시 읽습니다.
SELECT balance FROM accounts WHERE name = 'Alice';
격리 수준이 REPEATABLE READ이므로 터미널 2 에서 새로운 값을 커밋했더라도 트랜잭션이 시작되었을 때의 원래 잔액이 표시됩니다.
마지막으로 터미널 1에서 트랜잭션을 커밋합니다.
COMMIT;
이제 터미널 1에서 Alice 의 잔액을 다시 읽으면 최신 커밋된 값 (100.00) 이 표시됩니다.

두 개의 psql 쉘을 모두 종료합니다.
\q
이 단계에서는 PostgreSQL 에서 동시 업데이트로 잠금을 시뮬레이션합니다. 잠금은 동시 트랜잭션이 서로 간섭하는 것을 방지하여 데이터 무결성을 보장하는 메커니즘입니다.
이전 단계에서 PostgreSQL 데이터베이스에 연결되어 있어야 합니다. 그렇지 않으면 다음 명령어를 사용하여 다시 연결합니다.
sudo -u postgres psql
두 개의 별도 터미널 창을 엽니다. 각 터미널에서 postgres 사용자로 PostgreSQL 데이터베이스에 연결합니다. 두 개의 postgres=# 프롬프트가 나타납니다.
터미널 1:
sudo -u postgres psql
터미널 2:
sudo -u postgres psql
터미널 1에서 트랜잭션을 시작하고 Alice 의 잔액을 업데이트합니다. 중요하게도 행을 잠그기 위해 SELECT ... FOR UPDATE를 사용합니다.
BEGIN;
SELECT balance FROM accounts WHERE name = 'Alice' FOR UPDATE;
이 명령은 Alice 의 잔액을 검색하고 해당 행에 잠금을 설정하여 이 트랜잭션이 커밋되거나 되돌려질 때까지 다른 트랜잭션이 해당 행을 수정하는 것을 방지합니다.
터미널 2에서 트랜잭션을 시작하고 Alice 의 잔액을 업데이트하려고 시도합니다.
BEGIN;
UPDATE accounts SET balance = balance + 10 WHERE name = 'Alice';
터미널 2의 이 명령은 멈춰 있는 것처럼 보입니다. 이는 터미널 1이 보유한 잠금이 해제될 때까지 기다리고 있기 때문입니다.
이제 터미널 1에서 트랜잭션을 커밋합니다.
COMMIT;
터미널 1에서 트랜잭션을 커밋한 후 터미널 2의 UPDATE 명령이 계속 진행됩니다.
터미널 2에서 트랜잭션을 커밋합니다.
COMMIT;
이제 어느 터미널에서든 accounts 테이블을 쿼리하여 변경 사항을 확인합니다.
SELECT * FROM accounts;
Alice 의 잔액이 터미널 1 이 잠금을 해제한 후 터미널 2 의 트랜잭션에 의해 업데이트되었음을 확인해야 합니다.

마지막으로 두 개의 psql 쉘을 모두 종료합니다.
\q
이 예제는 SELECT ... FOR UPDATE를 사용하여 잠금을 시뮬레이션하고 동시 업데이트가 서로 간섭하는 것을 방지하는 방법을 보여줍니다. 잠금이 없으면 두 트랜잭션 모두 동일한 초기 잔액을 읽고 해당 값을 기반으로 업데이트를 적용할 수 있으므로 업데이트가 손실될 수 있습니다.
이 실습에서는 PostgreSQL 에서 트랜잭션을 관리하는 방법을 배웠습니다. psql을 사용하여 PostgreSQL 데이터베이스에 연결하고 초기 데이터가 있는 샘플 accounts 테이블을 생성하는 것으로 시작했습니다.
그런 다음 트랜잭션 사용을 보여주는 데 집중했습니다. BEGIN 명령을 사용하여 트랜잭션을 시작하고, 여러 데이터베이스 작업 (Alice 와 Bob 의 잔액 업데이트) 을 수행한 다음, COMMIT 명령을 사용하여 트랜잭션을 커밋하여 변경 사항을 영구적으로 적용했습니다. 이는 트랜잭션의 기본 원칙을 보여주는 것으로, 일련의 작업을 하나의 원자 단위로 취급하는 것을 보여줍니다. 또한 트랜잭션을 되돌리는 방법과 격리 수준을 설정하는 방법을 배웠습니다. 마지막으로, 동시 업데이트로 잠금을 시뮬레이션하여 데이터 손상을 방지하는 방법을 이해했습니다.