去年我写过一篇文章《PG数据库离企业级数据库还有多远》,实际上对PG了解得越深入,这个问题就越值得我们去思考。前几天一个做数据库高可用架构的朋友在我的公众号上留言,说在PG数据库中,如果删除了某一个数据文件,PG数据库居然不报错,还能查出数据来,不过查出来的数据是错的。这一点我以前倒是没有注意到,数据库丢失数据文件不报错是正常现象,不过查询数据的时候,如果扫描到了这部分内容,按理说应该是会报错的,比如Oracle就是如此。昨天下班前我正好有点时间,就做了个小实验。实验内容有点长,我先讲一些结论性的东西,有兴趣了解细节的朋友看完结论性的分析后再去看实验的详情吧。
数据文件的完整性检查是一个开销十分巨大的操作,因此几乎没有数据库会随时对数据文件的完整性做检查。连Oracle这种段页式结构,以表空间为组织模式的数据库都不会随时去检查数据文件的完整性和可用性。只有在访问某个数据文件的时候才会通过文件头去做一些校验。不过对于数据文件中的数据的一致性仍然不会去做检查。这是一种更大开销的操作。只有访问到相关数据的时候才会去做一致性和完整性的检查(并不是所有的访问操作都会做)。不过不管如何,RDBMS系统要尽可能保证查询出来的数据的逻辑一致性,确保数据一定是正确的。
对于Oracle这样的数据库,文件的属性被记录在control file中,新增一个文件或者文件的大小发生变化的时候,会自动更新数据。对于PG这样的每张表都会有多个文件来存储数据的数据库来说,登记每个使用过的文件是一种十分高成本的操作,一个上TB的表可能就会拥有上千个数据文件。这是一种一致性对于性能的妥协,这种妥协为PG数据库的数据一致性带来了巨大的隐患。昨天我的实验的结论是:“当PG数据文件出现丢失的时候,PG数据库不一定会因为文件丢失而报错,而是会直接返回错误的数据”。这是一种十分恐怖的特性,对于关键性的企业级应用来说,错误的数据比丢失数据还要可怕。
下面请大家看我的实验过程,我使用的PG版本是12.6,如果PG数据库在新版本中已经修复了我今天实验中的问题,也请朋友留言告知。先创建一张表,写入部分数据。从pg_class里可以看到relfilenode是16399。
可以看出目前数据文件存储在3个文件里。同时有一个fsm文件记录了空闲空间的情况。我们做个简单的count查询。
没错,我刚才写入了1000万条数据。然后我们开始作妖,删除16399.1文件看看会出现什么情况。
在另外一个窗口用rm命令删除了文件后,我们查一下这张表的数据:
这里是报错了,确实发现了刚才被删除的文件丢失了。在多次实验中,我发现有时候不会报错,可以直接成功。我们先不管不报错的场景,后面我会补充这方面的数据,现在我们重启一下数据库,再来看看。
数据库重启后,居然查询成功了,只不过数据似乎不太对,少了一些数据,而且少的还不是16399.1里的所有数据,似乎重启数据库的时候做了RECOVER。
回到目录中再去查看一下。十分奇怪,刚才被删除的文件又回来了。
从数据库的日志中我们可以看到,RDBMS是做了RECOVER,自动恢复了被删除的文件。不过这个恢复并不完整,但是系统也没有报错,这种机制会给我们带来错觉,导致业务数据的错乱,是十分可怕的。于是我再做一次删除,然后重启数据库试试。十分奇怪的是,这回数据库重启没有像上回那样RECOVER了丢失的数据文件,这种行为的不确定性也说明了PG数据库在数据一致性检查方面存在一定的缺陷,不能保持某些恢复行为的一致性,对于企业级数据库来说,这也是十分致命的。
从文件系统上看,这回丢失的文件也没有恢复。
我再查询数据,发现丢失了一半的数据,只有一半数据了。接下来再试试CTAS,完整拷贝这张表的全部数据。这个操作居然成功完成,没有任何报错,这说明RDBMS认为当前的数据是完整的,而实际上数据已经产生了严重的丢失。接下来测试下写入数据,这个测试也成功了。
这是一个十分恐怖的实验,在我的理解里,FSM里起码会记录空块的情况,丢失文件的问题应该能从查找FSM文件的时候被发现。不过这没有发生,不过也很容易理解,因为INSERT数据的时候,只需要查空块就可以了,不一定会发现问题。目前我还没有从PG的源码上去分析这个问题,因此还不是很清晰这方面的机理。不过从目前的实验上看,PG确实存在误删文件后不会被发现,导致数据出现错误的问题。这种缺陷是十分恐怖的,很多时候不怕丢数据,而是怕丢了数据你不知道。因为对于核心业务系统来说,数据准确性是最为关键的。
这回我们换一个玩法,首先我们创建好表数据,然后我们关闭数据库,再删除某个文件。重启数据库。
上图中黄线后面的操作是我重启数据库后做的,发现被删除的文件没有被恢复。再看看查询结果。
如预期的那样,没有报错,但是结果是错误的,少了400多万条数据。刚才删除文件前我备份了该文件,把该文件直接拷贝回来看看。
仍然没有报错,正确的结果回来了,是不是很神奇!!!。从上面的实验可以看到,如果在PG数据库中丢失某个数据文件,那么数据库的行为可能是不确定的,不过大概率会给你返回错误的数据。这种特性会对于关键的企业级应用带来困难。因此我们必须在尽可能不影响数据库性能的前提下弥补这个缺陷。至于如何弥补,可能需要对源代码做一些解读后才能想办法。今天的实验先到这里吧,源代码的解读随后有时间再做。有兴趣的朋友也可以去阅读分析一下。