운영 중 sqlite3에서 database is locked가 보이면 저는 timeout부터 키우기보다 어느 연결이 잠금을 오래 잡았는지 먼저 궁금해해요. timeout은 잠시 기다리는 시간만 늘릴 뿐, 끝나지 않은 트랜잭션이나 겹치는 쓰기 구조를 정리하지는 않거든요. 잠금을 보유한 연결과 동시 writer를 확인한 뒤 트랜잭션을 짧게 끝내고, 필요하면 쓰기를 직렬화하는 순서가 안전합니다.
예약 실행과 수동 실행이 같은 DB 파일을 쓰는 구조를 떠올려 보면, 누가 먼저 열었는지보다 트랜잭션이 언제 끝나는지가 더 중요한 단서가 돼요. 아래 내용은 Python과 SQLite 공식 문서에 적힌 동작을 기준으로 그 순서를 좁혀 봅니다.
database is locked는 무엇을 뜻하나
SQLite는 여러 연결의 읽기를 허용할 수 있지만, 한 데이터베이스 파일에서 동시에 진행되는 쓰기 트랜잭션은 하나뿐입니다. 다른 연결이 이미 변경 중이면 새 쓰기가 잠금 해제까지 기다리거나 실패할 수 있습니다. 따라서 이 오류는 단순히 “파일을 다른 프로그램이 열었다”는 뜻으로 단정하기보다 다른 프로세스·스레드의 미완료 트랜잭션, commit·rollback·close 누락, 여러 writer의 겹침을 나눠 확인해야 합니다.
먼저 확인할 트랜잭션과 connection 수명
모든 connect() 호출에서 연결을 연 뒤 SQL을 실행하고, 성공하면 commit, 예외면 rollback, 작업이 끝나면 close하는 경로가 있는지 확인합니다. Python connection context manager는 정상 종료 시 commit하고 예외 시 rollback할 수 있지만 connection 자체를 닫아 주지는 않습니다.
import sqlite3
def save_item(db_path: str, name: str) -> None:
con = sqlite3.connect(db_path, timeout=5)
try:
with con:
con.execute("INSERT INTO items(name) VALUES (?)", (name,))
finally:
con.close()
이 코드는 실행 결과를 주장하는 예제가 아니라 성공·예외·종료 경계를 드러내는 기본 형태입니다. 실제 코드에서는 긴 계산이나 네트워크 호출을 쓰기 트랜잭션 안에 넣지 않는지 함께 살펴보십시오.
timeout은 어디까지 도움이 되나
sqlite3.connect(timeout=5)의 timeout은 잠긴 테이블을 기다리는 시간입니다. 잠금이 곧 해제되는 짧은 경합에는 도움이 될 수 있지만, rollback 누락·connection 장기 보유·계속 겹치는 writer를 해결하지는 않습니다. 값을 무작정 크게 하면 오류가 늦게 드러나고 예약 작업이 더 오래 대기할 수 있습니다.
isolation_level과 스레드 접근 구분하기
isolation_level은 DEFERRED, EXCLUSIVE, IMMEDIATE, None 등의 방식으로 암시적 트랜잭션 동작을 제어합니다. 운영 중인 코드의 모드를 모른 채 바꾸면 잠금 시점이 달라질 수 있으므로, 먼저 connection 생성·종료, 쓰기 시작·끝, commit·rollback, 예외를 기록하는 편이 안전합니다.
check_same_thread=True는 connection을 만든 스레드 외 사용을 제한합니다. False로 바꾼다고 쓰기 직렬화가 자동으로 보장되지는 않습니다. 여러 스레드가 같은 파일에 쓰는 구조라면 connection 소유권과 writer 조정 방식을 별도로 정해야 합니다. 로그 파일이 남지 않는 문제까지 겹쳤다면 파이썬 로그 파일이 안 생기는 이유와 logging 설정 점검법을 다음 점검 글로 참고할 수 있습니다.
동시에 쓰는 작업은 직렬화할 수 있는가
예약 실행과 수동 실행이 같은 파일에 동시에 쓰면 timeout을 늘려도 충돌이 반복될 수 있습니다. 작업 큐를 통해 writer를 하나로 제한하거나, 애플리케이션에서 쓰기 순서를 보장하는 구조를 검토해야 합니다. 이 글에서는 WAL 세부 튜닝보다 먼저 트랜잭션 경계와 writer 수를 확인하는 데 초점을 둡니다.
SQLite를 바꿔야 하는 조건
writer가 빠르게 작업하고 차례로 진행할 수 있다면 SQLite를 계속 사용할 여지가 있습니다. 반대로 동시에 많은 writer가 필요하고 기다릴 수 없거나, 네트워크를 통해 많은 클라이언트가 같은 DB에 접근해야 한다면 client/server 방식의 외부 데이터베이스를 검토할 조건입니다. 데이터 양 하나보다 writer 수와 대기 허용 여부가 더 직접적인 판단 기준입니다.
재현 시 점검 순서
저는 같은 .db 파일을 쓰는 예약 작업과 수동 실행이 겹치는지부터 확인해요. connection 생성 시각, 쓰기 SQL의 시작과 끝, 성공 경로의 commit, 예외 경로의 rollback, 마지막 close()까지 기록하면 잠금이 어느 구간에서 이어지는지 좁힐 수 있습니다.
여기까지 정리해도 writer가 겹친다면 애플리케이션에서 순서를 보장하고, 실제로 짧은 경합만 남았을 때 timeout을 조정하는 게 순서예요.
결국 database is locked의 첫 처방은 큰 timeout이 아니에요. 잠금 보유 연결의 트랜잭션을 짧게 만들고, commit·rollback·close를 분명히 하며, 동시 쓰기를 직렬화할 수 있는지 확인하는 게 먼저예요. 지금 같은 오류가 반복된다면 timeout 숫자보다 어떤 작업이 같은 DB 파일을 열고 있는지 기록부터 살펴보세요.