SQLite数据库损坏及其修复探究
数据库如何发生损坏
SQLite 数据库具有很强的抗损坏能力。在执行事务时如果发生应用程序崩溃、操作系统崩溃甚至电源故障,那么在下次访问数据库文件时,会自动回滚部分写入的事务。恢复过程是全自动的,不需要用户或应用程序的任何操作。尽管 SQLite 数据库具有很强的抗损坏能力,但仍有可能发生损坏。
1. db文件被其他线程或进程破坏
数据库文件本身是磁盘文件的一种,因此任何进程都可以往这个文件中写入数据。SQLite 自身对这种行为也无能为力。
1.1. 向已经关闭的文件描述符继续写入数据
数据库文件关闭后又被开启,其他线程往旧的文件描述符写入数据,导致覆盖部分数据产生数据库损坏。
延伸:不同系统对于多进程写入同一个文件提供的处理能力。
1.2 事务处于活跃状态下进行备份
在后台对数据库文件进行自动备份的时候,此时数据库可能处于事务之中。这个备份可能包含一些脏数据(旧的或者新的处于被更改的内容)。
实现可靠的数据库备份方式是使用 SQLite 提供的 backup API。当前一个事务失败时,将 journal 或 wal 日志文件与数据库文件一起拷贝非常重要。
1.3. 删除Hot Journals
SQLite通常将所有的内容存储在单个文件中。但在执行事务时,当产生崩溃或者异常断电,恢复数据库的必要信息保存在了一个辅助文件中,这个文件与数据库同名,并且添加了 journal 或者 wal 的文件后缀。当这个辅助文件被修改或者删除,那么数据库就有可能崩溃。
关于Hot Journals,官网是这么阐述的:
当 journal 日志或 wal 日志文件包含恢复数据库状态所需的信息时,它们被称为“热日志”或“热 WAL 文件”,通常出现在应用程序或者设备在事务完成之前崩溃。热日志和热 WAL 文件只是错误恢复场景中的一个因素,因此并不常见。但它们是 SQLite 数据库状态的一部分,因此不容忽视。
1.4. 数据库文件与日志文件不一致
SQLite 数据库受数据库文件及日志文件共同控制,当两者受外部影响因素导致错误搭配时,可能导致数据库损坏。以下这些行为则可能导致数据库损坏:
- 交换两个不同数据库的日志文件
- 将数据库日志文件复写为其他数据库的日志文件
- 将一个数据库的日志文件移动给其他数据库
- 覆盖数据库时却没有将其关联的日志文件一起删除
2. 文件锁问题
SQLite 在数据库文件、WAL 文件上使用文件锁来协调并发进程之间的访问。如果没有加锁机制,多个线程或进程可能会尝试同时对数据库文件进行不兼容的更改,从而导致数据库损坏。
2.1 文件系统的锁机制出问题或者未实现
SQLite 依赖于底层文件系统对文件进行锁处理。但是一些文件系统在其锁逻辑中包含错误,因此文件加锁并不总是如预期表现。对于网络文件系统和 NFS 尤其如此。如果在锁定原语包含错误的文件系统上使用 SQLite,并且如果多个线程或进程尝试同时访问同一个数据库,则可能导致数据库损坏。
3. 同步失败
为了保证数据库文件始终保持一致,SQLite 偶尔会要求操作系统将所有挂起的写入刷新到持久存储,然后等待刷新完成。这是使用 unix 下的 fsync() 系统调用和 Windows 下的 FlushFileBuffers() 来完成的。我们将这种挂起的写入刷新称为“同步”。 实际上,如果一个人只关心原子性和一致性写入并且愿意放弃持久性写入,那么同步操作不需要等到内容完全存储在持久性媒体上。相反,可以将同步操作视为 I/O 屏障。如果同步作为 I/O 屏障而不是真正的同步运行,则电源故障或系统崩溃可能会导致一个或多个先前提交的事务回滚(违反“ACID”的“持久”属性),但数据库至少会继续保持一致,这是大多数人关心的。
3.1 不遵守同步请求的设备驱动器
大多数消费级存储设备对于写入内容并不是严格同步的,当内容到达轨道缓冲区却还未被写入到磁盘时,设备驱动器就会反馈已经写入磁盘,这使得设备驱动器看起来运行得更快。在大部分时候,这种行为并没有什么不妥。但当内容到达轨道缓冲区却未写入磁盘,此时发生断电,那么数据库文件就可能发生损坏。相比较默认的日志模式,WAL 日志模式更能容忍乱序写入。在 WAL 模式下。如果在 checkpoint 期间出现同步失败,那么这将是导致数据库损坏的唯一原因。因此,防止由于同步失败导致的数据库损坏的一种方式是:在 WAL 日志模式,不要频繁的触发 checkpoint。
3.2. 使用 PRAGMAs 禁用同步
SQLite 确保完整性的同步操作可以在运行时使用 synchronous pragma 命令禁用。通过设置PRAGMA synchronous=OFF
,所有同步操作都被省略。这使得 SQLite 看起来运行得更快,但它也允许操作系统自由地重新排序写入,如果在所有内容到达持久存储之前发生电源故障或硬重置,这可能会导致数据库损坏。
4. 磁盘驱动器或者闪存故障
磁盘驱动器或闪存故障导致文件内容而发生更改,则 SQLite 数据库可能会损坏。虽然这种现象非常罕见,但磁盘仍可能意外翻转扇区中的一点导致故障产生。
5. 内存损坏
SQLite 是一个 C 库,它与宿主应用运行在同一地址空间中。这意味着应用程序中的野指针、缓冲区溢出、堆损坏或其他故障可能会损坏 SQLite 内部的数据结构并最终导致数据库文件损坏。通常,这些类型的问题在发生任何数据库损坏之前都表现为段错误,但是在某些情况下,应用程序代码错误会导致 SQLite 发生故障,从而损坏数据库文件。
使用内存映射 I/O 时,内存损坏问题变得更加严重。当数据库文件的全部或部分映射到应用程序的地址空间时,覆盖该映射空间的任何部分的野指针将立即损坏数据库文件,而无需应用程序执行后续的 write()
系统调用。
6. 数据库配置错误
SQLite 具有许多针对数据库损坏的内置保护。但是其中许多保护可以通过配置选项禁用。如果禁用保护,可能会发生数据库损坏。 以下是禁用 SQLite 内置保护机制的示例:
- 设置 PRAGMA synchronous=OFF在出现操作系统崩溃或电源故障时可能导致数据库损坏(这个设置不会因为应用程序崩溃而损坏数据库)
- 当其他数据库连接打开时,改变 PRAGMA schema_version
- 使用 PRAGMA journal_mode=OFF 或 PRAGMA journal_mode=MEMORY 并在写入事务的中间应用程序崩溃。
- 设置 PRAGMA writable_schema=ON 然后使用 DML 语句更改数据库模式可能会使数据库完全不可读。
数据库异常在Android上的表现方式
Android 基于 SQLite 提供了应用框架层的 API 供用户使用,当操作异常时,通过特定的 Exception 提示用户。一般有以下几种数据库错误:
数据库文件被异常删除
android.database.sqlite.SQLiteDatabaseCorruptException: file is not a database (Sqlite code 26 SQLITE_NOTADB): , while compiling: PRAGMA journal_mode, (OS error - 2:No such file or directory)
android.database.sqlite.SQLiteConnection.nativePrepareStatement(SQLiteConnection.java)
android.database.sqlite.SQLiteConnection.acquirePreparedStatement(SQLiteConnection.java:1030)
android.database.sqlite.SQLiteConnection.executeForString(SQLiteConnection.java:773)
android.database.sqlite.SQLiteConnection.setJournalMode(SQLiteConnection.java:420)
android.database.sqlite.SQLiteConnection.setWalModeFromConfiguration(SQLiteConnection.java:334)
android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:238)
android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:211)
android.database.sqlite.SQLiteConnectionPool.openConnectionLocked(SQLiteConnectionPool.java:559)
android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:222)
android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:211)
android.database.sqlite.SQLiteDatabase.openInner(SQLiteDatabase.java:947)
android.database.sqlite.SQLiteDatabase.open(SQLiteDatabase.java:931)
android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:790)
android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:779)
android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:389)
android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:332)
android.arch.persistence.db.framework.FrameworkSQLiteOpenHelper$OpenHelper.getWritableSupportDatabase(FrameworkSQLiteOpenHelper.java:96)
android.arch.persistence.db.framework.FrameworkSQLiteOpenHelper.getWritableDatabase(FrameworkSQLiteOpenHelper.java:54)
日志文件问题
android.database.sqlite.SQLiteDatabaseCorruptException: file is encrypted or is not a database (code 26): , while compiling: PRAGMA journal_mode
android.database.sqlite.SQLiteConnection.nativePrepareStatement(SQLiteConnection.java)
android.database.sqlite.SQLiteConnection.acquirePreparedStatement(SQLiteConnection.java:921)
android.database.sqlite.SQLiteConnection.executeForString(SQLiteConnection.java:648)
android.database.sqlite.SQLiteConnection.setJournalMode(SQLiteConnection.java:322)
android.database.sqlite.SQLiteConnection.setWalModeFromConfiguration(SQLiteConnection.java:293)
android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:217)
android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:195)
android.database.sqlite.SQLiteConnectionPool.openConnectionLocked(SQLiteConnectionPool.java:493)
android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:200)
android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:192)
android.database.sqlite.SQLiteDatabase.openInner(SQLiteDatabase.java:864)
android.database.sqlite.SQLiteDatabase.open(SQLiteDatabase.java:852)
android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:724)
android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:714)
android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:295)
android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:238)
android.arch.persistence.db.framework.FrameworkSQLiteOpenHelper$OpenHelper.getWritableSupportDatabase(FrameworkSQLiteOpenHelper.java:96)
android.arch.persistence.db.framework.FrameworkSQLiteOpenHelper.getWritableDatabase(FrameworkSQLiteOpenHelper.java:54)
存储空间不足
android.database.sqlite.SQLiteFullException: database or disk is full (code 13 SQLITE_FULL)
android.database.sqlite.SQLiteConnection.nativeExecute(SQLiteConnection.java)
android.database.sqlite.SQLiteConnection.execute(SQLiteConnection.java:717)
android.database.sqlite.SQLiteSession.endTransactionUnchecked(SQLiteSession.java:439)
android.database.sqlite.SQLiteSession.endTransaction(SQLiteSession.java:403)
android.database.sqlite.SQLiteDatabase.endTransaction(SQLiteDatabase.java:592)
android.arch.persistence.db.framework.FrameworkSQLiteDatabase.endTransaction(FrameworkSQLiteDatabase.java:90)
android.database.sqlite.SQLiteDiskIOException: disk I/O error - SQLITE_IOERR_SHMSIZE (Sqlite code 4874): , while compiling: PRAGMA journal_mode, (OS error - 28:No space left on device)
android.database.sqlite.SQLiteConnection.nativePrepareStatement(SQLiteConnection.java)
android.database.sqlite.SQLiteConnection.acquirePreparedStatement(SQLiteConnection.java:927)
android.database.sqlite.SQLiteConnection.executeForString(SQLiteConnection.java:672)
android.database.sqlite.SQLiteConnection.setJournalMode(SQLiteConnection.java:358)
android.database.sqlite.SQLiteConnection.setWalModeFromConfiguration(SQLiteConnection.java:332)
android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:231)
android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:209)
android.database.sqlite.SQLiteConnectionPool.openConnectionLocked(SQLiteConnectionPool.java:541)
android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:209)
android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:198)
android.database.sqlite.SQLiteDatabase.openInner(SQLiteDatabase.java:936)
android.database.sqlite.SQLiteDatabase.open(SQLiteDatabase.java:920)
android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:795)
android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:785)
android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:307)
android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:250)
损坏修复
优化应用磁盘空间占用
应用迭代中,每个业务团队都有一些持久化的需求,然而大部分团队只管文件的创建,文件使用完后没有及时清理掉。如果不及时对各业务线文件创建进行监控和治理的话,会恶化由于空间不足导致的数据库异常。
除了 APP 本身对于磁盘空间的占用外,用户手机被其他文件占用导致磁盘空间满也是一大因素。因此,引导用户释放一定的空间也是一种方式。
备份恢复
通过一定的手段对数据库进行备份,同时为了减小备份的数据库文件对于磁盘空间的占用,进一步压缩备份文件。这种方案能够挽回一部分数据损失,主要取决于数据库损坏时备份的日志文件的时效性。
直接备份
定期备份数据库及日志文件。当数据库损坏时,恢复备份的数据库文件。
.dump 命令
.dump
命令通过解析sqlite_master
表拿到所有的表信息,然后遍历每一张表的数据,对于每条记录输出一条相关的 SQLite 语句,当遇到错误无法解析出来则跳过继续解析下一张表。恢复的话对空 DB 文件执行输出的全部 SQLite 语句,这样就能恢复数据。这种方式可以提前对没有损坏的数据库文件执行.dump
命令,起到备份恢复的作用。
// 查询完整的 sqlite_master 信息
SELECT * FROM sqlite_master
// 重定向到某个文件
.output sqlite_dump.txt
// dump数据库
.dump
.dump
命令也可以直接执行于损坏的数据库文件,当sqlite_master
都无法读取时,将导致无法恢复任何数据。
Backup API
SQLite自身提供的一套备份机制,按 Page 为单位复制到新 DB, 支持热备份。
RepairKit
WCDB 提供的修复方案,实际是自实现了B+树的解析逻辑,实现对数据的读取,补齐了备份恢复方案有时效性的缺点。并且由于大部分case(来自WCDB的统计数据)都是因为sqlite_master
表损坏导致.dump
方案失效,因此增加了对sqlite_mater
的备份。而由于sqlite_master
并不会频繁变更,只在表结构有变化时改变,因此可在升级时机覆盖备份。
参考链接
- How To Corrupt An SQLite Database File
- SQLite Result and Error Codes
- 微信 SQLite 数据库修复实践
- 微信移动端数据库组件WCDB系列(二) — 数据库修复三板斧
SQLite数据库损坏及其修复探究的更多相关文章
- 讨论SQLite数据库损坏与修复
版权声明:博客将逐步迁移到 http://cwqqq.com https://blog.csdn.net/cwqcwk1/article/details/45541409 昨晚,朋友和我反馈SQL ...
- SQL Server数据库损坏、检测以及简单的修复办法
简介 在一个理想的世界中,不会存在任何数据库的损坏,就像我们不会将一些严重意外情况列入我们生活中的日常一样,而一旦这类事情发生,一定会对我们的生活造成非常显著的影响,在SQL Server中也 ...
- 修复 SQLite 数据库文件
目 录 第1章 说明 1 1 下载SQLite Tools 1 2 运行 2 第1章 说明 笔者编写的一个程序,无法往 SQLite 数据库文件里写数据.使用SQLiteSpy打开该 ...
- 微信 SQLite 数据库修复实践
1.前言 众所周知,微信在后台服务器不保存聊天记录,微信在移动客户端所有的聊天记录都存储在一个 SQLite 数据库中,一旦这个数据库损坏,将会丢失用户多年的聊天记录.而我们监控到现网的损坏率是0.0 ...
- Sql server Compact 小型数据库损坏修复
之前碰到过小型数据库损坏打不开的问题,一直没有理会,今天生产上客户本地小库产生这样的问题,已经修复 SqlCeEngine engine = new SqlCeEngine(" ...
- sql2005数据库置疑修复断电崩溃索引损坏 数据库索引错误修复/数据库表损坏/索引损坏/系统表混乱等问题修复
sql2005数据库置疑修复断电崩溃索引损坏 数据库索引错误修复/数据库表损坏/索引损坏/系统表混乱等问题修复 客 户 名 称 济南某电子商务公司 数 据 类 型 SQL2005数据库 故 障 检 测 ...
- Sqlite 数据库出现database disk image is malformed报错的解决方法
软件用的是Sqlite数据库,昨天还好好的,今天开机登录软件报错:database disk image is malformed 用Sqlite Expert Personal 重建索引,发现其中一 ...
- sqlite 数据库错误 The database disk image is malformed database disk image
收银机上的sqlite数据库经常出现这种错误,错误的原因有可能是突然断电或是一些不规范操作导致的. 网上一般的做法有两种: 方法一: 1.在https://www.sqlite.org/downloa ...
- SQLite 数据库调研
SQLite数据库的特点(转载的): ★技术上的优点和特性 SQLite是一个轻量级.跨平台的关系型数据库.既然号称关系型数据库,支持SQL92标准中常用的玩意儿(比如视图.事务.触发器等)就是理所当 ...
随机推荐
- 12-factors
12-factors 官方网址 The Twelve-Factor App 简介 如今,软件通常会作为一种服务来交付,它们被称为网络应用程序,或软件即服务(SaaS).12-Factor 为构建如下的 ...
- 如何写好一份晋升PPT(附PPT模板)
又到了每年晋升述职的时间,在过去的5.6年里,我以评委身份参与了大量的晋升述职(主要是前端,也包括客户端和测试),也辅导了许多(100+)组内外的同学,指导他们书写和完善PPT.过程中我发现大家有许多 ...
- Metalama简介2.利用Aspect在编译时进行消除重复代码
上文介绍到Aspect是Metalama的核心概念,它本质上是一个编译时的AOP切片.下面我们就来系统说明一下Metalama中的Aspect. Metalama简介1. 不止是一个.NET跨平台的编 ...
- window 的简单使用
window 的延迟加载 js代码 window的原始用法 (缺点 : 只能使用一次) window.onload = function() { var btn = document.querySel ...
- javascript中的宏任务和微任务(二)
js事件轮询执行顺序总结: 1)所有的同步任务都在主线程上执行,行成一个执行栈. 2)除了主线程之外,还存在一个任务列队,只要异步任务有了运行结果,就在任务列队中植入一个时间标记. 3)主线程完成所有 ...
- oracle split 以及 简单json解析存储过程
BEGIN; 由于之前工作上需要在oracle中做split功能以及json格分解.然后经过一番google和优化整合,最后整理到一个存储过程包中,易于管理,代码如下: 1.包定义: CREATE O ...
- Day 002:PAT练习--1021 个位数统计 (15 分)
话不多说,看题目: 显而易见,这道题用map实现非常的方便,(才不是,其实还有更简单的办法,但是我觉得写那种代码实在没什么意义,再加上正好借此练习一下map)我的代码如下: #include& ...
- 配置Docker镜像源为国内镜像源
镜像加速 /etc/docker/daemon.json 没有这个文件 创建这个文件 vi /etc/docker/daemon.json 按 i 进行插入 { "registry-mirr ...
- OpenStack计费服务
cloudkitty服务介绍 当前版本cloudkitty可以完成虚拟机实例(compute),云硬盘(volume),镜像(image),网络进出流量(network.bw.in,network.b ...
- 【mq】从零开始实现 mq-11-消费者消息回执添加分组信息 pull message ack groupName
前景回顾 [mq]从零开始实现 mq-01-生产者.消费者启动 [mq]从零开始实现 mq-02-如何实现生产者调用消费者? [mq]从零开始实现 mq-03-引入 broker 中间人 [mq]从零 ...