SQLite 作为应用程序文件格式

执行摘要

具有定义模式的 SQLite 数据库文件通常是一种出色的应用程序文件格式。以下是造成这种情况的十几个原因:

  1. 简化的应用程序开发
  2. 单文件文件
  3. 高级查询语言
  4. 无障碍内容
  5. 跨平台
  6. 原子事务
  7. 增量和持续更新
  8. 易于扩展
  9. 表现
  10. 多个进程并发使用
  11. 多种编程语言
  12. 更好的应用

在首先更仔细地考虑“应用程序文件格式”的含义之后,下面将更详细地描述这些要点中的每一个。另请参阅本白皮书的简短版本

什么是应用程序文件格式?

“应用程序文件格式”是用于将应用程序状态保存到磁盘或用于在程序之间交换信息的文件格式。当今使用的应用程序文件格式有数千种。这里只是几个例子:

我们区分“文件格式”和“应用程序格式”。文件格式用于存储单个对象。因此,例如,GIF 或 JPEG 文件存储单个图像,XHTML 文件存储文本,因此这些是“文件格式”而不是“应用程序格式”。相比之下,EPUB 文件存储文本和图像(包含 XHTML 和 GIF/JPEG 文件),因此它被认为是一种“应用程序格式”。此条目介绍的是“应用程序格式”。

文件格式和应用程序格式之间的界限是模糊的。本文将 JPEG 称为文件格式,但对于图像编辑器而言,JPEG 可能被视为应用程序格式。很大程度上取决于上下文。对于本文,假设文件格式存储单个对象,而应用程序格式存储许多不同的对象及其相互关系。

大多数应用程序格式都属于以下三类之一:

  1. 完全自定义格式。 自定义格式专为单个应用程序而设计。DOC、DWG、PDF、XLS 和 PPT 是自定义格式的示例。自定义格式通常包含在单个文件中,以便于传输。它们通常也是二进制的,但 DWG 格式是一个明显的例外。自定义文件格式需要专门的应用程序代码来读取和写入,并且通常无法通过 unix 命令行程序和文本编辑器等常用工具访问。换句话说,自定义格式通常是“不透明的斑点”。要访问自定义应用程序文件格式的内容,需要一种专门设计用于读取和/或写入该格式的工具。

  2. 文件堆格式。 有时,应用程序状态存储为文件层次结构。Git 是这方面的一个典型例子,尽管这种现象经常发生在一次性和定制的应用程序中。一堆文件格式本质上将文件系统用作键/值数据库,将小块信息存储到单独的文件中。这具有使内容更容易被诸如文本编辑器或“awk”或“grep”之类的常用实用程序访问的优势。但是,即使一堆文件格式的许多文件很容易阅读,通常也有一些文件具有自己的自定义格式(例如:Git“Packfiles”),因此是“不透明的斑点”,不可读或无需专门工具即可写入。将一堆文件从一个地方或机器移动到另一个地方也不太方便,而不是移动单个文件。例如,很难将一堆文件文档制作成电子邮件附件。最后,一堆文件格式打破了“文档隐喻”:用户可以指向的文件没有一个是“文档”。

  3. 包装的文件堆格式。 一些应用程序使用 Pile-of-Files,然后将其封装到某种单文件容器中,通常是 ZIP 存档。EPUB、ODT 和 ODP 是这种方法的示例。EPUB 书籍实际上只是一个 ZIP 存档,其中包含书籍章节文本的各种 XHTML 文件、艺术品的 GIF 和 JPEG 图像,以及一个专门的目录文件,告诉电子书阅读器如何将所有 XML 和图像文件组合在一起。OpenOffice 文档(ODT 和 ODP)也是 ZIP 存档,包含表示其内容的 XML 和图像,以及显示组成部分之间相互关系的“目录”文件。

    包装文件堆格式是完全自定义文件格式和纯文件堆格式之间的折衷。包装的文件堆格式不是自定义格式意义上的不透明 blob,因为组件部分仍然可以使用任何常见的 ZIP 存档器访问,但格式不像纯文件堆那样易于访问-files 格式,因为人们仍然需要 ZIP 归档器,并且如果不首先解压缩文件,通常不能在文件层次结构上使用命令行工具,如“查找”。另一方面,打包的一堆文件格式通过将所有内容放入单个磁盘文件来保留文档隐喻。而且因为它是压缩的,所以打包的一堆文件格式往往更紧凑。

    与自定义文件格式一样,与纯文件堆格式不同,包装的文件堆格式不那么容易编辑,因为通常必须重写整个文件才能更改任何组成部分。

