본문으로 건너뛰기
김신건의 로그

[SQL] DCL TCL

· 수정 · 📖 약 3분 · 1,207자/단어 #sql #dcl #tcl #transaction #permission #security #database
SQL DCL TCL, SQL DCL, SQL TCL, GRANT, REVOKE, Transaction, BEGIN, COMMIT, ROLLBACK, SAVEPOINT, ACID

정의

  • DCL (Data Control Language): 권한. GRANT, REVOKE.
  • TCL (Transaction Control Language): 트랜잭션 경계. BEGIN, COMMIT, ROLLBACK, SAVEPOINT.

같이 묶어 다루는 이유, “보안”과 “원자성”은 운영의 두 기둥이고 SQL 의 나머지 (DDL/DML) 와 결이 다르다.

전체 SQL 체계는 SQL 참고.

트랜잭션과 ACID

트랜잭션 = 하나의 논리 단위로 묶인 SQL 문 집합. 전체가 성공하거나 (COMMIT), 전체가 취소되거나 (ROLLBACK).

성질의미
Atomicity원자성, all-or-nothing
Consistency무결성 제약을 깨지 않음
Isolation동시 트랜잭션이 서로 안 보임 (격리 수준 따라)
Durability커밋된 변경은 영구 (장애에도 살아남음)

Redis 의 트랜잭션은 ACID 가 아니다, 자세한 비교는 Redis 참고.

BEGIN / COMMIT / ROLLBACK

BEGIN;       -- 트랜잭션 시작 (PG)
START TRANSACTION;   -- 표준 / MySQL

UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;

COMMIT;     -- 확정
-- 또는
ROLLBACK;   -- 취소, 둘 다 안 일어난 상태로

IMPORTANT

  • PostgreSQL: 모든 statement 가 암묵적 트랜잭션. BEGIN 명시하면 다중 statement 트랜잭션.
  • MySQL: autocommit=1 (기본). statement 마다 자동 커밋. BEGIN/START TRANSACTION 으로 묶거나 SET autocommit=0 후 명시 COMMIT.

autocommit 의 함정

-- MySQL 기본 (autocommit=1)
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- 즉시 커밋, 되돌릴 수 없음!

-- 안전한 방법
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
SELECT balance FROM accounts WHERE id = 1;     -- 확인
COMMIT;        -- 또는 ROLLBACK

운영 DB 에선 항상 명시적 트랜잭션. autocommit 끄는 습관 권장.

SAVEPOINT

트랜잭션 안에서 부분 롤백 지점.

BEGIN;

INSERT INTO logs (msg) VALUES ('start');

SAVEPOINT before_risky;

UPDATE inventory SET qty = qty - 5 WHERE id = 1;
-- 이상 발견
ROLLBACK TO SAVEPOINT before_risky;
-- start log 는 살아 있고 inventory 변경만 취소

INSERT INTO logs (msg) VALUES ('skipped risky op');

COMMIT;

격리 수준 (Isolation Level)

동시 트랜잭션 간 가시성. ANSI 표준 4 단계.

수준Dirty ReadNon-repeatable ReadPhantom Read
READ UNCOMMITTED가능가능가능
READ COMMITTED방지가능가능
REPEATABLE READ방지방지가능 (표준), 방지 (PG, MySQL InnoDB)
SERIALIZABLE방지방지방지
이상현상의미
Dirty Read커밋 안 된 데이터 읽음
Non-repeatable Read같은 쿼리 두 번 → 다른 값
Phantom Read같은 쿼리 두 번 → 새로 추가된 행 보임

dialect 기본값

  • PostgreSQL: READ COMMITTED 기본. REPEATABLE READ, SERIALIZABLE 사용 가능 (MVCC + SSI).
  • MySQL InnoDB: REPEATABLE READ 기본. Phantom 도 방지 (gap lock).
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN;
-- ...
COMMIT;

MVCC, 잠금, 비교

  • 잠금 기반 (Lock-based): 읽는 동안 쓰기 차단. 단순하지만 짧은 쓰기에도 읽기가 막힘.
  • MVCC (Multi-Version Concurrency Control): 트랜잭션마다 자기 버전의 스냅샷을 본다. 읽기가 쓰기를 막지 않고, 쓰기가 읽기를 막지 않는다. PostgreSQL, MySQL InnoDB, Oracle 의 기본.
