SQLite数据库(备份)

2024-09-03 18:36
文章标签 数据库 备份 sqlite

本文主要是介绍SQLite数据库(备份),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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 contentreturn 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官方样例代码。

这篇关于SQLite数据库(备份)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/1133715

相关文章

Druid连接池实现自定义数据库密码加解密功能

《Druid连接池实现自定义数据库密码加解密功能》在现代应用开发中,数据安全是至关重要的,本文将介绍如何在​​Druid​​连接池中实现自定义的数据库密码加解密功能,有需要的小伙伴可以参考一下... 目录1. 环境准备2. 密码加密算法的选择3. 自定义 ​​DruidDataSource​​ 的密码解密3

Maven项目中集成数据库文档生成工具的操作步骤

《Maven项目中集成数据库文档生成工具的操作步骤》在Maven项目中,可以通过集成数据库文档生成工具来自动生成数据库文档,本文为大家整理了使用screw-maven-plugin(推荐)的完... 目录1. 添加插件配置到 pom.XML2. 配置数据库信息3. 执行生成命令4. 高级配置选项5. 注意事

Mac备忘录怎么导出/备份和云同步? Mac备忘录使用技巧

《Mac备忘录怎么导出/备份和云同步?Mac备忘录使用技巧》备忘录作为iOS里简单而又不可或缺的一个系统应用,上手容易,可以满足我们日常生活中各种记录的需求,今天我们就来看看Mac备忘录的导出、... 「备忘录」是 MAC 上的一款常用应用,它可以帮助我们捕捉灵感、记录待办事项或保存重要信息。为了便于在不同

在Java中基于Geotools对PostGIS数据库的空间查询实践教程

《在Java中基于Geotools对PostGIS数据库的空间查询实践教程》本文将深入探讨这一实践,从连接配置到复杂空间查询操作,包括点查询、区域范围查询以及空间关系判断等,全方位展示如何在Java环... 目录前言一、相关技术背景介绍1、评价对象AOI2、数据处理流程二、对AOI空间范围查询实践1、空间查

Python+PyQt5实现MySQL数据库备份神器

《Python+PyQt5实现MySQL数据库备份神器》在数据库管理工作中,定期备份是确保数据安全的重要措施,本文将介绍如何使用Python+PyQt5开发一个高颜值,多功能的MySQL数据库备份工具... 目录概述功能特性核心功能矩阵特色功能界面展示主界面设计动态效果演示使用教程环境准备操作流程代码深度解

MySQL数据库实现批量表分区完整示例

《MySQL数据库实现批量表分区完整示例》通俗地讲表分区是将一大表,根据条件分割成若干个小表,:本文主要介绍MySQL数据库实现批量表分区的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考... 目录一、表分区条件二、常规表和分区表的区别三、表分区的创建四、将既有表转换分区表脚本五、批量转换表为分区

MySQL Workbench工具导出导入数据库方式

《MySQLWorkbench工具导出导入数据库方式》:本文主要介绍MySQLWorkbench工具导出导入数据库方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝... 目录mysql Workbench工具导出导入数据库第一步 www.chinasem.cn数据库导出第二步

Mysql数据库中数据的操作CRUD详解

《Mysql数据库中数据的操作CRUD详解》:本文主要介绍Mysql数据库中数据的操作(CRUD),详细描述对Mysql数据库中数据的操作(CRUD),包括插入、修改、删除数据,还有查询数据,包括... 目录一、插入数据(insert)1.插入数据的语法2.注意事项二、修改数据(update)1.语法2.有

查看MySQL数据库版本的四种方法

《查看MySQL数据库版本的四种方法》查看MySQL数据库的版本信息可以通过多种方法实现,包括使用命令行工具、SQL查询语句和图形化管理工具等,以下是详细的步骤和示例代码,需要的朋友可以参考下... 目录方法一:使用命令行工具1. 使用 mysql 命令示例:方法二:使用 mysqladmin 命令示例:方

MySQL数据库约束深入详解

《MySQL数据库约束深入详解》:本文主要介绍MySQL数据库约束,在MySQL数据库中,约束是用来限制进入表中的数据类型的一种技术,通过使用约束,可以确保数据的准确性、完整性和可靠性,需要的朋友... 目录一、数据库约束的概念二、约束类型三、NOT NULL 非空约束四、DEFAULT 默认值约束五、UN