DBeaver에서 COMMIT했는데 다른 창에 반영되지 않는 이유: Oracle 세션으로 검증

DBeaver에서 UPDATE를 실행하고 같은 창에서 조회하면 바뀐 값이 잘 보입니다. 그런데 다른 SQL 편집기 창에서는 여전히 이전 값이 나오고, 그 창에서 COMMIT을 눌러도 결과가 그대로인 경우가 있습니다. 데이터베이스 장애나 캐시 문제처럼 보이지만, 대부분은 변경한 세션과 COMMIT한 세션이 다르기 때문입니다.
이 글은 Oracle Database 21c XE에서 같은 계정으로 접속한 두 세션을 열고, 어느 세션에서 COMMIT하느냐에 따라 다른 창의 조회 결과가 어떻게 달라지는지 직접 재현한 기록입니다. 각 단계에서 실제 SID와 주문 상태, 금액을 함께 확인했습니다.
1. 증상: 같은 행인데 창마다 다른 값이 보입니다
실험 데이터는 주문번호 1번 한 행이며, 초기값은 주문 상태 READY, 금액 10,000입니다. A 세션에서 이 행을 PAID, 12,000으로 바꾼 뒤 두 세션에서 번갈아 조회한 결과는 다음과 같습니다.
| 실험 단계 | 조회 세션 | 주문 상태 | 금액 |
|---|---|---|---|
| A에서 UPDATE 후 조회 | A | PAID | 12,000 |
| A가 COMMIT하기 전 조회 | B | READY | 10,000 |
| B에서 COMMIT 후 조회 | B | READY | 10,000 |
| A에서 COMMIT 후 새로 조회 | B | PAID | 12,000 |
B에서 COMMIT을 실행해도 값은 바뀌지 않았고, 실제로 UPDATE를 실행한 A에서 COMMIT한 뒤에야 B의 새 조회에 PAID, 12,000이 나타났습니다. 이 실험은 처리 속도를 비교하는 테스트가 아니라, 같은 행이 세션과 커밋 시점에 따라 어떻게 보이는지 확인하는 실험입니다.
2. 원인: COMMIT은 실행한 세션의 트랜잭션만 확정합니다
Oracle에서 트랜잭션은 계정이 아니라 세션에 속합니다. COMMIT은 그 문장을 실행한 세션의 현재 트랜잭션을 확정할 뿐이며, 같은 사용자 이름으로 접속한 다른 세션의 변경을 대신 확정하지 못합니다. B가 실행한 COMMIT은 B 자신의 트랜잭션(이 실험에서는 확정할 변경이 없는 상태)에만 적용된 것입니다.
두 세션이 서로 다른 값을 보는 이유는 Oracle의 읽기 일관성 때문입니다.
- 변경한 세션(A)은 커밋 전에도 자신의 변경 내용을 조회합니다. 그래서 UPDATE 직후 같은 창에서 새 값이 보인다고 해서 커밋됐다고 판단할 수 없습니다.
- 다른 세션(B)의 일반 SELECT는 커밋된 데이터만 봅니다. A가 바꾸고 있는 행은 Undo에 남아 있는 변경 전 이미지로 재구성해서 읽기 때문에, A의 잠금을 기다리지 않고 이전 커밋 값(READY, 10,000)을 돌려받습니다.
따라서 “다른 창에 반영되지 않는다”는 증상을 만나면 COMMIT을 실행했는지보다 어느 세션에서 COMMIT을 실행했는지를 먼저 확인해야 합니다.
3. 테스트 환경과 데이터 준비
| 항목 | 실험 조건 |
|---|---|
| 데이터베이스 | Oracle Database 21c Express Edition 21.3 (Windows) |
| 클라이언트 도구 | DBeaver |
| 접속 정보 | 두 세션 모두 ORA_LAB 계정, PDB XEPDB1, 서비스 xepdb1 |
| 세션 SID | A 1096, B 974 (이번 접속에서 확인한 값) |
| 테스트 테이블 | LAB_COMMIT_DEMO, 1행, ORDER_ID 기본키 |
| 커밋 모드 | 수동 커밋 |
| 격리 수준 | 기본값 READ COMMITTED |
SID는 접속할 때마다 달라지므로, 재현할 때는 직접 조회한 값을 기준으로 삼아야 합니다.
DBeaver 설정: ORA_LAB 연결을 두 개 만들어 A와 B로 구분했습니다. 편집기 탭 이름만으로는 세션이 나뉘었는지 알 수 없으므로 SID를 직접 확인했습니다. 또한 두 연결 모두 수동 커밋 모드로 두었습니다. 자동 커밋이 켜져 있으면 UPDATE가 실행 즉시 커밋되어 이번 실험의 미커밋 상태를 만들 수 없습니다.
테이블 생성과 초기화: 아래 블록은 실험 시작 전에 A 편집기에서 한 번만 실행합니다. 테이블이 없으면 만들고, 주문번호 1번을 READY, 10,000으로 맞춘 뒤 COMMIT해서 B에서도 같은 값을 보도록 합니다. 실험 도중에 다시 실행하면 초기화와 COMMIT이 함께 수행되어 관찰 중인 상태가 바뀝니다.
DECLARE
l_count PLS_INTEGER;
BEGIN
IF USER <> 'ORA_LAB'
OR SYS_CONTEXT('USERENV','CON_NAME') <> 'XEPDB1' THEN
RAISE_APPLICATION_ERROR(-20001,
'ORA_LAB 계정으로 XEPDB1에 접속해야 합니다.');
END IF;
SELECT COUNT(*) INTO l_count
FROM user_tables
WHERE table_name = 'LAB_COMMIT_DEMO';
IF l_count = 0 THEN
EXECUTE IMMEDIATE 'CREATE TABLE lab_commit_demo (
order_id NUMBER(10) CONSTRAINT pk_lab_commit_demo PRIMARY KEY,
order_status VARCHAR2(10 CHAR) NOT NULL,
order_amount NUMBER(12,2) NOT NULL)';
END IF;
EXECUTE IMMEDIATE q'[
MERGE INTO lab_commit_demo t
USING (SELECT 1 AS order_id FROM dual) s
ON (t.order_id = s.order_id)
WHEN MATCHED THEN
UPDATE SET t.order_status = 'READY', t.order_amount = 10000
WHEN NOT MATCHED THEN
INSERT (order_id, order_status, order_amount)
VALUES (s.order_id, 'READY', 10000)
]';
COMMIT;
END;
4. 두 세션으로 재현한 다섯 단계
① 같은 계정과 서비스지만 SID는 다릅니다
A와 B에서 각각 아래 SQL을 실행했습니다. B에서는 첫 줄의 ‘A’만 ‘B’로 바꿨습니다.
SELECT 'A' AS session_label,
USER AS login_user,
SYS_CONTEXT('USERENV','CON_NAME') AS pdb_name,
SYS_CONTEXT('USERENV','SERVICE_NAME') AS service_name,
SYS_CONTEXT('USERENV','SID') AS session_id
FROM dual;