本文档的目的是支持第四种新的应用程序文件格式类别:SQLite 数据库文件。

SQLite 作为应用程序文件格式

任何可以记录在一堆文件中的应用程序状态也可以使用简单的键/值模式记录在 SQLite 数据库中,如下所示:

CREATE TABLE files(filename TEXT PRIMARY KEY, content BLOB);
如果内容被压缩,那么这样的SQLite 存档数据库 与等效的 ZIP 存档大小相同(±1%),并且它具有能够更新单个“文件”而无需重写整个文档的优势。

但是 SQLite 数据库并不局限于简单的键/值结构,如文件堆数据库。一个 SQLite 数据库可以有数十个或数百个或数千个不同的表,每个表有数十个或数百个或数千个字段,每个字段都有不同的数据类型和约束以及特定的含义,所有这些都相互交叉引用,适当地自动索引以便快速检索,所有这些都有效且紧凑地存储在一个磁盘文件中。SQL 模式为人类简洁地记录了所有这些结构。

换句话说,SQLite 数据库可以做一堆文件或包装的一堆文件格式可以做的所有事情,再加上更多,而且更清晰。SQLite 数据库是比键/值文件系统或 ZIP 存档更通用的容器。(有关详细示例,请参阅 OpenOffice 案例研究文章。)

理论上,可以使用自定义文件格式来实现 SQLite 数据库的强大功能。但是任何像关系数据库一样具有表现力的自定义文件格式都可能需要庞大的设计规范和数万或数十万行代码来实现。最终结果将是一个“不透明的斑点”,如果没有专门的工具是无法访问的。

