Sqlite
其他 | Miscellaneous

SQLite Backup API

Using the SQLite Online Backup API

从历史上看,使用以下方法创建SQLite数据库的备份(副本):

  • 使用SQLite API(即shell工具)在数据库文件上建立一个共享锁。

  • 使用外部工具(例如unix'cp'实用程序或DOS'copy'命令)复制数据库文件。

  • 放弃步骤1中获取的数据库文件的共享锁。

这个程序在许多情况下运作良好,通常速度非常快。但是,这种技术有以下缺点:

  • 任何希望在创建备份时写入数据库文件的数据库客户端都必须等到共享锁被放弃。

  • 它不能用于将数据复制到内存数据库或从内存数据库复制数据。

  • 如果复制数据库文件时发生电源故障或操作系统故障,系统恢复后备份数据库可能会损坏。

在线备份API是为了解决这些问题而创建的。联机备份API允许将一个数据库的内容复制到另一个数据库中,覆盖目标数据库的原始内容。复制操作可以逐步完成,在这种情况下,源数据库不需要在复制过程中被锁定,仅在真正被读取的短暂时间段内被锁定。这允许其他数据库用户在进行在线数据库的备份时不中断地继续。

这里记录了在线备份API。本页面的其余部分包含两个C语言示例,说明API的常见用法及其讨论。阅读这些示例不能替代阅读API文档!

Example 1: Loading and Saving In-Memory Databases

/* ** 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()分配的资源。

Error handling

如果三个主要备份API例程中的任何一个发生错误,则错误代码和消息将附加到目标数据库连接。此外,如果sqlite3_backup_step()遇到错误,则错误代码由sqlite3_backup_step()调用本身以及随后对sqlite3_backup_finish()的调用返回。因此,对sqlite3_backup_finish()的调用不会通过sqlite3_backup_step()覆盖存储在目标数据库连接中的错误代码。示例代码使用此功能来减少所需的错误处理量。sqlite3_backup_step()和sqlite3_backup_finish()调用的返回值将被忽略,并且错误代码表示从目标数据库连接收集的复制操作成功或失败。

Possible Enhancements

至少可以通过以下两种方式来增强该功能的实施:

  • 无法获取数据库文件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()可以检测到这种情况,并尝试在调用联机备份API函数之前将内存数据库的页面大小设置为数据库zFilename的页大小。

Example 2: Online Backup of a Running Database

/* ** 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标识的备份数据库文件。

  • 使用参数5调用函数sqlite3_backup_step()以将5页数据库pDb复制到备份数据库(文件zFilename)。

  • 如果还有更多页面要从数据库pDb中复制,那么函数将休眠250毫秒(使用sqlite3_sleep()实用程序),然后返回到步骤2。

  • 函数sqlite3_backup_finish()被调用来清理由sqlite3_backup_init()分配的资源。

File and Database Connection Locking

在上述步骤3中的250 ms睡眠期间,数据库文件上没有读锁,并且与pDb关联的互斥锁未保持。这允许其他线程使用数据库连接pDb和其他连接来写入底层数据库文件。

如果在此函数处于休眠状态时另一个线程或进程将数据写入源数据库,则SQLite会检测到此情况,并且通常会在下一次调用sqlite3_backup_step()时重新启动备份进程。此规则有一个例外:如果源数据库不是内存数据库,并且写操作是在与备份操作相同的进程内执行的并使用相同的数据库句柄(pDb),则目标数据库(一个使用连接pFile打开)会随源一起自动更新。在sqlite3_sleep()调用返回后,备份过程可能会继续,如同没有任何事情发生一样。

无论备份过程是否由于写入源数据库中间备份而重新启动,用户都可以确保备份操作完成后,备份数据库将包含原始数据的一致和最新快照。然而:

  • 写入内存源数据库或通过外部进程或线程使用除pDb以外的数据库连接写入基于文件的源数据库比使用pDb对基于文件的源数据库写入要昂贵得多(如整个备份操作必须在前两种情况下重新启动)。

  • 如果备份进程足够频繁地重新启动,它可能永远不会运行完成,并且backupDb()函数可能永远不会返回。

backup_remaining() and backup_pagecount()

backupDb()函数使用sqlite3_backup_remaining()和sqlite3_backup_pagecount()函数通过用户提供的xProgress()回调报告其进度。函数sqlite3_backup_remaining()返回要复制的页数,sqlite3_backup_pagecount()返回源数据库中的页面总数(在本例中为由pDb打开的数据库)。因此,该过程的完成百分比可以计算为:

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

sqlite3_backup_remaining()和sqlite3_backup_pagecount()API将先前调用sqlite3_backup_step()的值存储起来,但它们实际上并不检查源数据库文件。这意味着如果在调用sqlite3_backup_step()之后但在使用sqlite3_backup_remaining()和sqlite3_backup_pagecount()返回的值之前,源数据库由另一个线程或进程写入,则这些值在技术上可能不正确。这通常不是问题。

SQLite is in the Public Domain.