1.备份分类

1.1文件级备份

直接复制数据库文件(例如  .db文件)的副本。SQLite数据库本质上是一个单一的文件,使得其备份起来相对简单。操作相对来说也比较简单,在数据库关闭时,直接复制文件即可(如使用 cp 命令在UNIX系统上,或使用文件管理工具)。在数据库打开时,可以使用SQLite的备份机制或事务日志数据保持一致性。

优点:操作简单,适用于快速备份和文件恢复。
缺点:如果数据库处于正在被使用或者处于活动状态,可能需要确保数据一致性(使用WAL模式或备份机制)

#include <iostream>
#include <fstream>
#include <string>

/* 
    打开源文件和目标文件,将源文件的内容复制到目标文件中
    ifstream,ofstream用于文件的读写操作,ios::binary(以二进制的方式)
*/
bool copyFile(const std::string& sourcePath, const std::string& destPath) {
    std::ifstream src(sourcePath, std::ios::binary);
    if (!src) {
        std::cerr << "Error opening source file: " << sourcePath << std::endl;
        return false;
    }

    std::ofstream dest(destPath, std::ios::binary);
    if (!dest) {
        std::cerr << "Error opening destination file: " << destPath << std::endl;
        return false;
    }

    dest << src.rdbuf(); // Copy content

    return src && dest;
}

int main() {
    std::string sourceFile = "source.db"; //替换为源文件路径
    std::string backupFile = "backup.db"; //替换为备份文件路径

    if (copyFile(sourceFile, backupFile)) {
        std::cout << "Backup successful: " << backupFile << std::endl;
    } else {
        std::cerr << "Backup failed." << std::endl;
    }

    return 0;
}

注:一致性:如果数据库在复制时处于活动状态,可能会导致备份文件不一致。考虑使用SQLite的在线备份工具或者在低流量时进行备份。
存储:将备份文件存储在安全的地方,并定期更新。
恢复:恢复时只需要备份文件替换现有数据库即可。

WAL模式(Write-Ahead Logging) 是一种数据库日志记录机制,在执行数据库写操作之前,首先将操作记录到日志中。这确保了数据的一致性和恢复能力,即使系统崩溃,也可以通过日志完成未完成的操作。
备份机制是指用来保护数据库数据的各种方法和策略,包括文件级备份(直接复制文件),(逻辑备份导出数据结构和内容),增量备份(只备份自上次备份以来的更改)等,以便在数据丢失或者损坏时进行修复。

1.2在线备份

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

1.使用SQLite API(即shell工具)在数据库文件上建立共享锁。
2.使用外部工具复制数据库文件(例如unix的 “cp”实用程序或DOS的“Copy”命令)。
3.解除数据库文件上的共享锁

这个过程在很多情况下都是很有效的,而且通常非常快。然而,这种技术有以下缺点:

任何希望在创建备份是写入数据库文件的数据库客户端都必须等待,直到共享锁被释放。
无法用于向内存数据库复制数据或从内存数据库复制数据。
如果在复制数据库文件时发生电源故障或者操作系统故障,则备份数据库可能会在系统回复后损坏。

为此SQLite创建了在线备份API来解决这些问题。在线备份API允许将一个数据库的内容复制到另一个数据库文件中,替换目标数据库的任何原有内容。复制操作可以增量完成,在这种情况下,源数据库在复制期间不需要被锁定,只需要在实际被读取的短暂时间内被锁定吗。这样就允许其他数据库用户在对在线数据库进行备份时继续操作,而不会出现过多的延迟。

完成备份调用序列的效果是使目标数据库成为源数据库的按位相同的副本,就像开始复制时一样。

SQLite提供了以下三个接口完成:将内存中数据库的内容加载并保存到磁盘上的一个文件中。

sqlite3_backup *sqlite3_backup_init(
  sqlite3 *pDest,         /* 目标数据库连接 */
  const char *zDestName,  /* 目标数据库的附加名称 */
  sqlite3 *pSource,       /* 源数据库连接 */
  const char *zSrcName    /* 源数据库的附加名称 */
);

注:目标数据库连接和源数据库连接必须不同,否则sqlite3_backup_init()将失败并返回错误。

如果目标数据库上已经打开了一个读或者读写事务,那么调用sqlite3_backup_init()将失败,返回NULL。

如果在sqlite3_backup_init()中发生错误,则返回NULL并将错误代码和错误信息存储在目标数据库连接(pDest)中。调用sqlite3_backup_init()失败的错误代码和消息可以使用sqlite3_errcode(),sqlite3_errmsg和sqlite3_errmsg16函数检索。

