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

相关文章

C# WinForms存储过程操作数据库的实例讲解

《C#WinForms存储过程操作数据库的实例讲解》:本文主要介绍C#WinForms存储过程操作数据库的实例,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、存储过程基础二、C# 调用流程1. 数据库连接配置2. 执行存储过程(增删改)3. 查询数据三、事务处

mysql数据库重置表主键id的实现

《mysql数据库重置表主键id的实现》在我们的开发过程中,难免在做测试的时候会生成一些杂乱无章的SQL主键数据,本文主要介绍了mysql数据库重置表主键id的实现,具有一定的参考价值,感兴趣的可以了... 目录关键语法演示案例在我们的开发过程中,难免在做测试的时候会生成一些杂乱无章的SQL主键数据,当我们

Spring Boot 整合 MyBatis 连接数据库及常见问题

《SpringBoot整合MyBatis连接数据库及常见问题》MyBatis是一个优秀的持久层框架,支持定制化SQL、存储过程以及高级映射,下面详细介绍如何在SpringBoot项目中整合My... 目录一、基本配置1. 添加依赖2. 配置数据库连接二、项目结构三、核心组件实现(示例)1. 实体类2. Ma

使用Python实现网络设备配置备份与恢复

《使用Python实现网络设备配置备份与恢复》网络设备配置备份与恢复在网络安全管理中起着至关重要的作用,本文为大家介绍了如何通过Python实现网络设备配置备份与恢复,需要的可以参考下... 目录一、网络设备配置备份与恢复的概念与重要性二、网络设备配置备份与恢复的分类三、python网络设备配置备份与恢复实

查看Oracle数据库中UNDO表空间的使用情况(最新推荐)

《查看Oracle数据库中UNDO表空间的使用情况(最新推荐)》Oracle数据库中查看UNDO表空间使用情况的4种方法:DBA_TABLESPACES和DBA_DATA_FILES提供基本信息,V$... 目录1. 通过 DBjavascriptA_TABLESPACES 和 DBA_DATA_FILES

Java实现数据库图片上传与存储功能

《Java实现数据库图片上传与存储功能》在现代的Web开发中,上传图片并将其存储在数据库中是常见的需求之一,本文将介绍如何通过Java实现图片上传,存储到数据库的完整过程,希望对大家有所帮助... 目录1. 项目结构2. 数据库表设计3. 实现图片上传功能3.1 文件上传控制器3.2 图片上传服务4. 实现

使用Dify访问mysql数据库详细代码示例

《使用Dify访问mysql数据库详细代码示例》:本文主要介绍使用Dify访问mysql数据库的相关资料,并详细讲解了如何在本地搭建数据库访问服务,使用ngrok暴露到公网,并创建知识库、数据库访... 1、在本地搭建数据库访问的服务,并使用ngrok暴露到公网。#sql_tools.pyfrom

Java实现数据库图片上传功能详解

《Java实现数据库图片上传功能详解》这篇文章主要为大家详细介绍了如何使用Java实现数据库图片上传功能,包含从数据库拿图片传递前端渲染,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1、前言2、数据库搭建&nbsChina编程p; 3、后端实现将图片存储进数据库4、后端实现从数据库取出图片给前端5、前端拿到

IDEA连接达梦数据库的详细配置指南

《IDEA连接达梦数据库的详细配置指南》达梦数据库(DMDatabase)作为国产关系型数据库的代表,广泛应用于企业级系统开发,本文将详细介绍如何在IntelliJIDEA中配置并连接达梦数据库,助力... 目录准备工作1. 下载达梦JDBC驱动配置步骤1. 将驱动添加到IDEA2. 创建数据库连接连接参数

Jmeter如何向数据库批量插入数据

《Jmeter如何向数据库批量插入数据》:本文主要介绍Jmeter如何向数据库批量插入数据方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Jmeter向数据库批量插入数据Jmeter向mysql数据库中插入数据的入门操作接下来做一下各个元件的配置总结Jmete