| 연결 | 계정 | PDB | 서비스 | SID |
|---|---|---|---|---|
| A | ORA_LAB | XEPDB1 | xepdb1 | 1096 |
| B | ORA_LAB | XEPDB1 | xepdb1 | 974 |
계정과 접속 대상은 같지만 SID가 다르므로 두 창은 서로 다른 DB 세션입니다. 이후 단계에서도 결과마다 SID를 함께 조회해 어느 세션의 결과인지 구분했습니다.
② A는 커밋 전에도 자신의 변경을 봅니다
UPDATE lab_commit_demo
SET order_status = 'PAID', order_amount = 12000
WHERE order_id = 1;
SELECT 'A_AFTER_UPDATE' AS check_step,
SYS_CONTEXT('USERENV','SID') AS session_id,
order_id, order_status, order_amount
FROM lab_commit_demo
WHERE order_id = 1;

COMMIT하지 않은 상태에서 SID 1096, PAID, 12,000이 조회됐습니다. A가 자신의 미커밋 변경을 보고 있는 것이며, 다른 세션에도 이 값이 보인다는 뜻은 아닙니다.
③ B는 이전에 커밋된 값을 봅니다
A의 트랜잭션을 열어 둔 채로 B에서 일반 SELECT를 실행했습니다.
SELECT 'B_BEFORE_A_COMMIT' AS check_step,
SYS_CONTEXT('USERENV','SID') AS session_id,
order_id, order_status, order_amount
FROM lab_commit_demo
WHERE order_id = 1;