因此,与其他方法相比,使用 SQLite 数据库作为应用程序文件格式具有引人注目的优势。以下列举和阐述了其中的一些优势:

  1. 简化的应用程序开发。 读取或写入应用程序文件不需要新代码。只需链接到 SQLite 库,或将 单个“sqlite3.c”源文件与其余应用程序 C 代码包含在一起,SQLite 将处理所有应用程序文件 I/O。这可以将应用程序代码大小减少数千行,并相应地节省开发和维护成本。

    SQLite 是世界上使用 最广泛的软件库之一。每天在智能手机和小工具以及桌面应用程序中使用的 SQLite 数据库文件确实有数百亿个。SQLite经过仔细测试并证明是可靠的。它不是一个需要大量调整或调试的组件,让开发人员可以专注于应用程序逻辑。

  2. 单文件文档。 SQLite 数据库包含在单个文件中,可以轻松复制、移动或附加。“文件”的隐喻被保留下来。

    SQLite 没有任何文件命名要求,因此应用程序可以使用它想要帮助将文件标识为“属于”应用程序的任何自定义文件后缀。SQLite 数据库文件在其标头中包含一个 4 字节的应用程序 ID,可以将其设置为应用程序定义的值,然后用于识别实用程序(例如file(1) )文档的“类型” ,进一步增强文档隐喻.

  3. 高级查询语言。 SQLite 是一个完整的关系数据库引擎,这意味着应用程序可以使用高级查询来访问内容。应用程序开发人员无需花时间考虑“如何”从文档中检索他们需要的信息。开发人员编写 SQL 来表达他们想要的“什么”信息,并让数据库引擎找出如何最好地检索该内容。这有助于开发人员“抬头”操作并始终专注于解决用户的问题,并避免花时间“低头”摆弄低级文件格式细节。

    一堆文件格式可以被视为键/值数据库。键/值数据库总比没有数据库好。但是如果没有事务或索引或高级查询语言或适当的模式,使用键/值数据库比关系数据库更难也更容易出错。

  4. 可访问的内容。 保存在 SQLite 数据库文件中的信息可以使用常用的开源命令行工具访问——这些工具默认安装在 Mac 和 Linux 系统上,并且可以作为独立的 EXE 文件在 Windows 上免费提供。与自定义文件格式不同,特定于应用程序的程序不需要读取或写入 SQLite 数据库中的内容。SQLite 数据库文件不是不透明的 blob。诚然,文本编辑器或“grep”或“awk”等命令行工具在 SQLite 数据库上没有用,但 SQL 查询语言是检查内容的更强大和方便的方式,因此无法使用“grep”和“awk”之类的不看是亏。

    SQLite 数据库是一种定义明确且文档齐全的 文件格式,被数以百万计的应用程序广泛使用,并且向后兼容它于 2004 年问世的版本,并且有望在未来几十年内继续兼容。SQLite 数据库文件的持久性对于定制应用程序尤为重要,因为它允许在很远的将来访问文档内容,即使原始应用程序的所有痕迹都已丢失。数据比代码寿命更长。SQLite 数据库是美国国会图书馆推荐的 一种长期保存数字内容的存储格式。

  5. 跨平台。 SQLite 数据库文件可以在 32 位和 64 位机器之间、大端架构和小端架构之间以及各种 Windows 和类 Unix 操作系统之间移植。使用 SQLite 应用程序文件格式的应用程序可以存储二进制数字数据,而不必担心整数或浮点数的字节顺序。文本内容可以作为 UTF-8、UTF-16LE 或 UTF-16BE 读取或写入,SQLite 将自动即时执行任何必要的翻译。

  6. 原子交易。 写入 SQLite 数据库是原子的。它们要么完全发生,要么根本不发生,即使在系统崩溃或电源故障期间也是如此。因此,不会因为恰好在将更改写入磁盘的同一时刻断电而损坏文档的危险。

    SQLite 是事务性的,这意味着可以将多个更改组合在一起,以便全部发生或全部不发生,并且如果在提交之前发现问题,则可以回滚更改。这允许应用程序进行增量更改,然后在将更改提交到磁盘之前对结果数据运行各种完整性和一致性检查。Fossil DVCS 使用这种 技术 来验证在每次更改之前没有存储库历史丢失。

  7. 增量和持续更新。 写入 SQLite 数据库文件时,只有文件中实际发生变化的部分才会写入磁盘。这使写入速度更快,并减少了 SSD 的磨损。与自定义和包装的一堆文件格式相比,这是一个巨大的优势,这两种格式通常都需要重写整个文档才能更改单个字节。纯文件堆格式也可以在一定程度上进行增量更新,尽管文件堆格式(单个文件)的写入粒度通常比 SQLite(单个页面)大。

    SQLite 还支持持续更新。不是收集内存中的更改然后仅在文件/保存操作时将它们写入磁盘,而是可以在更改发生时将其写回磁盘。这避免了在系统崩溃或电源故障时丢失工作。使用触发器管理的自动撤消/重做堆栈可以保存在磁盘数据库中,这意味着撤消/重做可以跨会话边界发生。

  8. 易于扩展。 随着应用程序的增长,只需​​向模式添加新表或向现有表添加新列,即可将新功能添加到 SQLite 应用程序文件格式中。添加列或表不会改变先前查询的含义,因此只要稍微注意确保保留旧列和表的含义,就可以保持向后兼容性。

    当然,也可以扩展自定义或一堆文件格式,但这样做通常要困难得多。如果添加了索引,则必须找到并修改所有更改相应表的应用程序代码,以使这些索引保持最新。如果添加了列,则必须找到并修改访问相应表的所有应用程序代码以将新列考虑在内。

  9. 表现。 在许多情况下,SQLite 应用程序文件格式 比一堆文件格式或自定义格式更快。除了加快原始读写速度之外,SQLite 通常还可以显着缩短启动时间,因为应用程序不必将整个文档读取并解析到内存中,而是可以执行查询以仅提取初始屏幕所需的信息。随着应用程序的进行,它只需要加载绘制下一个屏幕所需的材料,并且可以丢弃不再使用的先前屏幕的信息。这有助于控制应用程序的内存占用。

    可以像 SQLite 一样增量读取一堆文件格式。但是许多开发人员惊讶地发现,SQLite 可以从其数据库中读取和写入较小的 BLOB(大小小于 100KB),这比将相同的 blob 作为单独的文件从文件系统中读取或写入的速度要快。(有关更多信息,请参阅 比文件系统内部 BLOB 与外部 BLOB快 35%。)操作关系数据库引擎会产生开销,但是不应假设直接文件 I/O 比 SQLite 数据库 I/O 快,因为通常不是。

    在任何一种情况下,如果 SQLite 应用程序确实出现了性能问题,通常可以通过向模式添加一个或两个CREATE INDEX 语句或者运行一次ANALYZE来解决这些问题,而不必触及一行应用程序代码。但是,如果性能问题出现在自定义或一堆文件格式中,修复通常需要对应用程序代码进行大量更改以添加和维护新索引或使用不同算法提取信息。

  10. 多个进程并发使用。 SQLite 自动协调从多个线程和/或进程对同一文档的并发访问。两个或多个应用程序可以同时连接并读取同一个文档。写入是序列化的,但由于写入通常只需要几毫秒,应用程序只需轮流写入即可。SQLite 自动确保文档的低级格式未损坏。相比之下,使用自定义格式或一堆文件格式来完成相同的操作需要应用程序的广泛支持。支持并发所需的应用程序逻辑是臭名昭著的错误磁铁。

  11. 多种编程语言。 尽管 SQLite 本身是用 ANSI-C 编写的,但几乎所有您能想到的其他编程语言都存在接口:C++、C#、Objective-C、Java、Tcl、Perl、Python、Ruby、Erlang、JavaScript 等等。因此,程序员可以使用他们最熟悉且最符合项目需求的任何语言进行开发。

    如果有单独程序的集合或“联合”,通常由不同的开发团队用不同的语言编写,SQLite 应用程序文件格式是一个很好的选择。这在研究或实验室环境中很常见,其中一个团队负责数据采集,而其他团队负责不同阶段的分析。每个团队都可以使用他们最熟悉的任何硬件、操作系统、编程语言和开发方法,只要所有程序都使用具有通用模式的 SQLite 数据库,它们就可以互操作。

  12. 更好的应用程序。 如果应用程序文件格式是 SQLite 数据库,则该文件格式的完整文档包括数据库模式,可能还有一些关于每个表和列代表什么的额外文字。另一方面,自定义文件格式的描述通常长达数百页。一堆文件格式虽然比完全自定义格式更简单、更容易描述,但仍然比 SQL 模式转储更大、更复杂,因为仍然必须描述单个文件的名称和格式。

    这不是一个微不足道的问题。清晰、简洁且易于理解的文件格式是任何应用程序设计的关键部分。弗雷德·布鲁克斯 (Fred Brooks) 在他有史以来最畅销的计算机科学教科书《人月神话》( The Mythical Man-Month ) 中说:

    表示是计算机编程的本质。
    ...
    给我看你的流程图并隐藏你的表格,我将继续感到困惑。给我看你的表格,我通常不需要你的流程图;他们会很明显。

    Rob Pike 在他 的编程规则中以这种方式表达了同样的想法:

    数据占主导地位。如果您选择了正确的数据结构并且组织得很好,算法几乎总是不言而喻的。数据结构,而不是算法,是编程的核心。

    Linus Torvalds 在 2006 年 6 月 27 日的 Git 邮件列表中用不同的词表达了同样的事情:

    糟糕的程序员担心代码。优秀的程序员会担心数据结构及其关系。

    关键在于:SQL 数据库模式几乎总是能更好地定义和组织表和数据结构及其关系。具有清晰、简洁和定义良好的表示几乎总能使应用程序性能更好、问题更少并且更易于开发和维护。

结论

SQLite 并不是适用于所有情况的完美应用程序文件格式。但在许多情况下,SQLite 是比自定义文件格式、文件堆或包装文件堆更好的选择。SQLite 是一种高级、稳定、可靠、跨平台、广泛部署、可扩展、高性能、可访问的并发文件格式。在您的下一个应用程序设计中,它值得您考虑作为标准文件格式。