Восстановить базу из бэкапа: PostgreSQL, MySQL, SQLite, DuckDB

Восстанавливать базу обычно приходится в спешке: данные удалены, миграция сломала схему, сервер не загружается. В этот момент легко сделать вторую ошибку — накатить дамп поверх рабочей базы и потерять то, что ещё можно было спасти. Ниже — команды восстановления для PostgreSQL, MySQL, SQLite и DuckDB, порядок действий, который не ухудшает ситуацию, и чек-лист.

Главное правило: сначала в новую базу

Не восстанавливайте поверх рабочей базы. Порядок такой:

  1. Сохраните текущее состояние, даже если оно испорчено: дамп или копию файла. В нём могут быть данные, появившиеся после последнего бэкапа.
  2. Восстановите бэкап в новую базу рядом: app_restore вместо app.
  3. Проверьте её: число строк в ключевых таблицах, свежие записи, работу приложения на ней.
  4. Переключите приложение на новую базу или переименуйте базы.

Так у вас всегда остаётся путь назад, а ошибка в дампе не уничтожает то, что было.

Сначала определите формат бэкапа

gzip -t app.sql.gz && echo "архив цел"
head -c 5 app.dump; echo                 # PGDMP — custom-формат pg_dump
gunzip -c app.sql.gz | head -n 20        # текстовый SQL: plain-дамп

Plain-дамп PostgreSQL начинается с комментария PostgreSQL database dump, дамп mysqldump — с MySQL dump. Custom-формат pg_dump начинается с сигнатуры PGDMP, directory-формат — это каталог с файлом toc.dat.

Если бэкап лежит в S3, скачайте его на сервер, где будете восстанавливать:

aws s3 cp s3://my-backups/pg/app-2026-09-17.sql.gz . \
  --endpoint-url https://storage.yandexcloud.net --region ru-central1
# Selectel: --endpoint-url https://s3.ru-1.storage.selcloud.ru --region ru-1

Как настроить регулярную выгрузку дампов в бакет, описано в статье pg_dump по cron в S3.

PostgreSQL: plain SQL через psql

Plain-дамп (pg_dump -Fp, формат по умолчанию) — это SQL-скрипт. pg_restore его не читает, восстанавливают его через psql:

createdb app_restore
psql -v ON_ERROR_STOP=1 --single-transaction -d app_restore -f app.sql

# сжатый дамп — через конвейер
gunzip -c app.sql.gz | psql -v ON_ERROR_STOP=1 --single-transaction -d app_restore
  • -v ON_ERROR_STOP=1 останавливает восстановление на первой ошибке. Без него psql продолжит выполнять скрипт, и вы получите базу без части таблиц, не заметив этого в длинном выводе.
  • --single-transaction выполняет весь дамп одной транзакцией: либо база восстановлена целиком, либо не изменилось ничего.

Типичные ошибки plain-восстановления:

  • role "app" does not exist. В дампе есть ALTER ... OWNER TO app. Создайте роли заранее (см. ниже) или делайте дамп с --no-owner --no-privileges — у psql таких флагов нет, это опции pg_dump.
  • Дамп сделан с --clean, но без --if-exists. На пустой базе первые же DROP упадут с ошибкой. Восстанавливайте в существующую базу или пересоздайте дамп с --clean --if-exists.
  • Дамп сделан с --create. В нём есть CREATE DATABASE и \connect, поэтому подключайтесь к служебной базе postgres: дамп сам создаст базу с исходным именем. Если база с таким именем уже есть, CREATE DATABASE упадёт — такой дамп удобнее восстанавливать на отдельном сервере или в контейнере.
  • Версии. Восстанавливайте psql той же или более новой версии, чем pg_dump. Свежие выпуски pg_dump (с августа 2025 года) добавляют в plain-дамп мета-команды \restrict/\unrestrict, которые старый psql не знает. Дамп с новой версии PostgreSQL на старый сервер может не встать из-за неизвестных параметров и синтаксиса.

PostgreSQL: custom и directory через pg_restore

pg_restore --list app.dump | head -n 30       # оглавление архива

createdb app_restore
pg_restore -d app_restore --no-owner --exit-on-error -j 4 app.dump

# directory-формат: тот же вызов, вместо файла — каталог
pg_restore -d app_restore --no-owner -j 4 ./app.dir
  • --no-owner создаёт объекты от имени пользователя, который запускает восстановление, — удобно, если ролей из исходной базы нет.
  • -j 4 восстанавливает данные и индексы в 4 потока. Работает для custom и directory, но не при чтении из stdin и не вместе с --single-transaction.
  • --clean --if-exists нужны, когда вы восстанавливаете в существующую базу: перед созданием объектов удаляются их старые версии. В новую пустую базу эти флаги не нужны.

pg_restore должен быть не старше pg_dump, которым сделан архив: старый pg_restore не прочитает архив нового формата.