-- 명시적 행 잠금
SELECT * FROM accounts WHERE id = 1 FOR UPDATE;     -- 쓰기 잠금
SELECT * FROM accounts WHERE id = 1 FOR SHARE;      -- 읽기 잠금

-- PG: 다른 트랜잭션 락 충돌 시 즉시 에러
SELECT * FROM accounts WHERE id = 1 FOR UPDATE NOWAIT;
SELECT * FROM accounts WHERE id = 1 FOR UPDATE SKIP LOCKED;     -- 큐 패턴

데드락

서로의 락을 기다리는 두 트랜잭션. RDBMS 가 감지해서 한쪽을 강제 롤백.

T1: UPDATE a; UPDATE b;
T2: UPDATE b; UPDATE a;
   → T1 이 a 잠그고 b 대기, T2 가 b 잠그고 a 대기 → deadlock

해법: 항상 같은 순서로 락 획득, 또는 짧은 트랜잭션 유지, 또는 비관적 잠금 → 낙관적 잠금 (version 컬럼).

DCL: GRANT / REVOKE

권한 부여/회수.

사용자/역할

-- PostgreSQL
CREATE USER alice WITH PASSWORD 'secret';
CREATE ROLE app_reader;
GRANT app_reader TO alice;

-- MySQL
CREATE USER 'alice'@'%' IDENTIFIED BY 'secret';
CREATE ROLE 'app_reader';
GRANT 'app_reader' TO 'alice'@'%';

PG 는 USER 와 ROLE 이 같은 개념 (USER 는 LOGIN 권한이 있는 ROLE), MySQL 도 8.0 부터 통합.

권한 부여

-- 테이블 단위
GRANT SELECT, INSERT ON users TO alice;
GRANT ALL PRIVILEGES ON orders TO alice;

-- 스키마 단위 (PostgreSQL)
GRANT USAGE ON SCHEMA public TO alice;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO alice;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
  GRANT SELECT ON TABLES TO alice;     -- 미래 테이블도 자동 적용

-- 데이터베이스 단위 (MySQL)
GRANT SELECT, INSERT ON mydb.* TO 'alice'@'%';

-- 모든 데이터베이스 (위험)
GRANT ALL PRIVILEGES ON *.* TO 'alice'@'%' WITH GRANT OPTION;

컬럼 단위 권한

GRANT SELECT (id, name) ON users TO alice;     -- 일부 컬럼만

salary 같은 민감 컬럼은 컬럼 단위 + 뷰로 격리하는 게 더 흔하다.

REVOKE

REVOKE INSERT ON users FROM alice;
REVOKE ALL PRIVILEGES ON users FROM alice;

권한 확인

-- PostgreSQL
\dp users
SELECT * FROM information_schema.table_privileges WHERE grantee = 'alice';

-- MySQL
SHOW GRANTS FOR 'alice'@'%';

RLS (Row Level Security, PostgreSQL)

행 단위 권한, 같은 테이블이라도 사용자에 따라 보이는 행이 다르다.

ALTER TABLE orders ENABLE ROW LEVEL SECURITY;

CREATE POLICY owner_only ON orders
  FOR SELECT
  USING (user_id = current_setting('app.user_id')::int);

세션 변수로 사용자 컨텍스트를 주고 RLS 가 자동으로 WHERE 추가. 멀티테넌트 SaaS 의 강력한 패턴.

MySQL 은 RLS 가 없다, 뷰 + 권한으로 흉내 또는 애플리케이션 레벨.

트랜잭션과 DDL

dialect 큰 차이.

PostgreSQLMySQL
CREATE TABLE 트랜잭션 안롤백 가능묵시적 커밋 (롤백 불가)
ALTER TABLE 트랜잭션 안롤백 가능8.0 의 일부 atomic DDL 외엔 묵시적 커밋
TRUNCATE롤백 가능묵시적 커밋

PostgreSQL 마이그레이션 스크립트는 전부 한 트랜잭션 으로 묶을 수 있다, MySQL 은 중간에 멈추면 부분 적용 상태가 남는다.

2-Phase Commit (분산 트랜잭션)

여러 노드에 걸친 트랜잭션. PG PREPARE TRANSACTION / COMMIT PREPARED.

BEGIN;
UPDATE ...;
PREPARE TRANSACTION 'tx1';     -- 모든 노드가 prepare 성공이면

COMMIT PREPARED 'tx1';
-- 한 노드라도 실패면
ROLLBACK PREPARED 'tx1';

