RussianLDP Рейтинг@Mail.ru
WebMoney: 
WMZ Z294115950220 
WMR R409981405661 
WME E134003968233 
Visa 
4274 3200 2453 6495 

Small. Fast. Reliable.
Choose any three.

Применение SQLite Online Backup API

Исторически, резервные копии баз данных SQLite были созданы, используя следующий метод:

  1. Установите коллективную блокировку на файле базы данных, используя API SQLite (т.е. инструмент оболочки).
  2. Скопируйте файл базы данных, используя внешний инструмент (например, unix 'cp' или DOS 'copy').
  3. Снимите коллективную блокировку на файле базы данных, полученную на шаге 1.

Эта процедура работает хорошо во многих сценариях и обычно очень быстра. Однако, у этой техники есть следующие недостатки:

  • Любые клиенты базы данных, желающие писать файл базы данных в то время, как резервная копия создается, должны ждать, пока коллективная блокировка не снята.
  • Это не может использоваться, чтобы скопировать данные к или от баз данных в памяти.
  • Если перебой в питании происходит, копируя файл базы данных, резервная база данных может быть испорчена после системного восстановления.

Online Backup API был создан, чтобы решить эти проблемы. Оnline backup API позволяет содержанию одной базы данных быть скопированным в другой файл базы данных, заменяя любое оригинальное содержание целевой базы данных. Операция по копии может быть сделана инкрементно, в этом случае исходная база данных не должна быть блокирована на время копии, а только в течение кратких промежутков времени, когда это на самом деле читается. Это позволяет другим пользователям базы данных продолжать работу без чрезмерных задержек, в то время как резервная копия базы данных делается.

Эффект завершения резервной последовательности состоит в том, чтобы сделать место назначения идентичной копией исходной базы данных. Место назначения становится образом или "snapshot".

Описание online backup API см. здесь. Остаток этой страницы содержит два примера на языке C, иллюстрирующие общее использование API и обсуждение этого. Чтение этих примеров не является никакой заменой для чтения документации API!

Обновление: команда VACUUM INTO введена в SQLite version 3.27.0 (2019-02-07) может служить альтернативой резервному API.

Пример 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 демонстрирует одно из самого простого и наиболее распространенного использования backup API: загрузка и сохранение содержания базы данных в памяти к файлу на диске. Резервный API используется следующим образом в этом примере:

  1. Функцмя sqlite3_backup_init() вызвана, чтобы создать объект sqlite3_backup, чтобы скопировать данные между этими двумя базами данных (от файла в базу данных в памяти или наоборот).
  2. Функция sqlite3_backup_step() вызвана с параметром -1, чтобы скопировать всю исходную базу данных к месту назначения.
  3. Функция sqlite3_backup_finish() вызвана, чтобы очистить ресурсы, ассигнованные sqlite3_backup_init().

Обработка ошибок

Если ошибка происходит в какой-либо из трех главных функций backup API, код ошибки и сообщение присоединены к связи целевой базы данных. Кроме того, если sqlite3_backup_step() сталкивается с ошибкой, то error code возвращен обоими вызовами: sqlite3_backup_step() и последующим обращением к sqlite3_backup_finish(). Таким образом, обращение к sqlite3_backup_finish() не переписывает error code, сохраненный в связи целевой базы данных через sqlite3_backup_step() . Эта функция используется в примере кода, чтобы уменьшить объем требуемой обработки ошибок. Возвращаемые значения sqlite3_backup_step() и sqlite3_backup_finish() проигнорированы и код ошибки, указывающий на успешность или неуспешность операции по копии, из связи целевой базы данных позже.

Возможные улучшения

Внедрение этой функции могло быть улучшено по крайней мере двумя способами:

  1. Сбой при получении блокировки на файле базы данных zFilename (ошибка SQLITE_BUSY) мог быть обработан, и
  2. Случаи, где размеры страницы базы данных pInMemory и zFilename отличаются, могли быть обработаны лучше.