Роли и права

Дамп одной базы не содержит ролей — они глобальные для кластера. Их выгружают отдельно:

pg_dumpall --globals-only > globals.sql   # роли, хеши паролей, табличные пространства
psql -v ON_ERROR_STOP=1 -d postgres -f globals.sql

В globals.sql есть хеши паролей, храните его так же аккуратно, как сами дампы. Если какие-то роли на целевом сервере уже существуют, ON_ERROR_STOP остановится на CREATE ROLE — уберите его для этого файла и проверьте вывод вручную.

Переключение на восстановленную базу

Самый простой вариант — поменять строку подключения приложения на app_restore. Если имя базы зашито во много мест, переименуйте базы. Для этого к ним не должно быть подключений:

SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE datname IN ('app', 'app_restore') AND pid <> pg_backend_pid();

ALTER DATABASE app RENAME TO app_broken;
ALTER DATABASE app_restore RENAME TO app;

После восстановления выполните ANALYZE (или vacuumdb --analyze-in-stages -d app), чтобы планировщик получил статистику, — иначе первые часы запросы могут идти неудачными планами.

Одна таблица из бэкапа

Чаще всего нужна не вся база, а одна таблица: кто-то выполнил DELETE без WHERE.

Из custom-архива:

createdb app_restore
pg_restore -d app_restore --no-owner -t orders app.dump

pg_restore -t восстанавливает только определение таблицы и её данные — без индексов, ограничений и объектов, от которых таблица зависит (схем, типов). Для точного контроля сохраните оглавление pg_restore -l app.dump > toc.txt, оставьте нужные строки и передайте его через -L toc.txt.

Из plain-дампа выдернуть одну таблицу надёжно нельзя — проще восстановить весь дамп во временную базу и перенести строки:

pg_dump -d app_restore -t public.orders --data-only | psql -v ON_ERROR_STOP=1 -d app

Эта команда вставит все строки таблицы и упадёт на дубликатах первичного ключа. Если нужно вернуть только удалённые строки, выгрузите их во временную таблицу и перенесите INSERT ... SELECT ... WHERE NOT EXISTS.

MySQL и MariaDB

mysql -e "CREATE DATABASE app_restore CHARACTER SET utf8mb4"
mysql --default-character-set=utf8mb4 app_restore < app.sql

# сжатый дамп
gunzip -c app.sql.gz | mysql --default-character-set=utf8mb4 app_restore

Логин и пароль держите в ~/.my.cnf с правами 600, а не в командной строке: -pSECRET виден в списке процессов.

На что смотреть:

  • --databases и --all-databases. Если дамп сделан с этими флагами, внутри есть CREATE DATABASE и USE app. Такой дамп восстановится в исходную базу app, а не в app_restore, который вы указали в команде. Проверьте grep -m 5 -n '^USE' app.sql до запуска.
  • Кодировка. Если дамп в utf8mb4, а клиент подключается в latin1, кириллица превратится в мусор без единой ошибки. Явно указывайте --default-character-set.
  • --force. Флаг продолжает восстановление после ошибок. Иногда он нужен, но результатом может стать база без части таблиц. Если используете его, пишите ошибки в лог и читайте этот лог.
  • GTID. Дамп с сервера, где включён GTID, содержит SET @@GLOBAL.GTID_PURGED. На сервере с собственной историей GTID это может закончиться ошибкой. Для переноса на другой сервер делайте дамп с --set-gtid-purged=OFF.
  • Пользователи и гранты в дамп одной базы не входят — создайте их на целевом сервере отдельно.

Одна таблица: восстановите дамп во временную базу на том же сервере и перенесите нужные строки запросом INSERT INTO app.orders SELECT * FROM app_restore.orders WHERE .... Не переливайте таблицу через mysqldump app_restore orders | mysql app: по умолчанию в дампе есть DROP TABLE IF EXISTS, и рабочая таблица будет заменена целиком.

Подробнее о том, как делать такие дампы регулярно, — в статье mysqldump: автоматизация и ротация.

SQLite

SQLite-база — это файл, поэтому восстановление — это замена файла при остановленном приложении:

sudo systemctl stop app
cd /srv/app/data
gunzip -c /backups/app-2026-09-17.db.gz > app.db.restore
sqlite3 app.db.restore "PRAGMA integrity_check;"      # должно быть: ok
mkdir -p ../broken && mv app.db app.db-wal app.db-shm ../broken/ 2>/dev/null || true
mv app.db.restore app.db
sudo systemctl start app

Старые -wal и -shm нельзя оставлять рядом с восстановленным файлом: журнал от прежней базы может быть применён к новой и испортить её. Как делать согласованные копии SQLite, — в статье бэкап SQLite без повреждения.

DuckDB

Файл .duckdb восстанавливается так же, как SQLite: остановите все процессы, которые его открывают, уберите в сторону старый файл и app.duckdb.wal, положите копию на место.