운영에선 거의 안 쓴다 (느림, 잠금 길게 유지, 코디네이터 SPOF). Saga 패턴 등 분산 트랜잭션 대안 검토.

pandas 와 트랜잭션

pandas 자체는 트랜잭션 개념이 없다. DB I/O 시 SQLAlchemy 의 connection 컨텍스트가 트랜잭션 역할.

from sqlalchemy import create_engine, text
engine = create_engine('postgresql://...')

with engine.begin() as conn:        # 컨텍스트 종료 시 자동 commit, 예외 시 rollback
    df1.to_sql('a', conn, if_exists='append', index=False)
    df2.to_sql('b', conn, if_exists='append', index=False)
# 둘 다 성공해야 commit, 한쪽 실패면 둘 다 rollback

자세한 Pandas read_sql 패턴 참고.

함정

1. 트랜잭션 너무 길게

대용량 UPDATE 를 한 트랜잭션에 넣으면:

  • 락 점유 길어짐 → 동시성 저하
  • WAL/redo log 부피 증가
  • 롤백 시간 길어짐 (PG 의 dead tuple 누적)

→ 청크 단위 배치, 짧은 트랜잭션 다발.

2. SELECT 결과의 즉시성

SELECT COUNT(*) FROM t WHERE x = 1;     -- 100
-- 다른 트랜잭션이 INSERT
SELECT COUNT(*) FROM t WHERE x = 1;     -- 격리 수준에 따라 100 또는 101

REPEATABLE READ 면 같은 트랜잭션 내내 100, READ COMMITTED 면 변할 수 있다.

3. 데드락은 정상

운영 DB 에서 데드락은 발생한다, RDBMS 가 감지하고 롤백. 애플리케이션은 재시도 로직 을 가져야 한다.

for attempt in range(3):
    try:
        with engine.begin() as conn:
            do_work(conn)
        break
    except DeadlockError:
        time.sleep(0.1 * (2 ** attempt))

4. 권한 누락

GRANT SELECT ON ALL TABLES IN SCHEMA public TO alice;
-- 이후 새로 만든 테이블에는 권한 없음
-- → ALTER DEFAULT PRIVILEGES 같이

5. GRANT OPTION 의 전파

GRANT SELECT ON users TO alice WITH GRANT OPTION;
-- alice 가 또 다른 사용자에게 GRANT 가능
-- 추적 어려워짐, 운영에선 신중히

6. 최소 권한 원칙

운영 앱 계정은 보통:

  • DDL 권한 없음 (CREATE/DROP)
  • SELECT/INSERT/UPDATE/DELETE 만, 필요한 테이블에만
  • 슈퍼유저는 마이그레이션 도구 전용 분리

관련 위키

이 글의 용어 (7개)
[DB Internals] Redis 8 / Valkey 9: 라이센스 분기, 신 데이터 구조, 실전 운영database-internals
정의 Redis 는 in-memory key-value 데이터 구조 서버. 2009년 Salvatore Sanfilippo (antirez) 가 Lua Manuscripts 의 …
[DB] MySQL / InnoDB: clustered index, redo log, MVCCdatabase-internals
정의 MySQL + InnoDB (기본 스토리지 엔진) 의 clustered index + redo log 기반 행 기반 RDBMS. 웹 / SaaS 의 가장 흔한 backbon…
[DB] PostgreSQL: 프로세스 모델, MVCC, WAL, 확장성database-internals
정의 PostgreSQL 은 오픈소스 ORDBMS. 1986 UC Berkeley POSTGRES 의 후예. MVCC, 확장 가능 타입, JSONB, full-text searc…
[Pandas] read_sql / to_sqlpandas
정의 - : SQL 결과 → DataFrame - : DataFrame → DB 테이블 SQLAlchemy 의 connection / engine 객체를 사용. 직접 sqlite…
[SQL] DDLsql
정의 DDL (Data Definition Language) 은 스키마를 정의/변경/제거 하는 SQL 의 하위 집합. 대상은 테이블, 인덱스, 뷰, 시퀀스, 스키마, 도메인 등.…
[SQL] DMLsql
정의 DML (Data Manipulation Language) 은 데이터 자체를 조회/삽입/수정/삭제 하는 SQL 의 하위 집합. 일상 쿼리의 99% 가 여기에 속한다. | 명…
SQLsql
정의 SQL (Structured Query Language) 은 관계형 데이터베이스 (RDBMS) 와 대화하기 위한 선언형 (declarative) 질의 언어. 1970 년 E…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기