learn

SQLite 기초 — 연결, cursor, 그리고 "파일 DB"의 본질

SQLite 기초 — 연결, cursor, 그리고 "파일 DB"의 본질

SQLite는 서버가 없다. 포트도 없고, 데몬도 없다. 그냥 파일이다.

연결과 초기화 순서

init_db를 짤 때 순서가 헷갈리기 쉬운데, 올바른 순서는:

  1. 연결 (sqlite3.connect()) → 파일이 없으면 이 시점에 자동 생성
  2. 테이블 생성 (CREATE TABLE IF NOT EXISTS)

"DB 생성"이 별도 단계가 아니라 connect() 안에 녹아 있는 것이다.

def init_db():
    conn = sqlite3.connect("mukyon.db")  # 연결 = db 파일 생성
    cursor = conn.cursor()
    cursor.execute("""
        CREATE TABLE IF NOT EXISTS jobs (
            id INTEGER PRIMARY KEY,
            status TEXT NOT NULL
        )
    """)
    conn.commit()
    conn.close()

IF NOT EXISTS가 핵심이다. 이게 있어야 init_db멱등성을 가진다 — 앱을 재시작해도 기존 데이터를 건드리지 않고 몇 번 호출해도 결과가 같다.

conn과 cursor의 역할

  • conn — DB 파일과의 연결 그 자체. 트랜잭션 관리(commit, rollback)도 여기서.
  • cursor — 그 연결 위에서 SQL을 실제로 실행하는 창구. 쿼리 실행과 결과 fetch는 cursor를 통해.
cursor.execute("SELECT * FROM jobs WHERE status = ?", ("pending",))
rows = cursor.fetchall()

cursor를 여러 개 만들 수도 있다. 같은 conn에서 독립적으로 쿼리를 날릴 수 있어, 하나의 작업이 끝나길 기다리지 않고 다른 쿼리를 날릴 때 유용하다.

포트가 없다

PostgreSQL(5432), MySQL(3306)은 별도 서버 프로세스가 떠서 네트워크로 통신한다. SQLite는 그런 개념 자체가 없다. 연결도 "파일 경로"로 한다.

sqlite3.connect("mukyon.db")        # 로컬
sqlite3.connect("/data/mukyon.db")  # 절대 경로

SQLite가 맞는 상황

"DB 탈을 쓴 파일 관리자"

내부적으로는 B-tree 구조로 데이터를 파일에 직렬화하는 것이다. 이 특성 때문에 적합한 상황이 명확하다:

  • 단일 프로세스 앱 (CLI, 로컬 앱, 임베디드)
  • JSON/YAML 대신 구조화된 로컬 저장소가 필요할 때
  • 테스트용 DB (파일 하나 지우면 리셋)

반대로 여러 프로세스/서버가 동시에 써야 한다면 PostgreSQL로 가야 한다. SQLite의 쓰기 잠금 구조는 동시 다중 writer를 지원하지 않는다.

conn을 어떻게 유지할까

두 패턴이 있다:

요청마다 열고 닫기 — 안전하지만 오버헤드 있음

def get_job(id):
    conn = sqlite3.connect("mukyon.db")
    # ...
    conn.close()

앱 시작 시 하나 유지 — SQLite는 파일 기반이라 연결 비용이 낮아서, 단일 프로세스 앱에선 이게 더 흔하다

conn = sqlite3.connect("mukyon.db")  # 전역 or 싱글턴으로 관리

어느 쪽이 맞는지는 앱 구조(CLI vs 서버)에 따라 다르다.