프렌들리 가이드: SQLite 오류 코드 뽀개기 (BUSY, LOCKED, CONSTRAINT)

2025-10-14

SQLite에서는 함수 호출 결과로 결과 코드 (Result Code)를 반환하는데요, 이 중 문제가 있음을 나타내는 코드들을 오류 코드 (Error Code)라고 부릅니다. 코드를 확인해서 문제가 없으면 다음 작업을 진행하고, 문제가 생겼다면 적절한 대처를 해야 합니다.

코드 (Code)상수 (Constant)의미 (Meaning)
0SQLITE_OK성공! 작업이 성공적으로 완료되었습니다. (가장 많이 보게 될 코드예요 )
1SQLITE_ERROR일반적인 오류. 구문 오류 등 다른 특정 오류 코드가 없을 때 사용됩니다.
5SQLITE_BUSY바쁨! 다른 연결/프로세스에서 데이터베이스 파일에 락(Lock)이 걸려 잠시 접근할 수 없을 때 발생합니다.
6SQLITE_LOCKED잠김! 동일한 연결 내에서 락(Lock) 충돌이 발생했을 때 나타납니다.
19SQLITE_CONSTRAINT제약 조건 위반! NOT NULL이나 UNIQUE, FOREIGN KEY 등 테이블에 설정된 제약 조건을 위반했을 때 발생합니다.
100SQLITE_ROWsqlite3_step() 함수 호출 시 결과 행이 준비되었음을 나타냅니다. (오류가 아닙니다!)
101SQLITE_DONEsqlite3_step() 함수 호출 시 모든 결과 행 처리가 완료되었음을 나타냅니다. (오류가 아닙니다!)

<hr>

문제
여러 스레드나 프로세스가 동시에 데이터베이스에 쓰기 작업을 하려고 할 때 발생합니다. 데이터베이스가 "바쁘다"고 응답하는 거죠.

해결책 재시도 로직 (Retry Logic)을 구현하거나 Busy Handler를 설정합니다.

import sqlite3
import time

DB_NAME = 'test.db'

def safe_db_operation(sql, params=()):
    """
    SQLITE_BUSY 오류 발생 시 일정 시간 기다린 후 재시도하는 함수
    """
    max_retries = 5
    delay = 1  # 1초 대기 후 재시도
    
    for attempt in range(max_retries):
        try:
            conn = sqlite3.connect(DB_NAME)
            cursor = conn.cursor()
            
            cursor.execute(sql, params)
            conn.commit()
            
            print(f" 시도 {attempt + 1}: 성공적으로 실행되었습니다.")
            conn.close()
            return True
            
        except sqlite3.OperationalError as e:
            # SQLITE_BUSY (5) 또는 SQLITE_LOCKED (6) 관련 오류 처리
            if "database is locked" in str(e):
                print(f" 시도 {attempt + 1}: 데이터베이스 잠금 발생. {delay}초 후 재시도합니다...")
                time.sleep(delay)
                delay *= 2 # 대기 시간을 늘려서 충돌 확률을 줄일 수 있어요 (지수 백오프)
            else:
                # 다른 OperationalError는 즉시 보고
                print(f" 데이터베이스 작업 오류: {e}")
                conn.close()
                return False
                
        except Exception as e:
            print(f" 알 수 없는 오류 발생: {e}")
            conn.close()
            return False

    print(f" 최대 재시도 횟수({max_retries}회) 초과로 작업 실패.")
    return False

# 사용 예시
safe_db_operation("INSERT INTO users (name) VALUES (?)", ("Alice",))


Python의 sqlite3.connect() 함수에 timeout 인자를 설정하면, 해당 시간(초) 동안 자동으로 재시도를 시도하게 할 수 있어 더 간단합니다! (기본값은 5초예요)

문제
테이블에 데이터를 넣거나 수정하려는데, 테이블 정의 시 설정한 제약 조건 (예
고유해야 하는 값, NULL을 허용하지 않는 값 등)을 위반했을 때 발생합니다.

해결책
쿼리를 실행하기 전 입력 데이터의 유효성 검사를 하거나, 오류 발생 시 예외 처리를 통해 사용자에게 정확한 정보를 제공해야 합니다.

import sqlite3

DB_NAME = 'test_constraint.db'

# 테이블 생성 (UNIQUE 제약 조건 포함)
def setup_db():
    conn = sqlite3.connect(DB_NAME)
    cursor = conn.cursor()
    cursor.execute("""
        CREATE TABLE IF NOT EXISTS products (
            id INTEGER PRIMARY KEY,
            name TEXT NOT NULL UNIQUE, -- 이름은 NULL이 아니어야 하고, 고유해야 함!
            price REAL
        )
    """)
    conn.commit()
    conn.close()

def insert_product(name, price):
    conn = sqlite3.connect(DB_NAME)
    cursor = conn.cursor()
    
    try:
        # 첫 번째 성공적인 삽입
        cursor.execute("INSERT INTO products (name, price) VALUES (?, ?)", (name, price))
        conn.commit()
        print(f" '{name}' 제품이 성공적으로 추가되었습니다.")
        
    except sqlite3.IntegrityError as e:
        # SQLITE_CONSTRAINT 오류가 여기에 해당됩니다.
        if "UNIQUE constraint failed" in str(e):
            print(f" 오류: 제품 이름 '{name}'은 이미 존재합니다. 고유해야 합니다! (SQLITE_CONSTRAINT)")
        elif "NOT NULL constraint failed" in str(e):
             print(f" 오류: 제품 이름은 비워둘 수 없습니다. (SQLITE_CONSTRAINT)")
        else:
            print(f" 무결성 오류 발생: {e}")
            
    finally:
        conn.close()

