Comet5 블로그
개발·운영

SQLite 일괄 저장: 두 번째 행이 실패하면 첫 행은 어떻게 될까

Comet5 · 최초 발행 · 약 4분

중복 키 오류를 의도적으로 만들어 트랜잭션 전체 롤백과 행별 저장의 차이를 확인합니다.

이 글의 목차

여러 행을 저장하는 코드가 예외를 냈다고 해서 앞서 저장한 행도 자동으로 사라지는 것은 아닙니다. 트랜잭션을 어디에서 시작하고 끝내는지에 따라 결과가 달라집니다. 이 글은 SQLite 메모리 DB로 “한 묶음을 전부 저장하거나 전부 취소한다”는 조건을 확인합니다.

실습에서 원하는 결과

기존 주문 A가 있는 상태에서 신규 주문 B와 중복 주문 A를 한 번에 넣습니다. 두 번째 입력이 유일성 제약을 위반하면 B도 남지 않아야 합니다. 예제는 금액·재고 같은 업무 처리를 생략하고 저장 원자성만 확인합니다.

Python 3.11 이상에서 실행할 수 있도록 기존 isolation_level 방식의 연결을 사용합니다. Python 3.12부터는 autocommit 설정도 있으므로 프로젝트가 사용하는 연결 모드를 먼저 확인해야 합니다. 자동 커밋 모드에 아래 패턴만 옮기면 같은 결과를 보장하지 못할 수 있습니다.

실패 후 DB를 직접 조회하기

import sqlite3

connection = sqlite3.connect(':memory:', isolation_level='DEFERRED')
try:
    connection.execute('CREATE TABLE orders (id TEXT PRIMARY KEY, quantity INTEGER)')
    connection.execute('INSERT INTO orders VALUES (?, ?)', ('A', 1))
    connection.commit()
    try:
        with connection:
            connection.executemany(
                'INSERT INTO orders VALUES (?, ?)', [('B', 2), ('A', 3)]
            )
    except sqlite3.IntegrityError:
        print('묶음 저장 취소')
    else:
        raise AssertionError('중복 키 오류가 발생해야 합니다')
    rows = connection.execute('SELECT id, quantity FROM orders ORDER BY id').fetchall()
    assert rows == [('A', 1)]
    print(rows)
    with connection:
        connection.executemany(
            'INSERT INTO orders VALUES (?, ?)', [('B', 2), ('C', 3)]
        )
    assert connection.execute('SELECT count(*) FROM orders').fetchone()[0] == 3
finally:
    connection.close()

출력은 묶음 저장 취소와 [('A', 1)]입니다. 뒤이어 정상 입력 B·C를 저장한 뒤 전체 3행인지 검사합니다. 단순히 예외가 났는지만 검사하면 “B가 남지 않아야 한다”는 핵심 조건을 놓칩니다.

with connection은 정상 종료 시 커밋하고 예외가 빠져나오면 롤백하는 연결 컨텍스트입니다. 연결 자체를 닫는 기능은 아니어서 finally에서 닫습니다. SQL 값은 ? 매개변수로 전달합니다. 테이블 이름 같은 SQL 구조를 값 매개변수로 바꿀 수 있는 것은 아닙니다.

예외를 잡는 위치가 결과를 바꾼다

오류를 with 내부에서 잡고 아무 일 없었던 것처럼 블록을 끝내면 앞선 변경이 커밋될 수 있습니다. 전체 취소가 목적이라면 오류가 컨텍스트 밖으로 전달되게 하거나 명시적으로 롤백해야 합니다. 위 예제의 except가 블록 바깥에 있는 이유입니다.

저장 규칙 실패 시 기대 결과 적합한 확인
묶음 전체 성공 한 행 실패면 전부 취소 실패 전후 전체 행 비교
정상 행만 저장 거절 행을 제외하고 유지 성공·거절 수와 재처리 대상
단계별 부분 취소 정해진 구간만 되돌림 SAVEPOINT 경계 검사

처음부터 정상 행만 저장하려는 업무라면 전체 취소 패턴을 강제할 필요는 없습니다. 다만 사용자에게 일부 성공을 명확히 알려야 하고, 재시도 때 이미 저장한 행을 중복 생성하지 않아야 합니다.

트랜잭션 밖의 부작용

DB 롤백은 이미 전송한 이메일이나 외부 결제를 취소하지 않습니다. 커밋 전에 외부 호출을 섞으면 DB는 취소됐는데 외부 동작은 완료된 상태가 생길 수 있습니다. 운영에서는 커밋과 후속 작업을 분리하고 재시도 가능한 작업 기록을 설계합니다. 이 메모리 DB 예제는 여러 프로세스의 잠금 경합이나 장애 후 복구까지 시험하지 않습니다.

참고 자료