联系支持

我们通过电子邮件回复,通常两天内。

为防止滥用,Google reCAPTCHA 会检查这次提交,其间会向 Google 传输数据。脚本只在此表单打开时才加载。

← 全部文章

数据库结构实际存在哪里

关于内容及其审核

本文内容

这个服务所做的一切都在版本控制里。至少代码是。它运行其上的数据库是 32 张表,其中 15 张在仓库里任何地方都没有定义 — 没有 CREATE TABLE,没有结构文件,什么都没有。一份备份能把 32 张表连同结构一起还原;仓库里缺的,是这份结构的第二份、独立的副本。

17 张表有定义 15 张哪里都没有 21 个索引由代码创建 11 个只在库里 32 张表,32 个二级索引,0 个结构文件

这些数字来自把运行中的数据库与仓库里的 736 个文件、5,671,710 个字符做比对。它们不是估计。

半套结构是怎么丢的

这不是马虎,而这正是它值得写下来的原因。单看每一步都是合理的。

在现有代码之前建的表从来没被写下来,因为当时它们就是在那儿。此后新增的表是通过迁移脚本来的,而那些脚本确实在仓库里 — 那 17 处定义就是从这来的。更晚加上的列,是在命令行敲一行 ALTER TABLE 加的,因为敲它比为一个一秒钟的改动写脚本要快。

索引是最糟的情形。索引是在某个东西变慢的时候加的,就在它变慢的那一刻,而且立刻就解决了问题。它不留下任何产物,没有东西可审,忘了也不会有什么失败。这里三十二个二级索引中有十一个正是这么来的,而且哪里都没有记录。

存放计数器本身的那张表,166,438 行,就在这十五张没有定义的表里。

真正会坏的是什么

不多,直到某个特定时刻:数据库从转储恢复的时候。

转储带着结构,所以直接恢复没问题。危险的是任何重建表而不是恢复表的路径 — 迁到另一台机器、只导入部分表、从脚本重新建一张。数据回来了,索引悄悄地没回来。什么都没有失败。查询返回正确的答案。它们只是变慢了,而原因不可见,因为关于那个索引是什么的唯一记录,就在那台已经没有它的机器上。

这是值得起个名字的失效方式:缺失的索引不产生错误,它产生一个更慢的正确答案。没有测试能抓住它,因为测试都通过。

在此之前我们做什么

结构今天不在仓库里,这是对现状的准确描述。现有的东西比一个完整的修复要窄,而它覆盖的正是真正要付出代价的那种情形:

在任何破坏性操作之前先做转储,存在网站根目录之外。一次真正执行过、而不是假定过的恢复。以及一份短名单,每次恢复之后核对一遍,列出已知只存在于运行中数据库里的那些索引 — 因为机器是可以被问「现在有哪些索引」的,而答案可以和上一次的对照。

一般化的说法不止适用于数据库。如果系统的一部分只存在于运行中的系统里,它就没有第二份副本。对任何一块基础设施值得问的问题,不是它有没有备份,而是如果这台机器消失了,有什么是无法从仓库重建的。这里的答案是十五张表和十一个索引,弄清楚它花了一个下午,而它是一份清单,不是一个未知数。

更新:2026 年 9 月

这篇文章发出五天后,工作目录被提交了:26 个文件,4,058 行,其中十一个是 8 月 28 日到 9 月 1 日之间写的迁移脚本,在此之前只存在于那台运行中的机器上。这是容易的那一半——写出来却从未被记录的代码。

2026 年 9 月 1 日,用同样的办法重新测了一遍:运行中的数据库,对上 git 知道的一切。

表4630 张在仓库里有 CREATE TABLE,16 张哪里都没有
二级索引4332 个由仓库里的代码建立,11 个只在运行中的数据库里
迁移脚本109在版本控制下
30 张表有定义 16 张哪里都没有 32 个索引有文件 11 个只在库里 46 张表,43 个二级索引,0 个结构文件

真正要紧的那个数字没有动。8 月 27 日,有十一个二级索引除了运行中的数据库以外哪里都不存在。9 月 1 日,还是十一个。这中间又加了十一个索引,而且每一个都带着迁移脚本:习惯变了,欠账没变。

这十一个落在五张表上——计数器、语言、来源页、排除名单和事件记录。这五张都比今天的代码更老,正因如此才从来没有为它们写下过什么。现在补一个文件,意味着要从一台运行中的服务器上,还原多年前在命令行里敲下的东西。

所以八月的说法原样成立。结构依然不在仓库里,而一次重建表而非还原表的恢复,仍然会一声不响地丢掉十一个索引。变的是更小的一件事:此后添加的每一样,都留下了一个文件。

广告