setup_db()
insert_product("Laptop Pro", 1200.00)
insert_product("Laptop Pro", 1500.00) # <- UNIQUE 제약 조건 위반!
insert_product(None, 50.00)           # <- NOT NULL 제약 조건 위반!

<hr>

대부분의 프로그래밍 언어에서 제공하는 SQLite 바인딩(Python의 sqlite3처럼)은 내부적으로 이런 코드들을 사용하지만, 직접 코드를 다룰 때는 C 언어 API의 함수들을 알아두면 좋습니다.

sqlite3_errcode(db)
가장 최근에 발생한 주요 결과 코드 (예
5, 19)를 반환합니다.

sqlite3_extended_errcode(db)
더 상세한 정보를 제공하는 확장 오류 코드를 반환합니다. 예를 들어 SQLITE_CONSTRAINT는 여러 세부적인 제약 조건 위반을 포함할 수 있습니다.

sqlite3_errmsg(db)
오류에 대한 사람이 읽을 수 있는 설명 텍스트 (영어)를 반환합니다. 오류의 원인을 파악하는 데 가장 유용합니다!


sqlite



SQLITE_VTAB_DIRECTONLY, 왜 쓰고 왜 문제인가? (트리거/뷰 사용 제한 해설)

SQLITE_VTAB_DIRECTONLY는 SQLite의 가상 테이블(Virtual Table) 메커니즘과 관련된 설정 플래그 중 하나입니다.가상 테이블은 SQLite 데이터베이스 안에 있지만, 실제 데이터는 외부 소스(파일 시스템


SQLite CTE(공통 테이블 식) 사용법, 흔한 실수 및 대체 쿼리 전략 완벽 가이드

CTE는 하나의 SQL 문장 내에서만 사용할 수 있는 임시적인 명명된 결과 집합이에요. 마치 쿼리 안에서만 존재하는 임시 뷰(View)나 서브쿼리(Subquery) 같은 역할을 합니다.SQLite에서 CTE는 WITH 절을 사용하여 정의하며


SQLITE_TRACE_STMT, PROFILE, ROW: SQLite 내부 동작 엿보기

SQLite의 C API에는 sqlite3_trace_v2()라는 함수가 있어요. 이 함수를 사용하면 데이터베이스 연결(sqlite3*)에서 발생하는 특정 이벤트에 대한 콜백 함수를 등록할 수 있습니다. 이때, 어떤 종류의 이벤트를 추적할지 지정하는 데 사용되는 상수가 바로 SQL Trace Event Codes입니다



SQLite Checksum VFS Shim 사용 시 흔한 오류(reserve_bytes)와 애플리케이션 레벨 무결성 검증

SQLite Checksum VFS Shim은 이름 그대로 SQLite의 VFS (Virtual File System) 계층에 체크섬(Checksum) 기능을 추가하는 일종의 Shim(덧대기/중계) 확장 기능입니다


Windows에서 SQLite를 안전하게 쓰는 법: AV_RETRY 설정과 트러블 슈팅 가이드

이 기능은 Windows 환경에서 SQLite를 사용할 때 매우 유용하며, 주로 백신 프로그램으로 인해 발생하는 파일 접근 문제를 해결하기 위해 도입된 기능입니다.이 코드는 sqlite3_file_control() 함수에 사용되는 파일 제어 코드(File Control Opcode) 중 하나입니다


SQLite의 printf() 함수: 흔한 문제와 안전한 대체 코드 예시

SQLite의 내장 printf() 함수는 C 언어의 printf 함수와 매우 유사하게 작동하여, 포맷 문자열(pattern)에 따라 데이터를 원하는 형식의 문자열로 만들어주는 기능을 SQL 문 내에서 수행할 수 있게 해줍니다


SQLite C 인터페이스: 스크래치 메모리(SCRATCH_SIZE) 과부하 해결과 대안 코드 예시

SQLITE_STATUS_SCRATCH_SIZE는 SQLite 라이브러리 내에서 스크래치 메모리(scratch memory)의 최대 크기를 모니터링하는 데 사용되는 상태 측정값 중 하나입니다.스크래치 메모리는 SQLite가 쿼리 실행 중이나 다른 내부 작업을 수행할 때 단기적으로 임시 데이터를 저장하기 위해 사용하는 메모리 블록을 의미합니다


SQLite 메모리 할당 함수 구현 시 흔한 트러블슈팅과 안전한 대체 코드 예시

이 루틴들은 일반적으로 기본 C 라이브러리의 malloc(), realloc(), free() 대신 사용할 사용자 정의 메모리 관리 시스템을 SQLite에 연결할 때 사용됩니다.친절하고 자세하게, 흔한 문제점과 대체 방법의 예시 코드를 한국어로 설명해 드릴게요!


SQLite 커스텀 페이지 캐시(Page Cache) xShutdown() 완벽 가이드: 문제 해결 및 샘플 코드

SQLite는 데이터베이스 파일의 I/O 성능을 높이기 위해 페이지 캐시(Page Cache)라는 메모리 영역을 사용합니다. 기본적으로 SQLite는 내장된 페이지 캐시를 사용하지만, 특별한 성능 요구사항이 있거나 특정 환경에 맞추어야 할 경우 사용자가 sqlite3_pcache_methods2 구조체를 통해 자신만의 페이지 캐시 구현을 등록할 수 있습니다


SQLite API 마이그레이션: sqlite3_open()에서 sqlite3_open_v2()로 전환하는 이유와 방법

더 이상 사용되지 않는 함수는 이전 버전과의 호환성을 위해 남아있지만, 새로운 코드에서는 사용하지 않도록 권장되는 함수를 말해요. 보통 더 안전하고 효율적이거나 기능이 개선된 대체 함수가 있기 때문에 사용을 지양해야 합니다