成功调用sqlite3_backup_init()会返回一个指向sqlite3备份对象的指针。

int sqlite3_backup_step(sqlite3_backup *p, int nPage);
p: sqlite3_backup 结构体的指针,它是通过 sqlite3_backup_init() 创建的备份操作句柄。
nPage: 要复制的页面数。如果这个值为 -1,函数将复制源数据库中的所有剩余页面。

sqlite3_backup_step()成功复制了nPage个页面,并且还有更多的页面需要复制,那么该函数返回SQLITE_OK。如果sqlite3_backup_step()成功完成从源到目标的所有页面复制,那么它返回SQLITE_DONE。如果运行sqlite_backup_step()时发生错误,则返回错误码。除了SQLITE_OK和SQLITE_DONE,调用sqlite3_backup_step()可能会返回SQLITE_READONLY、SQLITE_NOMEM、SQLITE_BUSY、SQLITE_LOCKED或SQLITE_IOERR_XXX扩展错误代码。

sqlite3_backup_step()可能在以下情况返回SQLITE_READONLY

目标数据库以只读方式打开
目标数据库正在使用预先写日志的日志记录,并且目标和源页面大小不同
目标数据库是内存数据库,目标和源页面大小不同。

如果sqlite3_backup_step()无法获取所需的文件系统锁,那么调用繁忙处理程序函数(如果指定了一个)。如果处理繁忙程序在锁之前返回非零值,那么将SQLITE_BUSY返回给调用者。在这种情况下,可以稍后重试对sqlite3_backup_step()的调用。如果在调用sqlite3_backup_step()时正在使用源数据库连接写入源数据库,那么SQLITE_LOCKED会立即返回。同样,在这种情况下,可以稍后重试对sqlite3_backup_step()的调用。如果返回了SQLITE_IOERR_XXX,SQLITE_NOMEM或者SQLITE_READONLY,那么重新调用sqlite3_backup_step()就没有意义了。这些错误会被认为是致命的,应用程序必须接受备份操作失败,并将备份操作句柄传递给sqlite3_backup_finish()以释放相关资源。

对sqlite3_backup_step()的第一个调用获得了目标文件上的排它锁。排它锁只有在调用sqlite3_backup_finish()或者备份操作完成并且sqlite3_backup_step()返回SQLITE_DONE时才会释放。每次对sqlite3_backup_step()的调用都会获得一个在sqlite3_backup_step()调用期间持续的源数据库上的共享锁。因为源数据库在调用sqlite3_backup_step()之间没有被锁定,所以源数据库可能能在备份过程中被修改,如果源数据库被外部进程修改,或者通过与备份操作使用的数据库不同的数据库连接被修改,那么备份将会在下一个调用sqlite3_backup_step()时自动修改。如果使用与备份操作相同的数据库连接修改源数据库,则备份数据库将同时自动更新。

int sqlite3_backup_finish(sqlite3_backup *p);
p: 指向 sqlite3_backup 结构体的指针,这个结构体在之前通过 sqlite3_backup_init() 
函数初始化,用于备份操作。

当sqlite3_backup_step()返回SQLITE_DONE时,或者当应用程序希望放弃备份操作时,应用程序应该通过将sqlite3_backup_finish()来销毁sqlite3_backup。sqlite3_backup_finish()接口释放与sqlite3_backup对象关联的所有资源。如果sqlite3_backup_step()还没有返回SQLITE_DONE,那么目标数据库上任何活动的写事务都会回滚(如果在事务未完成之前发生了错误,或者事务被中断,回滚操作会撤销所有在该事务中进行的变更,恢复到事务开始前的状态。)。sqlite3_backup对象无效,不能在调用sqlite3_backup_finish()后使用。

如果没有sqlite3_backup_step()错误发生,sqlite3_backup_step()是否完成,sqlite3_backup_step()返回的值是SQLITE_OK。如果在同一个sqlite3_backup对象上的任何之前的sqlite3_backup_step()调用期间发生了内存不足情况或IO错误,那么sqlite3_backup_finish()将返回相应的错误代码。

注:sqlite3_backup_step()返回SQLITE_BUSY或SQLITE_LOCKED不是永久性错误,不会影响sqlite3_backup_finish()的返回值。

