Reliability Lessons From SQLite
摘要
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)
本文整理了 Richard Hipp 在 2024 年关于 SQLite 设计原理与哲学演讲的要点,阐述了 SQLite 嵌入式、单文件、低复杂度的特点及其工作原理。
SQLite:万能数据库解决方案
本文倡导将SQLite视为一种多功能且稳定的数据库解决方案,强调其在不同技术栈中替代多种其他工具的能力。
@huihoo: 这次 SQLite 讨论,大家也可就此深入学习一下 SQLite 微信基于 SQLite 和 SQLCipher 构建的移动数据库框架 WCDB https://github.com/Tencent/wcdb DB Browser for…
介绍微信基于 SQLite 和 SQLCipher 构建的移动数据库框架 WCDB,并推荐相关学习资源。
在剧透与猜测之间:避免编码基准中的过度规范
该论文分析了仓库级编码基准中的过度规范与规范不足问题,指出将真值信息泄露到任务描述会形成"剧透",并提出生成简洁提示的建议以提升基准的可靠性与持久性。
我们如何在加固Turso时使用Quint在SQLite中发现超过10个漏洞
Turso使用Quint形式化验证工具对SQLite的C API进行建模,并在SQLite自身中发现了超过10个漏洞,从而增强了其SQLite重写版的可靠性。