Если бэкап сделан через EXPORT DATABASE, это каталог со схемой и данными. Импортируйте его в новый пустой файл:

duckdb /data/app_restore.duckdb -c "IMPORT DATABASE '/backups/export-2026-09-17'"

EXPORT DATABASE + IMPORT DATABASE полезны и при обновлении DuckDB: файл, записанный новой версией, старая версия может не открыть, а экспорт в CSV или Parquet от формата хранения не зависит. Подробности — в статье бэкап DuckDB-файлов.

Сколько это займёт

Точных цифр без вашей базы никто не даст. Ориентиры качественные:

  • замена файла SQLite или DuckDB — время на скачивание и копирование;
  • plain-дамп PostgreSQL или MySQL выполняется последовательно, а построение индексов и проверка ограничений часто занимают больше времени, чем вставка данных;
  • custom или directory с -j обычно быстрее на многоядерном сервере;
  • самый медленный этап при первом реальном восстановлении — поиск нужного файла, ключей доступа и команд. Его сокращает только заранее отрепетированный тест.

Замерьте время на тестовом восстановлении и запишите в runbook. Как это автоматизировать, описано в статье как проверить, что бэкап рабочий.

Чек-лист

До:

  • остановить запись: приложение, воркеры, cron-задачи;
  • сохранить текущее состояние базы, даже испорченное;
  • выбрать версию бэкапа: дата, размер, контрольная сумма;
  • проверить версии клиента (psql, pg_restore, mysql) и свободное место на диске.

Во время:

  • восстанавливать в новую базу или новый файл;
  • использовать ON_ERROR_STOP=1 и --single-transaction для psql, --exit-on-error для pg_restore;
  • писать вывод в лог: ... 2>&1 | tee restore.log.

После:

  • сравнить число строк в ключевых таблицах и свежесть данных (max(created_at));
  • проверить последовательности, права ролей, ANALYZE;
  • переключить приложение и проверить его логи;
  • не удалять старую базу, пока не убедитесь, что всё работает;
  • записать, что пошло не так, и поправить процесс бэкапа.

Если базу испортил ИИ-агент или неудачная миграция, полезно прочитать и что делать, если ИИ стёр базу данных.

Как это сделать в dbsend

dbsend хранит версии файлов и дампов, которые вы загружаете: SQLite и DuckDB-файлы, plain-дампы PostgreSQL и MySQL. Сервис не подключается к вашему серверу БД и не применяет дамп за вас: восстановление — это скачивание нужной версии, а psql или mysql вы запускаете сами.

dbsend log                                        # история версий
dbsend diff <fromSnapshotId> <toSnapshotId>       # что изменилось между версиями
dbsend restore <snapshotId> ./restore/app.sql --yes
psql -v ON_ERROR_STOP=1 --single-transaction -d app_restore -f ./restore/app.sql

dbsend restore скачивает файл во временный, сверяет SHA-256 и только потом атомарно переименовывает. Для SQLite дополнительно выполняется PRAGMA integrity_check, а старые -wal/-shm рядом с целевым путём удаляются (с --yes) — бэкапы SQLite можно восстанавливать прямо на место остановленной базы. При ошибке проверки CLI завершается с кодом 6.

Diff между любыми двумя версиями (схема, таблицы, число строк) помогает выбрать версию до инцидента, а не наугад; он доступен на платных тарифах, подробности на странице тарифов. Все команды — в документации CLI.

Частые вопросы

Чем pg_restore отличается от psql при восстановлении?

psql выполняет plain-дамп как обычный SQL-скрипт. pg_restore читает только архивные форматы pg_dump (custom, directory, tar) и умеет восстанавливать выборочно и в несколько потоков. Plain .sql через pg_restore восстановить нельзя.

Можно ли восстановить дамп PostgreSQL на сервер другой версии?

На более новую версию — как правило, да, это обычный способ обновления. На более старую — не гарантируется: дамп может содержать синтаксис и параметры, которых старый сервер не знает. Клиентские утилиты берите той же или более новой версии, чем pg_dump.

Как восстановить только одну таблицу?

Из custom-архива — pg_restore -t имя_таблицы в новую базу. Из plain-дампа — восстановите всё во временную базу и перенесите нужные строки запросом. В MySQL — тоже через временную базу и INSERT ... SELECT.

Нужно ли останавливать приложение?

Для SQLite и DuckDB — обязательно, иначе процесс продолжит писать в старый файл или журнал. Для PostgreSQL и MySQL при восстановлении в новую базу останавливать не нужно; остановка понадобится в момент переключения.

Что делать, если восстановление упало посередине?

При --single-transaction база осталась пустой — исправьте причину и запустите заново. Без неё удалите частично восстановленную базу и начните с чистой: досоздавать недостающие объекты вручную дольше и опаснее.