하나의 커넥션으로 여러 DB 파일 다루기
SQLite 커넥션은 파일 하나에만 묶여 있지 않습니다. ATTACH DATABASE를 쓰면 처음 연 데이터베이스 옆에 다른 .db 파일들을 추가로 붙여서, 마치 한 데이터베이스 안의 여러 스키마처럼 자유롭게 조회할 수 있죠. SQLite에서 "한 서버에 여러 데이터베이스"에 가장 가까운 기능이 바로 이 sqlite ATTACH DATABASE입니다.
기본 문법은 다음과 같습니다:
archive.db 파일은 메인 데이터베이스와 마찬가지로, 없으면 새로 만들어집니다. 이번 세션 동안 archive.이 붙은 객체는 두 번째 파일에 속하고, main.이 붙어 있거나 접두사가 없는 객체는 원래 파일에 속합니다.
연결(connection)에는 항상 두 개의 스키마가 암묵적으로 따라붙습니다. 처음에 연 파일인 main, 그리고 임시 테이블을 두는 작업 공간인 temp입니다. ATTACH는 여기에 스키마를 더 얹는 동작이라고 보면 됩니다.
ATTACH DATABASE 문법과 별칭(alias)의 역할
ATTACH DATABASE 'path/to/file.db' AS alias_name;
별칭은 테이블을 한정할 때 사용하는 스키마 이름입니다. 현재 커넥션에만 유효하기 때문에, 같은 파일을 attach하는 다른 커넥션은 다른 별칭을 골라도 됩니다. 어차피 자주 타이핑하게 되니 archive, analytics, cache처럼 짧고 직관적인 이름으로 정하는 게 좋습니다.
알아두면 좋은 몇 가지:
- 절대 경로가 아니라면 파일 경로는 프로세스의 작업 디렉터리 기준입니다.
- 문자열
':memory:'을 쓰면 해당 별칭으로 새로운 in-memory 데이터베이스가 attach됩니다. - 별칭은
main이나temp와 겹칠 수 없고, 같은 커넥션 안에서 중복될 수도 없습니다.
여러 데이터베이스 조인하기
대부분 ATTACH DATABASE를 쓰는 이유가 바로 이것입니다. 두 파일이 같은 커넥션에 올라와 있으면, 하나의 쿼리 안에서 두 파일의 테이블을 자유롭게 조인할 수 있습니다:
쿼리 플래너는 두 스키마를 main에 있는 테이블과 똑같이 취급합니다. attach된 테이블의 인덱스도 그대로 활용되고, EXPLAIN QUERY PLAN도 양쪽을 넘나들며 동작합니다. 네트워크 왕복 같은 건 없고, 두 파일 모두 같은 프로세스 안에서 열려 있는 것뿐이니까요.
핫 데이터와 콜드 아카이브를 분리하거나, 테넌트별로 파일을 나누거나, 읽기 전용 룩업 DB에 참조 데이터를 따로 빼두는 식으로 쓰면 정말 유용합니다.
읽기 전용 attach와 in-memory database 연결
두 번째 데이터베이스를 읽기만 하고 절대 수정할 일이 없다면 — 예를 들어 배포에 함께 포함되는 참조 데이터셋이라면 — URI를 사용해서 sqlite 읽기 전용 attach로 연결하면 됩니다:
URI 형식을 사용하려면 SQLite 라이브러리에서 SQLITE_OPEN_URI 옵션이 켜져 있어야 합니다(CLI나 대부분의 언어 바인딩에서는 기본적으로 활성화되어 있습니다). 이렇게 attach해 두면 ref.*에 대한 INSERT, UPDATE, DELETE는 파일에 손대기도 전에 곧바로 에러로 막힙니다.
데이터를 임시로 가공할 때는 in-memory 데이터베이스를 attach하는 방식도 그에 못지않게 유용합니다:
scratch는 연결이 닫히는 순간 함께 사라집니다. temp와 비슷하지만, 수명을 직접 제어할 수 있다는 점이 다릅니다.
트랜잭션은 연결된 모든 데이터베이스를 아우른다
BEGIN/COMMIT 한 번이면 main은 물론 attach해 둔 모든 스키마에 대한 쓰기까지 한꺼번에 묶입니다. 전부 커밋되거나, 전부 롤백되거나 — 파일이 여러 개로 흩어져 있어도 원자성은 그대로 보장됩니다:
라이브 테이블에서 아카이브 파일로 행을 옮기는 작업이야말로 이런 보장이 꼭 필요한 대표적인 상황입니다. 파일 간 원자성이 없다면 작업 도중에 크래시가 났을 때 중복 행이 생기거나, 최악의 경우 행이 통째로 사라질 수도 있습니다.
한 가지 주의할 점이 있습니다. 하나의 트랜잭션에서 여러 개의 attached 데이터베이스에 쓰기를 수행하면, SQLite는 좀 더 보수적인 커밋 프로토콜을 사용하며 임시 저널 파일을 만듭니다. 단일 파일 커밋보다는 느리지만, 안전성은 그대로 보장됩니다.
DETACH DATABASE 사용법
연결한 데이터베이스를 다 쓰고 나면 깔끔하게 분리해 줍니다:
DETACH DATABASE archive;
디스크에 있는 파일 자체는 그대로 있고, DETACH는 현재 커넥션이 들고 있던 핸들만 닫아줍니다. 기억해 둘 제약은 두 가지입니다.
main과temp는 detach할 수 없습니다.- 트랜잭션이 진행 중이거나 해당 DB를 대상으로 열려 있는 statement가 있으면 detach할 수 없습니다.
깜빡하고 detach하지 않아도 큰 문제는 없습니다. 커넥션이 닫히면 알아서 정리됩니다.
한도와 자주 마주치는 에러
알아두면 좋은 실무적인 한도들입니다.
- 기본값은 커넥션당 attach 가능한 DB 10개입니다(
main과temp는 별도). 컴파일 타임 최대치는 125개고요. 한도를 넘기면too many attached databases - max 10에러를 만나게 됩니다. - attach된 파일마다 페이지 캐시를 따로 씁니다. 큰 DB를 여러 개 붙이면 그만큼 메모리도 늘어나니 공짜가 아닙니다.
ATTACH자체는 트랜잭션 안에서 실행할 수 없습니다.BEGIN전이나COMMIT후에 실행하세요.
자주 마주치게 될 에러 몇 가지를 살펴봅시다.
-- ファイルが存在せず、ディレクトリが書き込み可能でない場合:
Error: unable to open database: 'missing/path.db'
-- 読み取り専用のアタッチメントに書き込もうとした場合:
Error: attempt to write a readonly database
-- 同じエイリアスを2回使用した場合:
Error: database archive is already in use
대부분은 한 번 읽어보면 바로 이해되는 에러들이다. 사람들이 자주 걸려 넘어지는 건 "already in use" 에러인데, ATTACH는 이미 있는 별칭을 덮어쓰지 않으니까 먼저 DETACH로 떼어내야 한다.
실전 패턴: 핫/콜드 데이터 분리
지금까지 배운 걸 합쳐서, 1년이 지난 주문 데이터를 메인 DB 바깥으로 옮기는 작은 아카이브 워크플로우를 만들어 보자.
오래된 주문은 archive.orders로 옮기고, 최근 데이터는 main에 그대로 둡니다. 이력까지 봐야 하는 리포트는 두 DB를 조인해서 처리하면 되고, 일상적인 쿼리는 main.orders만 보면 되니 테이블이 작아서 빠르게 동작하죠. 커넥션 하나, 파일 두 개, 트랜잭션은 하나로 묶이는 구조입니다.
다음 주제: Prepared Statement
ATTACH가 하나의 커넥션에 더 많은 데이터를 붙여주는 기능이었다면, 이어지는 주제들은 애플리케이션이 SQLite와 어떻게 안전하고 효율적으로 대화할지에 관한 이야기입니다. 그 출발점이 바로 prepared statement인데요, 파라미터 바인딩과 SQL 인젝션 방어의 기반이 되는 핵심 개념입니다.
자주 묻는 질문
SQLite에서 ATTACH DATABASE는 무슨 일을 하나요?
ATTACH DATABASE 'file.db' AS alias를 실행하면 현재 커넥션 안에서 또 다른 SQLite 파일을 열고, 거기에 스키마 이름을 붙여줍니다. 이후부터는 alias.table_name 형태로 그 안의 테이블에 접근할 수 있고, 메인 DB의 테이블과 한 쿼리에서 조인해서 쓸 수 있습니다.
한 커넥션에 SQLite 데이터베이스를 몇 개까지 붙일 수 있나요?
기본값은 커넥션당 첨부 DB 10개이고, 여기에 main과 temp 스키마가 추가로 붙습니다. 컴파일 시 SQLITE_MAX_ATTACHED 옵션으로 늘릴 수 있지만 상한은 125개입니다. 한도를 넘기면 too many attached databases 에러가 떨어집니다.
여러 SQLite DB를 하나의 쿼리에서 조인할 수 있나요?
네, 가능합니다. 일단 ATTACH만 해두면 SELECT * FROM main.users JOIN archive.orders ON ... 처럼 테이블 앞에 스키마 이름만 붙여주면 됩니다. JOIN, 서브쿼리, INSERT ... SELECT 모두 스키마를 가로질러 동작하고, 트랜잭션도 첨부된 모든 DB를 포함하기 때문에 COMMIT 한 번으로 파일들 전체에 원자적으로 반영됩니다.
첨부한 SQLite DB는 어떻게 떼어내나요?
DETACH DATABASE alias를 실행하면 됩니다. 디스크의 파일 자체는 건드리지 않고 현재 커넥션의 핸들만 닫는 방식입니다. 단, main과 temp는 떼어낼 수 없고, 트랜잭션이 진행 중인 DB도 DETACH 할 수 없습니다.