결과는 SID 974, READY, 10,000이었고 조회는 기다림 없이 끝났습니다. A가 잠그고 있는 행이지만 일반 SELECT는 행 잠금을 요청하지 않기 때문입니다. 같은 행을 SELECT … FOR UPDATE로 조회하거나 UPDATE하려 했다면 B는 A의 트랜잭션이 끝날 때까지 기다렸을 것입니다.
④ B에서 COMMIT해도 A의 변경은 확정되지 않습니다
COMMIT;
SELECT 'B_AFTER_OWN_COMMIT' AS check_step,
SYS_CONTEXT('USERENV','SID') AS session_id,
order_id, order_status, order_amount
FROM lab_commit_demo
WHERE order_id = 1;

B에서 COMMIT은 오류 없이 끝났지만 새로 조회한 값은 여전히 READY, 10,000이었습니다. COMMIT이 정상 완료됐다는 메시지만으로는 원하던 변경이 확정됐는지 알 수 없다는 점을 보여주는 결과입니다.
⑤ A에서 COMMIT하면 B의 새 조회에 반영됩니다
변경을 실행한 A에서 COMMIT한 뒤, B에서 SELECT를 새로 실행했습니다.
-- A 세션
COMMIT;
-- B 세션
SELECT 'B_AFTER_A_COMMIT' AS check_step,
SYS_CONTEXT('USERENV','SID') AS session_id,
order_id, order_status, order_amount
FROM lab_commit_demo
WHERE order_id = 1;

