Reliability Lessons From SQLite

Lobsters Hottest 新闻

摘要

SQLite 开发者分享其可靠性经验,源自 DO-178B 航空电子标准,通过 100% MCDC 测试覆盖率确保代码质量,强调“没有测试过就不工作”的理念。

<p><a href="https://lobste.rs/s/kprcuc/reliability_lessons_from_sqlite">Comments</a></p>
查看原文
查看缓存全文

缓存时间: 2026/08/03 17:37

TL;DR: SQLite 的可靠性源于其 100% MCDC 测试覆盖率、严格的测试方法论,以及从 DO-178B 航空电子标准中汲取的经验。 ## 起源:Informix 之痛与 SQLite 的诞生 SQLite 的诞生源于一段痛苦的经历。1990 年代,我还是一个分包商,工作是为一个 NP 完全问题寻找近似解,并用图形化方式展示。我的程序需要与一个 Informix 数据库通信,但 Informix 由客户指定,我没有任何控制权。每当服务器宕机,我的程序就只能弹出一个对话框——这在当时已是最先进的对话框了。 最终用户看到对话框后会给我发 bug 报告,这让我非常难过。我开始思考:我们真的需要那台服务器吗?为什么不能直接读写磁盘上的数据,去掉中间那个给我带来痛苦的环节? 我四处寻找现成的解决方案,但没有找到类似的东西。于是我想:好吧,那我就自己写一个数据库引擎。这能有多难呢?事实证明,这比你想的要难得多。这就是 SQLite 的起源故事——它诞生是因为软件不好用。 ## 什么是 SQLite SQLite 是一个功能完备的 SQL 实现,具有强大的原子事务。它是一个 C 库,不是一个独立的服务器系统。数据库就是磁盘上的一个文件,你可以直接移动它,甚至用电子邮件发给朋友。 - 文件格式定义良好。美国国会图书馆已将 SQLite 数据库文件指定为二进制数据长期归档存储的首选介质。 - 向后兼容。 - 属于公有领域,免费发布。 - 由一个三人团队维护(包括我自己)。 - 所有代码在单个 C 语言文件中,超过 25 万行。实际开发时我们维护多个源文件,通过构建过程拼接成一个大文件。 - 设计寿命是 50 年。第一行代码写于 2000 年 5 月 29 日。我还有 24 年要干。 SQLite 可能已经成为世界上使用最广泛的软件。你的手机里,周围五到六英尺之内,大概就有几百个 SQLite 实例在安静运行。据可靠估计,现在有超过一万亿个 SQLite 数据库在活跃使用。苹果和谷歌的人告诉我,你手机上大约一半的文件系统 IO 是 SQLite,剩下的大部分是媒体文件。 ## 为什么 SQLite 如此普及 原因很直接: 1. **它有用**。人们需要存储数据。俗话说:没有数据,你要么是运气好,要么就是错了。 2. **它易于部署**。一份 C 代码文件,放进程序里编译即可。 3. **开源且公有领域**。你可以用它做任何想做的事(人们确实会对它做非常不自然的事情)。 4. **免费**。 5. **没有低级别依赖**。只用了标准 C 库,不需要担心庞大的供应链。 6. **资源消耗低**。可以在智能手表和小型嵌入式设备上运行。 7. **通常不会崩溃**。 它变的流行,是因为它解决的问题比它制造的问题多。这其实也是一种很好的人生哲学:如果你想成功,那就解决比你制造的多的问题。 ## 从 DO-178B 学到的可靠性方法 SQLite 在 2004 年发布第三版后开始被科技巨头采用(美国在线、摩托罗拉、诺基亚,以及山景城一家当时不起眼的创业公司——Android)。Android 几乎每天给我发一个 bug 报告,这让我意识到:我毕竟不能写出完美的软件。 当时我为 Rockwell Collins(一家航空电子设备制造商)做一些工作,他们向我介绍了 DO-178B 文件。这是一份非常简洁的文件,只有 84 页,每页 6.5 美元。大多数质量保证文件都像书架一样厚,但这份不是。 DO-178B 从三个角度看软件: - 需求分析和跟踪——SQLite 也在做,但今天不讲。 - 配置和生命周期管理——启发我写了自己的版本控制系统 Fossil。 - **测试**——这是本次演讲的重点。 DO-178B 给我们的最大教训是:**如果没有测试过,它就不工作。** ## 100% MCDC 测试覆盖率 SQLite 遵循 100% MCDC(修改条件/判定覆盖)。这是一个拗口的词,有严格的技术定义。在 SQLite 的语境中,我把它简化为两件事: 1. 机器码级别的每个分支操作都经过两个方向的测试。 2. 位掩码测试中的每一位都会对结果产生影响。 例如,对于这样一段代码(编造的): ``` if (a == 5 || b == 7) { c = 11; } ``` 你至少需要四个测试用例: - a 不等于 5(由于短路求值,此时不需要处理 b) - a 等于 5 且 b 等于 7 - a 等于 5 且 b 不等于 7 且 c 等于 11 - a 等于 5 且 b 不等于 7 且 c 不等于 11 对于位掩码测试,比如 `x & 7`,你需要确保每一位都在测试中被单独测过。 ### 如何度量覆盖率 GCC 允许你用特殊方式编译,运行程序后生成 gcov 格式的输出。以 SQLite 为例,输出文件大约 15.5 兆字节,32 万行,显示每个分支操作被执行了多少次。脚本处理这个文件,查找执行次数为零的情况。 代码中还使用了 `test case` 宏。在测试覆盖率时,它会增加一个分支,并让优化器无法优化掉它。在交付的产品中,`test case` 什么都不做;它只在检查测试值时运行。这个宏也用于检查边界条件,确保没有差一错误。 ## 三种必须进行的测试 DO-178B 详细阐述了三种不同类型的测试: 1. **测试你的测试用例**。验证测试用例真的在测试你认为它们该测试的东西,确保覆盖了所有需求且测试本身正确。 2. **测试源代码**。确保源代码得到正确的答案,同时使用 sanitizer、静态分析器等工具。 3. **测试交付的目标代码**。我们不信任编译器。仅仅因为源代码正确,并不意味着编译后的代码是正确的。 事实上,SQLite 历史上曾在 GCC、Clang 和 MSVC 中都发现过编译器 bug。你可以在 SQLite 源代码里找到针对这些 bug 的规避代码——即使在修复这些 bug 的新版本编译器发布后,人们仍在使用旧版本编译器,所以这些规避代码仍然存在。 ## 如何测试 SQLite 产品 SQLite 是一个 C 库。通常应用程序与 C 库通信,C 库与文件系统通信。SQLite 是单体结构,有可插拔的组件和子组件。测试程序就像一个普通应用程序,以实际使用的方式与库通信。同时,测试程序可以通过特殊接口进入内部,以便进行更深入的检查。 ## 结语 SQLite 的可靠性不是偶然的。它遵循航空电子级别的测试标准,实现了 100% MCDC 覆盖率,并用严格的测试方法论来验证每一条代码路径。这些经验对其他软件项目同样适用:**如果没有测试过,它就不工作。** Source: [Reliability Lessons From SQLite - YouTube](https://www.youtube.com/watch?v=V_qzqY1bb7I)

相似文章

SQLite: How it works, by Richard Hipp (2024)

Lobsters Hottest

本文整理了 Richard Hipp 在 2024 年关于 SQLite 设计原理与哲学演讲的要点,阐述了 SQLite 嵌入式、单文件、低复杂度的特点及其工作原理。

SQLite:万能数据库解决方案

Lobsters Hottest

本文倡导将SQLite视为一种多功能且稳定的数据库解决方案,强调其在不同技术栈中替代多种其他工具的能力。