Одно соединение — много файлов
Соединение SQLite не привязано к одному файлу. С помощью ATTACH DATABASE можно открыть рядом с основным ещё один или несколько .db-файлов и обращаться к ним так, словно это разные схемы внутри одной базы. Это самое близкое, что есть у SQLite, к «нескольким базам на одном сервере».
Базовый синтаксис:
Файл archive.db создастся, если его ещё нет — точно так же, как и основная база. С этого момента в текущем сеансе всё с префиксом archive. лежит во втором файле. Всё с main. (или без префикса) — в исходном.
В вашем соединении всегда есть две неявные схемы: main (файл, который вы открыли первым) и temp (черновое пространство для временных таблиц). ATTACH добавляет к ним свои.
Синтаксис и роль алиаса
ATTACH DATABASE 'path/to/file.db' AS alias_name;
Алиас — это имя схемы, по которому вы будете уточнять таблицы. Он живёт только внутри текущего соединения: другое соединение, открыв тот же файл, может назвать его иначе. Берите что-то короткое и по делу (archive, analytics, cache) — печатать его придётся часто.
Несколько важных моментов:
- Путь относительный, если только не указан абсолютный — отсчитывается от рабочего каталога процесса.
- Строка
':memory:'подключит свежую in-memory базу под этим алиасом. - Алиас не может конфликтовать с
mainилиtempи не должен повторяться между подключениями.
JOIN между базами
Это та самая фишка, ради которой обычно и используют ATTACH. Как только два файла открыты в одном соединении, их таблицы можно джойнить в одном запросе:
Планировщик относится к обеим схемам так же, как к таблицам в main. Индексы на подключённых таблицах используются. EXPLAIN QUERY PLAN работает между ними. Никаких сетевых издержек — оба файла открыты в одном процессе.
Это правда удобно: вынести «горячие» данные от «холодных» архивов, разнести данные арендаторов по файлам или подтянуть справочные данные из read-only справочника.
Read-only и in-memory подключения
Если вторая база нужна только на чтение (например, прибитый к релизу справочник) — подключите её в режиме read-only через URI:
URI-форма требует, чтобы у библиотеки SQLite был включён SQLITE_OPEN_URI (он включён в CLI и в большинстве языковых биндингов). После этого любая попытка INSERT, UPDATE или DELETE против ref.* упадёт с ошибкой ещё до записи в файл.
In-memory подключения тоже отлично подходят для промежуточных вычислений:
scratch исчезает при закрытии соединения. Это как temp, только временем жизни управляете вы.
Транзакции охватывают все подключённые базы
Один BEGIN/COMMIT покрывает запись и в main, и в каждую подключённую схему. Либо коммитится всё, либо откатывается всё — атомарность работает между файлами:
Перенос строк из живой таблицы в архивный файл — ровно та операция, где такая гарантия как раз и нужна. Без атомарности по файлам падение посередине оставит вас либо с дубликатами, либо, что хуже, с потерянными строками.
Один нюанс: когда в транзакции пишется больше чем в одну подключённую базу, SQLite использует более осторожный протокол коммита с временным журналом. Это медленнее, чем коммит по одному файлу, но всё ещё безопасно.
Отключение
Когда подключённая база больше не нужна — отключите её:
DETACH DATABASE archive;
Файл остаётся на диске нетронутым — DETACH просто закрывает дескриптор в текущем соединении. Два ограничения, которые надо помнить:
mainиtempотключить нельзя.- Нельзя отключить базу, которая сейчас в транзакции или у которой открыты statement-ы.
Забыли отключить — ничего страшного: при закрытии соединения всё подчистится само.
Лимиты и типичные ошибки
Полезные практические лимиты:
- По умолчанию — 10 подключённых баз на соединение (плюс
mainиtemp). Жёсткий максимум на этапе компиляции — 125. Упрётесь — получитеtoo many attached databases - max 10. - Каждая подключённая база занимает страничный кэш. Подцепить десяток крупных баз не бесплатно — RAM растёт.
- Сам
ATTACHнельзя выполнять внутри транзакции. Делайте его доBEGINили послеCOMMIT.
Несколько ошибок, на которые легко наткнуться:
-- Файла нет, и каталог недоступен на запись:
Error: unable to open database: 'missing/path.db'
-- Попытались писать в read-only подключение:
Error: attempt to write a readonly database
-- Использовали один и тот же алиас дважды:
Error: database archive is already in use
Большинство из них становятся очевидными, как только вы их прочитаете. Спотыкаются обычно на «already in use» — ATTACH не подменяет существующий алиас; сначала нужно сделать DETACH.
Реалистичный паттерн: разделение на горячие и холодные
Соберём всё вместе — небольшой архивный сценарий, который выносит из основной базы заказы старше года:
Старые строки уезжают в archive.orders, свежие остаются в main. Отчёты, которым нужна история, могут джойнить обе. Повседневные запросы к main.orders работают быстро, потому что таблица меньше. То же соединение, два файла, одна транзакция.
Дальше: подготовленные выражения
ATTACH — это про то, чтобы дать одному соединению доступ к большему объёму данных. Следующий блок тем — про то, как приложения общаются с SQLite безопасно и эффективно, и начинается он с подготовленных выражений (prepared statements) — фундамента для биндинга параметров и защиты от инъекций.
Часто задаваемые вопросы
Что делает ATTACH DATABASE в SQLite?
ATTACH DATABASE 'file.db' AS alias открывает второй файл базы внутри текущего соединения и даёт ему имя схемы. После этого к таблицам можно обращаться как alias.table_name и в одном запросе джойнить их с таблицами основной базы.
Сколько баз SQLite можно подключить одновременно?
По умолчанию SQLite разрешает до 10 подключённых баз на одно соединение, плюс схемы main и temp. Жёсткий потолок — 125, настраивается на этапе компиляции через SQLITE_MAX_ATTACHED. Упрётесь в лимит — получите ошибку too many attached databases.
Можно ли в одном запросе обратиться сразу к нескольким подключённым базам?
Да. После ATTACH уточняйте каждую таблицу её схемой — SELECT * FROM main.users JOIN archive.orders ON .... Между схемами работают и JOIN-ы, и подзапросы, и INSERT ... SELECT. Транзакции тоже охватывают все подключённые базы, так что COMMIT атомарен по нескольким файлам сразу.
Как отключить базу в SQLite?
Выполните DETACH DATABASE alias. Сам файл на диске остаётся целым — DETACH лишь закрывает дескриптор в текущем соединении. main и temp отключить нельзя, как и базу, которая в данный момент участвует в транзакции.