B의 결과가 PAID, 12,000으로 바뀌었고 SID는 974 그대로였습니다. 같은 B 세션에서 A의 커밋 이후 값을 확인한 것입니다.
A의 COMMIT 실행 통계에는 Updated Rows 0이 표시됐습니다. UPDATE가 실패했다는 뜻이 아니라, COMMIT 문장 자체가 행을 수정하는 문장이 아니기 때문에 0으로 나온 것입니다. 변경이 확정됐는지는 이 숫자가 아니라 다른 세션의 새 조회 결과로 확인해야 합니다.
5. COMMIT 위치만 바꿨을 때의 비교
데이터와 조회 SQL은 그대로 두고, COMMIT을 실행한 세션만 달리한 결과입니다.
| 비교 항목 | B에서 COMMIT | A에서 COMMIT |
|---|---|---|
| UPDATE 실행 세션 | A (SID 1096) | A (SID 1096) |
| COMMIT 실행 세션 | B (SID 974) | A (SID 1096) |
| 확인 세션 | B (SID 974) | B (SID 974) |
| B에서 본 주문 상태 | READY | PAID |
| B에서 본 금액 | 10,000 | 12,000 |
재연결이나 설정 변경 없이, 변경한 세션에서 COMMIT하는 것만으로 결과가 바뀌었습니다. 이번에 재현한 현상의 원인은 변경 세션과 COMMIT 세션이 달랐던 것입니다.
6. DBeaver와 운영 환경에서 놓치기 쉬운 지점
편집기 창과 DB 세션은 1:1이 아닐 수 있습니다: DBeaver는 연결 설정에 따라 편집기마다 별도 연결을 열기도 하고, 여러 편집기가 하나의 연결을 함께 쓰기도 합니다. 창이 다르다고 세션이 다르다고 가정하지 말고, 헷갈릴 때는 SYS_CONTEXT('USERENV','SID')로 직접 확인하는 것이 가장 확실합니다.
미커밋 트랜잭션이 남아 있는지 먼저 봅니다: 다른 창에서 값이 안 보인다고 UPDATE를 다시 실행하면 같은 변경이 중복되거나, 다른 세션이 잠금을 기다리게 될 수 있습니다. 현재 세션에 진행 중인 트랜잭션이 있는지는 아래처럼 확인할 수 있습니다. 결과가 NULL이 아니면 아직 COMMIT이나 ROLLBACK되지 않은 트랜잭션이 있다는 뜻입니다.
SELECT DBMS_TRANSACTION.LOCAL_TRANSACTION_ID AS local_trx_id
FROM dual;
DBA 권한이 있다면 인스턴스 전체에서 열려 있는 트랜잭션과 그 세션을 함께 볼 수 있습니다.
SELECT s.sid, s.serial#, s.username, s.program,
s.status, t.start_time, t.used_urec
FROM v$transaction t
JOIN v$session s ON s.taddr = t.addr
ORDER BY t.start_time;
두 조회는 이번 실험에서 결과를 캡처하지 않았으며, 진단할 때 함께 쓸 수 있는 방법으로 소개합니다. V$ 뷰 조회에는 별도 권한이 필요합니다.
COMMIT은 트랜잭션 전체를 확정합니다: COMMIT은 방금 고친 한 행만 확정하는 것이 아니라, 그 세션의 트랜잭션에 쌓인 모든 미확정 변경을 함께 확정합니다. 운영 환경에서는 같은 세션에서 앞서 실행한 다른 변경이 없는지 확인한 뒤 COMMIT과 ROLLBACK을 결정합니다.
DDL은 암묵적으로 커밋합니다: Oracle에서 CREATE, ALTER, DROP, TRUNCATE 같은 DDL은 실행 전후에 자동으로 COMMIT을 수행합니다. 미커밋 UPDATE가 있는 세션에서 DDL을 실행하면 그 UPDATE도 함께 확정되므로, 수동 커밋 모드라도 의도치 않게 변경이 확정될 수 있습니다. 위 준비 블록을 실험 도중 다시 실행하면 안 되는 이유도 같습니다.
기존 결과창이 아니라 새 조회로 확인합니다: COMMIT 후에도 이전 값이 보인다면 결과 그리드가 예전 조회 결과를 그대로 보여주고 있는지 확인합니다. 이번 실험은 단계마다 SELECT를 새로 실행했습니다. READ COMMITTED에서는 각 SQL 문장이 시작된 시점에 커밋된 데이터를 읽기 때문입니다. SERIALIZABLE이나 읽기 전용 트랜잭션은 트랜잭션 시작 시점을 기준으로 읽으므로 이번 결과와 다르게 보일 수 있습니다.
실행 시간은 판단 기준이 아닙니다: A의 COMMIT 화면에 실행 시간 0.01초가 표시됐지만, 커밋 성능을 측정한 값은 아닙니다. 이 실험의 기준은 변경 세션에서 데이터가 확정됐는지와, 다른 세션의 새 조회에서 정확한 값이 보이는지입니다.
7. 실험 데이터를 되돌리고 정리하는 방법
이미 커밋한 변경은 ROLLBACK으로 되돌릴 수 없으므로, 초기 상태로 돌리려면 A에서 다시 UPDATE하고 COMMIT합니다. 이후 B에서 새로 조회하면 READY, 10,000이 보입니다.
UPDATE lab_commit_demo
SET order_status = 'READY', order_amount = 10000
WHERE order_id = 1;
COMMIT;
아직 커밋하지 않은 A의 변경을 취소하려면 A에서 ROLLBACK;을 실행합니다. COMMIT과 마찬가지로 ROLLBACK도 실행한 세션의 트랜잭션에만 적용되므로, B에서 ROLLBACK해도 A의 변경은 취소되지 않습니다.
테스트 테이블을 지울 때는 두 세션의 트랜잭션을 모두 끝낸 뒤 실행합니다.
DROP TABLE ora_lab.lab_commit_demo PURGE;
DBeaver에서 변경이 다른 창에 반영되지 않을 때는 접속 대상, 각 창의 SID, UPDATE를 실행한 세션, COMMIT을 실행한 세션, 새로 실행한 SELECT 결과를 순서대로 확인하면 대부분 원인을 바로 찾을 수 있습니다.




댓글 0
첫 댓글을 남겨보세요.