Так как база данных zFilename является файлом на диске, тогда к этому может получить доступ внешне другой процесс. Это означает, что когда вызов sqlite3_backup_step() пытается читать из него или писать данные в него, это может не получить необходимую блокировку файла. Если это произойдет, это внедрение потерпит неудачу, возвратив SQLITE_BUSY немедленно. Решение состояло бы в том, чтобы зарегистрировать отзыв обработчика или тайм-аут с database connection 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-функции.

Пример 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(). Это требует удерживания блокировки чтения на исходном файле базы данных на время операции, препятствуя тому, чтобы любой другой пользователь базы данных писал в базу данных. Это также считает mutex связанным с базой данных pInMemory всюду по копии, препятствуя тому, чтобы любой иной поток использовал его. Функция C в этой секции, разработанной, чтобы быть вызванной фоновым потоком или процессом для создания резервной копии базы данных онлайн, избегает этих проблем, используя следующий подход:

  1. Функция sqlite3_backup_init() вызвана, чтобы создать объект sqlite3_backup, чтобы скопировать данные из базы данных pDb к резервному файлу базы данных, определенному zFilename.
  2. Функция sqlite3_backup_step() вызвана с параметром 5, чтобы скопировать 5 страниц базы данных pDb к резервной базе данных (файл zFilename).
  3. Если есть еще больше страниц, чтобы скопировать из базы данных pDb, то функция спит 250 миллисекунд (используя sqlite3_sleep()) и возвращается к шагу 2.
  4. Функция sqlite3_backup_finish() вызвана, чтобы очистить ресурсы, ассигнованные sqlite3_backup_init().

Файл и захват соединения с базой данных

Во время сна 250 ms на шаге 3 никакая блокировка чтения не проводится в файл базы данных, и mutex, связанный с pDb, не хранится. Это позволяет другим потокам использовать database connection pDb и другие связи, чтобы написать основной файл базы данных.

Если другой поток или процесс пишут исходную базу данных в то время, как эта функция спит, то SQLite обнаруживает это и обычно перезапускает процесс резервного копирования, когда sqlite3_backup_step() вызвана снова. Есть одно исключение к этому правилу: Если исходная база данных не база данных в памяти, запись выполняется из того же самого процесса, что и операция резервного копирования, и использует тот же самый обработчик базы данных (pDb), то целевая база данных (открытая, используя связь pFile), автоматически обновляется наряду с источником. Процесс резервного копирования может тогда быть продолжен после sqlite3_sleep(), как будто ничего не произошло.

Перезапущен ли процесс резервного копирования в результате, записи исходной базы данных в середину резервной копии, пользователь может быть уверен, что, когда операция резервного копирования закончена, резервная база данных содержит последовательный и актуальный снимок оригинала. Однако:

  • Запись исходной базе данных в памяти или в основанную на файле исходную базу данных внешним процессом или потоком, используя соединение с базой данных кроме pDb значительно более дорогие, чем запись сделанная в основанную на файле исходную базе данных, используя pDb (поскольку вся операция резервного копирования должна быть перезапущена в этих двух ситуациях).
  • Если процесс резервного копирования перезапускается достаточно часто, он никогда может не дойти до завершения и функция backupDb() может никогда не возвращаться.

backup_remaining() и backup_pagecount()

Функция backupDb() использует sqlite3_backup_remaining() и sqlite3_backup_pagecount(), чтобы сообщить о ходе работы через переданный пользователем отзыв xProgress(). Функция sqlite3_backup_remaining() возвращает число страниц, оставшихся для изготовления копии, а sqlite3_backup_pagecount() возвращает общее количество страниц в исходной базе данных (в этом случае база данных, открытая pDb). Таким образом, процент завершения процесса может быть вычислено как:

Completion = 100% * (pagecount() - remaining()) / pagecount()

API sqlite3_backup_remaining() и sqlite3_backup_pagecount() вернут значения, сохраненные предыдущим вызовом sqlite3_backup_step(), они на самом деле не просматривают исходный файл базы данных. Это означает, что, если исходная база данных написана другим потоком или процессом после вызова sqlite3_backup_step(), но прежде, чем значения, возвращенные sqlite3_backup_remaining() и sqlite3_backup_pagecount() применены, значения могут быть технически неправильными. Это обычно не проблема.