修复sqlite3数据库 database disk image is malformed

database disk image is malformed 错误

sqlite是一个文本型数据库,其内部虽然做了异常处理,且官网上也说一般的异常不会引起数据库文件损坏,但是官方还是给出了有可能导致数据库文件损坏的情况

以下内容来自官网:

SQLite 经过非常仔细的测试,以帮助确保它尽可能没有错误。对每个 SQLite 版本执行的众多测试包括模拟电源故障、I/O 错误和内存不足 (OOM) 错误的测试,并验证在任何这些事件期间不会发生数据库损坏。SQLite 还经过现场验证,约有 20 亿次活动部署,没有出现严重问题。

然而,没有一个软件是 100% 完美的。SQLite 中存在一些历史错误(现已修复),这些错误可能会导致数据库损坏。可能还有一些尚未被发现。由于 SQLite 的广泛测试和广泛使用,导致数据库损坏的错误往往非常隐蔽。应用程序遇到 SQLite bug 的可能性很小。为了说明这一点,下面列出了从 2009 年 4 月 1 日到 2013 年 4 月 15 日这四年期间在 SQLite 中发现的所有数据库损坏错误。这个帐户应该让读者直观地了解 SQLite 中的各种错误,这些错误设法通过测试过程并进入发布版本。

来源: How To Corrupt An SQLite Database File

修复方案一( 命令行手动修复)

  • 以下是在linux环境下执行的,如果是windows需要把sqlite3修改为sqlite3.exe。

  • 确保sqlite3在环境变量中,可以直接调用到,否则需要指明路径。

1.命令行打开sqlite3.exe并读取损坏的文件

sqlite3 CorruptDB

此时进入了sqlite命令行环境

2.导出sql语句到临时文件

sqlite>.output tmp.sql
sqlite>.dump
sqlite>.quit

3.修改tmp.sql文件

由于数据库文件损坏,所以sqlite自动将tmp.sql最后一行加上了一句Rollback,因此我们需要手动修改tmp.sql文件,将最后一行的Rollback改为Commit;

这一步是最重要的!!!

4.读取tmp.sql并写入到新库中

sqlite3 NewDB

sqlite>.read tmp.sql
sqlite>.quit

大功告成!

修复方案二(python脚本自动修复)

  • 一共有三个文件,一个python文件外加两个sql文件,原理是将方案一的所有操作集成为一个脚本执行。

三个文件内容如下

repair_tool.py

# -*- coding: utf-8 -*-
import os


class Repair(object):

    def __init__(self):

        # 前提是sqlite3已经添加到环境变量
        self.__sqlite3_path = "sqlite3"

        self.__path = os.path.dirname(os.path.abspath(__file__))
        self.__dbDir = os.path.join(os.path.dirname(os.path.dirname(self.__path)), "data")

        self.__tmpFilePath = os.path.join(self.__dbDir, "tmp.sql")

        self.__dbPath = os.path.join(self.__dbDir, "data.db")
        self.__newDbPath = os.path.join(self.__dbDir, "new_data.db")

        self.__dumpSqlPath = os.path.join(self.__path, "dump.sql")
        self.__readSqlPath = os.path.join(self.__path, "read.sql")

    def doRepair(self):
        # print("INFO: Starting repairing, please wait for a while.")
        self.__dumpSql()
        # print("INFO: DumpSql is successful.")
        self.__modLastLine()
        # print("INFO: Modify last line is successful.")
        self.__readSql()
        # print("INFO: ReadSql is successful.")
        self.__delTmp()
        # print("INFO: Delete TempFile is successful.")
        self.__copyDB()
        # print("INFO: Repairing is successful!")

    def __dumpSql(self):
        cmd = "cd " + self.__dbDir + "&&" + f'''{self.__sqlite3_path} {self.__dbPath}<{self.__dumpSqlPath}'''
        os.system(cmd)

    def __readSql(self):

        with open(self.__newDbPath, "a"):
            ...
        cmd = "cd " + self.__dbDir + "&&" + f'''{self.__sqlite3_path} {self.__newDbPath}<{self.__readSqlPath}'''
        os.system(cmd)

    def __delTmp(self):
        os.remove(self.__tmpFilePath)

    def __copyDB(self):

        os.remove(self.__dbPath)
        os.rename(self.__newDbPath, self.__dbPath)

    def __modLastLine(self):
        with open(self.__tmpFilePath, "rb+") as f:
            file_size = os.path.getsize(self.__tmpFilePath)
            offset = -8

            while -1 * offset < file_size:
                f.seek(offset, os.SEEK_END)
                lines = f.readlines()
                if len(lines) >= 2:
                    last_line_len = len(lines[-1])
                    f.seek(-last_line_len, os.SEEK_END)
                    f.truncate()
                    f.write(b"Commit;")
                    return
                else:
                    offset *= 2


if __name__ == "__main__":
    repair = Repair()
    repair.doRepair()

dump.sql

.output tmp.sql
.dump
.quit

read.sql

.read tmp.sql
.quit
posted @ 2024-02-27 18:28  liuyang9643  阅读(975)  评论(0编辑  收藏  举报