API резервного копирования SQLite
Содержание
1. Использование API онлайн-резервного копирования SQLite
Исторически резервные копии (копии) баз данных SQLite создавались следующим методом:
- Установление совместной блокировки файла базы данных с помощью API SQLite (например, утилиты командной строки).
- Копирование файла базы данных с помощью внешней утилиты (например, утилиты unix 'cp' или команды DOS 'copy').
- Освобождение совместной блокировки файла базы данных, полученной на шаге 1.
Эта процедура хорошо работает во многих сценариях и обычно очень быстрая. Однако у этого метода есть следующие недостатки:
- Любые клиенты базы данных, желающие записать в файл базы данных во время создания резервной копии, должны ждать, пока совместная блокировка не будет освобождена.
- Его нельзя использовать для копирования данных в или из баз данных в памяти.
- Если во время копирования файла базы данных произойдет отключение питания или ошибка операционной системы, резервная база данных может быть повреждена после восстановления системы.
API онлайн-резервного копирования был создан для решения этих проблем. API онлайн-резервного копирования позволяет копировать содержимое одной базы данных в другой файл базы данных, заменяя любое исходное содержимое целевой базы данных. Копирование может выполняться инкрементально, в этом случае исходная база данных не должна блокироваться на все время копирования, только на короткие промежутки времени, когда она фактически считывается. Это позволяет другим пользователям базы данных продолжить работу без чрезмерных задержек во время создания резервной копии онлайн-базы данных.
Эффектом завершения последовательности вызовов резервного копирования является создание битового идентичного копии исходной базы данных, как она была в момент начала копирования. (Целевое становится "моментальным снимком".)
API онлайн-резервного копирования документирован здесь. Остальная часть этой страницы содержит два примера на языке C, иллюстрирующие общее использование API и обсуждения по нему. Чтение этих примеров не заменяет чтение документации к API!
1.1. Другие методы резервного копирования
API онлайн-резервного копирования — это оригинальный метод резервного копирования активных баз данных SQLite. Другие более новые методы достижения той же цели включают:
Команда VACUUM INTO создаст вакуумную копию активной базы данных SQLite в отдельный файл.
Программа sqlite3_rsync создает копию активной базы данных SQLite на удаленный компьютер или с него с использованием SSH-соединения.
2. Пример 1: Загрузка и сохранение баз данных в памяти
/*
** This function is used to load the contents of a database file on disk
** into the "main" database of open database connection pInMemory, or
** to save the current contents of the database opened by pInMemory into
** a database file on disk. pInMemory is probably an in-memory database,
** but this function will also work fine if it is not.
**
** Parameter zFilename points to a nul-terminated string containing the
** name of the database file on disk to load from or save to. If parameter
** isSave is non-zero, then the contents of the file zFilename are
** overwritten with the contents of the database opened by pInMemory. If
** parameter isSave is zero, then the contents of the database opened by
** pInMemory are replaced by data loaded from the file zFilename.
**
** If the operation is successful, SQLITE_OK is returned. Otherwise, if
** an error occurs, an SQLite error code is returned.
*/
int loadOrSaveDb(sqlite3 *pInMemory, const char *zFilename, int isSave){
int rc; /* Function return code */
sqlite3 *pFile; /* Database connection opened on zFilename */
sqlite3_backup *pBackup; /* Backup object used to copy data */
sqlite3 *pTo; /* Database to copy to (pFile or pInMemory) */
sqlite3 *pFrom; /* Database to copy from (pFile or pInMemory) */
/* Open the database file identified by zFilename. Exit early if this fails
** for any reason. */
rc = sqlite3_open(zFilename, &pFile);
if( rc==SQLITE_OK ){
/* If this is a 'load' operation (isSave==0), then data is copied
** from the database file just opened to database pInMemory.
** Otherwise, if this is a 'save' operation (isSave==1), then data
** is copied from pInMemory to pFile. Set the variables pFrom and
** pTo accordingly. */
pFrom = (isSave ? pInMemory : pFile);
pTo = (isSave ? pFile : pInMemory);
/* Set up the backup procedure to copy from the "main" database of
** connection pFile to the main database of connection pInMemory.
** If something goes wrong, pBackup will be set to NULL and an error
** code and message left in connection pTo.
**
** If the backup object is successfully created, call backup_step()
** to copy data from pFile to pInMemory. Then call backup_finish()
** to release resources associated with the pBackup object. If an
** error occurred, then an error code and message will be left in
** connection pTo. If no error occurred, then the error code belonging
** to pTo is set to SQLITE_OK.
*/
pBackup = sqlite3_backup_init(pTo, "main", pFrom, "main");
if( pBackup ){
(void)sqlite3_backup_step(pBackup, -1);
(void)sqlite3_backup_finish(pBackup);
}
rc = sqlite3_errcode(pTo);
}
/* Close the database connection opened on database file zFilename
** and return the result of this function. */
(void)sqlite3_close(pFile);
return rc;
}
Приведенная справа функция C демонстрирует одно из самых простых и наиболее распространённых применений API резервного копирования: загрузка и сохранение содержимого базы данных в памяти в файл на диске. API резервного копирования используется следующим образом в этом примере:
- Вызов функции sqlite3_backup_init() для создания объекта sqlite3_backup для копирования данных между двумя базами данных (либо из файла в базу данных в памяти, либо наоборот).
- Вызов функции sqlite3_backup_step() с параметром
-1для копирования всей исходной базы данных в целевую. - Вызов функции sqlite3_backup_finish() для очистки ресурсов, выделенных функцией sqlite3_backup_init().
2.1. Обработка ошибок
Если в любой из трёх основных процедур API резервного копирования возникает ошибка, то код ошибки и сообщение присоединяются к целевому подключению к базе данных. Кроме того, если функция sqlite3_backup_step() обнаруживает ошибку, то код ошибки возвращается как самой функцией sqlite3_backup_step(), так и последующим вызовом sqlite3_backup_finish(). Поэтому вызов sqlite3_backup_finish() не перезаписывает код ошибки, сохранённый в целевом подключении к базе данных функцией sqlite3_backup_step(). Эта функция используется в примере кода для уменьшения необходимого объёма обработки ошибок. Значения возврата функций sqlite3_backup_step() и sqlite3_backup_finish() игнорируются, а код ошибки, указывающий на успех или неудачу операции копирования, собирается из целевого подключения к базе данных впоследствии.
2.2. Возможные улучшения
Реализацию этой функции можно улучшить по крайней мере двумя способами:
- Не удалось получить блокировку файла базы данных zFilename (ошибка SQLITE_BUSY) может быть обработана, и
- Случаи, когда размеры страниц базы данных pInMemory и zFilename отличаются, могут быть обработаны лучше.
Поскольку база данных zFilename — это файл на диске, к нему может получить доступ другой процесс. Это означает, что когда функция sqlite3_backup_step() пытается прочитать или записать данные в него, она может не получить необходимую блокировку файла. Если это произойдёт, данная реализация завершится ошибкой, вернув SQLITE_BUSY сразу. Решением было бы зарегистрировать обработчик занятости или тайм-аут с подключением к базе данных pFile с помощью sqlite3_busy_handler() или sqlite3_busy_timeout(), как только он будет открыт. Если ему не удастся получить необходимую блокировку сразу, sqlite3_backup_step() использует любой зарегистрированный обработчик занятости или тайм-аут таким же образом, как и sqlite3_step() или sqlite3_exec().
Обычно неважно, отличаются ли размеры страниц исходной и целевой базы данных перед перезаписью содержимого целевой. Размер страницы целевой базы данных просто изменяется в ходе операции резервного копирования. Исключением является случай, если целевая база данных является базой данных в памяти. В этом случае, если размеры страниц не совпадают в начале операции резервного копирования, операция завершится с ошибкой SQLITE_READONLY. К сожалению, это может произойти при загрузке изображения базы данных из файла в базу данных в памяти с помощью функции loadOrSaveDb().
Однако, если база данных в памяти pInMemory только что открыта (и поэтому полностью пуста) перед передачей в функцию loadOrSaveDb(), то всё ещё возможно изменить её размер страницы с помощью команды SQLite "PRAGMA page_size". Функция loadOrSaveDb() могла бы обнаружить этот случай и попытаться установить размер страницы базы данных в памяти на размер страницы базы данных zFilename перед вызовом функций онлайн-резервного копирования API.
3. Пример 2: Онлайн-резервное копирование работающей базы данных
/*
** Perform an online backup of database pDb to the database file named
** by zFilename. This function copies 5 database pages from pDb to
** zFilename, then unlocks pDb and sleeps for 250 ms, then repeats the
** process until the entire database is backed up.
**
** The third argument passed to this function must be a pointer to a progress
** function. After each set of 5 pages is backed up, the progress function
** is invoked with two integer parameters: the number of pages left to
** copy, and the total number of pages in the source file. This information
** may be used, for example, to update a GUI progress bar.
**
** While this function is running, another thread may use the database pDb, or
** another process may access the underlying database file via a separate
** connection.
**
** If the backup process is successfully completed, SQLITE_OK is returned.
** Otherwise, if an error occurs, an SQLite error code is returned.
*/
int backupDb(
sqlite3 *pDb, /* Database to back up */
const char *zFilename, /* Name of file to back up to */
void(*xProgress)(int, int) /* Progress function to invoke */
){
int rc; /* Function return code */
sqlite3 *pFile; /* Database connection opened on zFilename */
sqlite3_backup *pBackup; /* Backup handle used to copy data */
/* Open the database file identified by zFilename. */
rc = sqlite3_open(zFilename, &pFile);
if( rc==SQLITE_OK ){
/* Open the sqlite3_backup object used to accomplish the transfer */
pBackup = sqlite3_backup_init(pFile, "main", pDb, "main");
if( pBackup ){
/* Each iteration of this loop copies 5 database pages from database
** pDb to the backup database. If the return value of backup_step()
** indicates that there are still further pages to copy, sleep for
** 250 ms before repeating. */
do {
rc = sqlite3_backup_step(pBackup, 5);
xProgress(
sqlite3_backup_remaining(pBackup),
sqlite3_backup_pagecount(pBackup)
);
if( rc==SQLITE_OK || rc==SQLITE_BUSY || rc==SQLITE_LOCKED ){
sqlite3_sleep(250);
}
} while( rc==SQLITE_OK || rc==SQLITE_BUSY || rc==SQLITE_LOCKED );
/* Release resources allocated by backup_init(). */
(void)sqlite3_backup_finish(pBackup);
}
rc = sqlite3_errcode(pFile);
}
/* Close the database connection opened on database file zFilename
** and return the result of this function. */
(void)sqlite3_close(pFile);
return rc;
}
Функция, представленная в предыдущем примере, копирует всю исходную базу данных в одном вызове sqlite3_backup_step(). Это требует удержания блокировки чтения на файле исходной базы данных на всё время операции, что препятствует записи в базу данных другим пользователям. Она также удерживает мьютекс, связанный с базой данных pInMemory на протяжении всего копирования, что препятствует использованию её другими потоками. Функция C в этом разделе, предназначенная для вызова фоновым потоком или процессом для создания резервной копии активной базы данных, избегает этих проблем, используя следующий подход:
- Вызов функции sqlite3_backup_init() для создания объекта sqlite3_backup для копирования данных из базы данных pDb в резервную базу данных, идентифицированную файлом zFilename.
- Вызов функции sqlite3_backup_step() с параметром 5 для копирования 5 страниц базы данных pDb в резервную базу данных (файл zFilename).
- Если ещё есть страницы для копирования из базы данных pDb, функция приостанавливается на 250 миллисекунд (используя утилиту sqlite3_sleep()) и возвращается к шагу 2.
- Вызов функции sqlite3_backup_finish() для очистки ресурсов, выделенных функцией sqlite3_backup_init().
3.1. Блокировка файла и подключения к базе данных
Во время 250-миллисекундной паузы на шаге 3 выше блокировка чтения не удерживается на файле базы данных, и мьютекс, связанный с pDb, не удерживается. Это позволяет другим потокам использовать подключение к базе данных pDb и другим подключениям для записи в базу данных.
Если другой поток или процесс записывает в исходную базу данных, пока эта функция находится в режиме ожидания, SQLite обнаруживает это и обычно перезапускает процесс резервного копирования, когда функция sqlite3_backup_step() вызывается в следующий раз. Есть одно исключение из этого правила: Если исходная база данных не является базой данных в памяти, и запись выполняется внутри того же процесса, что и операция резервного копирования, и использует тот же дескриптор базы данных (pDb), то целевая база данных (которая открывается с помощью подключения pFile) автоматически обновляется вместе с исходной. Затем процесс резервного копирования может быть продолжен после возвращения вызова sqlite3_sleep(), как если бы ничего не произошло.
Независимо от того, перезапускается ли процесс резервного копирования в результате записей в исходную базу данных во время резервного копирования, пользователь может быть уверен, что когда операция резервного копирования завершится, резервная база данных будет содержать согласованный и обновлённый моментальный снимок оригинала. Однако:
- Запись в базу данных в памяти или в базу данных на основе файлов внешним процессом или потоком с использованием подключения к базе данных, отличного от pDb, значительно дороже, чем запись в базу данных на основе файлов с использованием pDb (поскольку вся операция резервного копирования должна быть перезапущена в первых двух случаях).
- Если процесс резервного копирования перезапускается достаточно часто, он может никогда не завершиться, и функция backupDb() может никогда не вернуть результат.
3.2. backup_remaining() и backup_pagecount()
Функция backupDb() использует функции sqlite3_backup_remaining() и sqlite3_backup_pagecount() для отображения своего прогресса через предоставленный пользователем обратный вызов xProgress(). Функция sqlite3_backup_remaining() возвращает количество страниц, оставшихся для копирования, а sqlite3_backup_pagecount() возвращает общее количество страниц в исходной базе данных (в данном случае базы данных, открытой pDb). Таким образом, процент завершения процесса может быть рассчитан следующим образом:
Завершенность = 100% * (количество_страниц() - оставшиеся()) / количество_страниц()
API sqlite3_backup_remaining() и sqlite3_backup_pagecount() сообщают значения, сохранённые предыдущим вызовом sqlite3_backup_step(), они фактически не проверяют файл исходной базы данных. Это означает, что если исходная база данных записывается другим потоком или процессом после возврата вызова sqlite3_backup_step(), но до использования значений, возвращаемых sqlite3_backup_remaining() и sqlite3_backup_pagecount(), эти значения могут быть технически некорректными. Обычно это не проблема.
Эта страница была последним разом изменена 16.10.2024 11:05:34 UTC
SQLite is in the Public Domain.
https://sqlite.org/backup.html