/*
** 该函数用于将数据库文件的内容加载到磁盘上
** 进入“主”数据库的开放数据库连接pInMemory,或
** 将pInMemory打开的数据库的当前内容保存到
** 磁盘上的数据库文件。pInMemory可能是一个内存数据库,
** 但如果不是,这个函数也会正常工作。
**
** 参数zFilename指向一个以空字符结尾的字符串,其中包含
** 磁盘上要加载或保存的数据库文件的名称。如果参数
** isSave为非零,则文件zFilename的内容为
** 用pInMemory打开的数据库内容覆盖。如果
** 参数isSave为0,则数据库打开的内容由
** pInMemory被从zFilename加载的数据所取代。
**
** 如果操作成功,则返回SQLITE_OK。否则,如果
** 发生错误,返回一个SQLite错误码。
*/
int loadOrSaveDb(sqlite3* pInMemory, const char* zFilename, int isSave) {
	int rc;                   /* 函数返回值 */
	sqlite3* pFile;           /* 在zFilename上打开数据库连接 */
	sqlite3_backup* pBackup;  /* 备份对象复制数据的对象 */
	sqlite3* pTo;             /* 要复制到的数据库(pFile或pInMemory) */
	sqlite3* pFrom;           /* 要从其中复制的数据库(pFile或pInMemory) */

	/* 打开以zFilename标识的数据库文件。如果由于任何原因失败,则提前退出。 */
	rc = sqlite3_open(zFilename, &pFile);
	if (rc == SQLITE_OK) {

		/* 如果这是一个` load `操作(isSave==0),则数据被复制
		** 从刚刚打开的数据库文件到数据库pInMemory。
		** 否则,如果这是一个` save `操作(isSave==1),则数据
		** 从pInMemory复制到pFile。设置变量pFrom和
		** 相应地pTo。 */
		pFrom = (isSave ? pInMemory : pFile);
		pTo = (isSave ? pFile : pInMemory);

		/* 设置备份程序,从“主”数据库复制
		** 将pFile连接到连接pInMemory的主数据库。
		** 如果出现错误,pBackup将被设置为NULL并报错
		** 连接pTo中的代码和消息。
		**
		** 如果成功创建了备份对象,调用backup_step()
		** 将数据从pFile复制到pInMemory。然后调用backup_finish()
		** 释放与pBackup对象关联的资源。如果一个
		** 发生错误,则会留下错误代码和消息
		** 连接pTo。如果没有发生错误,则错误代码属于
		** to pTo设置为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);
	}

	/* 关闭在数据库文件zFilename上打开的数据库连接
	** 并返回这个函数的结果 */
	(void)sqlite3_close(pFile);
	return rc;
}




/*
** 在线备份数据库pDb到名为
** 通过zFilename。该函数将5个数据库页面从pDb复制到
** zFilename,然后解锁pDb并休眠250毫秒,然后重复执行
** 处理直到备份整个数据库。
**
** 传递给这个函数的第三个参数必须是一个指向进度的指针
** 函数。每组5页备份后,进度功能
** 调用时需要两个整数参数:剩余页数
** 复制,以及源文件中的总页数。这个信息
** 例如,可能用于更新GUI进度条。
**
** 当此函数运行时,另一个线程可能使用数据库pDb或
** 其他进程可以通过单独的连接。
**
** 如果备份过程成功完成,则返回SQLITE_OK。
** 否则,如果发生错误,则返回SQLite错误代码。
*/
int backupDb(
	sqlite3* pDb,               /* 数据库备份 */
	const char* zFilename,      /* 要备份的文件的名称 */
	void(*xProgress)(int, int)  /* 要调用的进度函数 */
) {
	int rc;                     /* 函数返回值 */
	sqlite3* pFile;             /* 在zFilename上打开数据库连接 */
	sqlite3_backup* pBackup;    /* 备份句柄用于复制数据 */

	/* 打开以zFilename标识的数据库文件。 */
	rc = sqlite3_open(zFilename, &pFile);
	if (rc == SQLITE_OK) {

		/* 打开用于完成传输的sqlite3_backup对象 */
		pBackup = sqlite3_backup_init(pFile, "main", pDb, "main");
		if (pBackup) {

			/* 这个循环的每次迭代都会从数据库复制5个数据库页面
			** pDb到备份数据库。如果backup_step()的返回值
			** pDb到备份数据库。如果backup_step()的返回值
			** 重复之前的250毫秒。 */
			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);

			/* 释放由backup init()分配的资源。*/
			(void)sqlite3_backup_finish(pBackup);
		}
		rc = sqlite3_errcode(pFile);
	}

	/* 关闭在数据库文件zFilename上打开的数据库连接
	** 并返回这个函数的结果。 */
	(void)sqlite3_close(pFile);
	return rc;
}

此代码为SQLite官方样例代码。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部