SQLite database is locked 에러 완벽 해결 가이드 (Python 코드 포함)

SQLite를 사용하는 파이썬 애플리케이션에서 'database is locked' 에러를 마주한 적이 있습니까? 이 에러는 SQLite의 파일 기반 잠금 메커니즘에서 비롯되며, 멀티스레드 환경이나 트랜잭션 처리 미흡 시 빈번하게 발생합니다. 이 글에서는 에러의 근본 원인과 실무에서 즉시 적용 가능한 해결책을 코드 레벨로 제시합니다.


SQLitedatabaseislocked에러완벽해결가이드(Python코드포함)




1. 'database is locked' 에러가 발생하는 3가지 원인

SQLite는 파일 단위 잠금(File-level Locking)을 사용하기 때문에, 한 프로세스가 쓰기 작업 중일 때 다른 프로세스의 접근이 차단됩니다. 주요 원인은 다음과 같습니다.

  • 트랜잭션 미종료: conn.commit() 또는 conn.rollback() 없이 연결을 유지한 채 다른 작업을 시도할 때
  • 멀티스레드 동시 접근: 여러 스레드가 동일한 DB 파일에 동시에 쓰기를 시도할 때 (기본 timeout 5초 초과 시 에러 발생)
  • 커넥션 미종료: conn.close()를 호출하지 않아 파일 핸들이 해제되지 않은 상태에서 재접속 시도

SQLite는 단일 Writer 모델을 채택하므로, 동시에 하나의 쓰기 작업만 허용됩니다. 읽기는 여러 프로세스가 동시에 가능하지만, 쓰기 작업 중에는 읽기도 대기해야 합니다.

2. 해결 방법 1: timeout 파라미터 조정

가장 간단한 해결책은 sqlite3.connect() 호출 시 timeout 값을 늘리는 것입니다. 기본값 5초를 초과하는 대기 시간을 설정하면, 잠금이 해제될 때까지 대기합니다.

import sqlite3

# timeout을 30초로 설정
conn = sqlite3.connect('mydb.db', timeout=30.0)
cursor = conn.cursor()

try:
    cursor.execute('INSERT INTO students (name, age) VALUES (?, ?)', ('Alice', 20))
    conn.commit()
except sqlite3.OperationalError as e:
    print(f"에러 발생: {e}")
    conn.rollback()
finally:
    conn.close()

주의사항: timeout을 무한정 늘리는 것은 데드락 상황에서 애플리케이션이 무한 대기할 수 있으므로, 적절한 값(10~30초)을 설정하고 예외 처리를 반드시 구현하십시오.

3. 해결 방법 2: WAL 모드 활성화

SQLite 3.7.0 이상에서는 WAL(Write-Ahead Logging) 모드를 지원합니다. 이 모드에서는 쓰기 작업이 별도의 WAL 파일에 기록되므로, 읽기와 쓰기가 동시에 가능합니다.

import sqlite3

conn = sqlite3.connect('mydb.db', timeout=30.0)
cursor = conn.cursor()

# WAL 모드 활성화 (DB 파일당 한 번만 설정하면 영구 적용)
cursor.execute('PRAGMA journal_mode=WAL')

# 이후 정상적으로 작업 수행
cursor.execute('CREATE TABLE IF NOT EXISTS students (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, age INTEGER)')
conn.commit()
conn.close()
  • 장점: 읽기 성능 향상, 동시성 개선 (여러 Reader + 단일 Writer 동시 실행 가능)
  • 단점: DB 파일 외에 -wal, -shm 파일이 추가 생성됨. 네트워크 파일 시스템(NFS)에서는 권장하지 않음

권장 사항: 로컬 디스크 환경에서 읽기 빈도가 높은 애플리케이션이라면 WAL 모드를 기본으로 설정하십시오.

4. 해결 방법 3: Connection Pool과 트랜잭션 관리

멀티스레드 환경에서는 스레드당 별도의 커넥션을 생성하거나, check_same_thread=False 옵션과 함께 락(Lock) 메커니즘을 구현해야 합니다.

import sqlite3
import threading

# 스레드 안전성을 위한 Lock 객체
db_lock = threading.Lock()

def insert_data(name, age):
    with db_lock:  # 락 획득
        conn = sqlite3.connect('mydb.db', timeout=30.0)
        cursor = conn.cursor()
        try:
            cursor.execute('INSERT INTO students (name, age) VALUES (?, ?)', (name, age))
            conn.commit()
        except sqlite3.OperationalError as e:
            print(f"Thread {threading.current_thread().name}: {e}")
            conn.rollback()
        finally:
            conn.close()

# 멀티스레드 실행
threads = []
for i in range(10):
    t = threading.Thread(target=insert_data, args=(f'Student{i}', 20+i))
    threads.append(t)
    t.start()

for t in threads:
    t.join()

핵심 포인트:

  1. with db_lock으로 한 번에 하나의 스레드만 DB 접근 보장
  2. 각 스레드가 작업 완료 후 conn.close()로 즉시 연결 해제
  3. try-except-finally 블록으로 예외 발생 시에도 커넥션이 반드시 닫히도록 구현

실무 팁: Flask나 FastAPI 같은 웹 프레임워크에서는 요청마다 커넥션을 생성하고 응답 후 즉시 닫는 패턴을 사용하십시오. 커넥션 풀링이 필요한 대용량 환경이라면 SQLite 대신 PostgreSQL이나 MySQL로 마이그레이션을 권장합니다.

5. 추가 체크리스트: 트랜잭션 명시적 관리

SQLite는 기본적으로 autocommit 모드가 비활성화되어 있습니다. 명시적으로 트랜잭션을 제어하면 락 해제 시점을 정확히 관리할 수 있습니다.

conn = sqlite3.connect('mydb.db', isolation_level=None)  # autocommit 모드
cursor = conn.cursor()

# 명시적 트랜잭션 시작
cursor.execute('BEGIN')
try:
    cursor.execute('UPDATE students SET age = ? WHERE name = ?', (21, 'Alice'))
    cursor.execute('DELETE FROM students WHERE name = ?', ('Bob',))
    cursor.execute('COMMIT')
except Exception as e:
    cursor.execute('ROLLBACK')
    print(f"롤백 실행: {e}")
finally:
    conn.close()
  • isolation_level=None: autocommit 모드 활성화 (각 쿼리가 즉시 커밋됨)
  • BEGIN으로 명시적 트랜잭션 시작 시, COMMIT 또는 ROLLBACK으로 반드시 종료해야 락 해제

요약 및 Action Item

SQLite의 'database is locked' 에러는 파일 잠금 메커니즘, 트랜잭션 미종료, 멀티스레드 동시 접근에서 발생합니다. timeout 증가, WAL 모드 활성화, 스레드별 커넥션 분리와 락 사용으로 해결 가능합니다. 지금 당장 실행할 작업: 기존 코드에서 conn.commit()conn.close() 호출 여부를 점검하고, 멀티스레드 환경이라면 PRAGMA journal_mode=WAL을 즉시 적용하십시오. 동시 접속이 100 TPS 이상 예상된다면 SQLite를 벗어나 PostgreSQL 마이그레이션을 검토하는 것이 장기적으로 합리적입니다.





# 함께 보면 좋은 글

댓글