如何在 PostgreSQL 中构建仅附加数据库?

ctw*_*els 2 postgresql database-design

前言

多年来,我们一直在使用具有糟糕且低效的数据库结构的单体程序。现在我们终于用我们自己的程序摆脱了它。

我们唯一遇到困难的领域是数据库。我们的数据是财务数据,我们现有的程序主要通过覆盖 ( UPDATE, DELETE)导致数据丢失。我们决定采用仅追加的结构来保存我们积累的所有数据。如果仓库增长超过我们最初的估计,我们可能会考虑使用它,但与此同时,我们预计数据库会在几年内增长到一些较大表的数百万行;在这一点上规模不大。

我们使用PostgreSQL,将被拒绝修改特权,比如UPDATEDELETE用户,以确保数据的一致性历史。


我们的理念

我们认为我们可以将所有当前和历史数据包含在一个表中,以便我们查询timestamp每个记录的最后一条(或最新),id如下表所示。

澄清一下,我们有几十个表;下面代表一个这样的表的简单版本。

????????????????????????????????????????????????????
? key ? id ? field1 ? field2 ? timestamp           ?
????????????????????????????????????????????????????
? 0   ? 0  ? a      ? b      ? 1970-01-01 00:00:01 ?
????????????????????????????????????????????????????
? 1   ? 1  ? x      ? y      ? 1970-01-01 00:00:02 ?
????????????????????????????????????????????????????
? 2   ? 0  ? a      ? c      ? 1970-01-01 00:05:01 ? # new version of id=0 row
????????????????????????????????????????????????????
Run Code Online (Sandbox Code Playgroud)

我们还考虑了 NoSQL 解决方案,但我们更愿意保持 ACID 兼容(并且大多数 NoSQL 解决方案无法满足该期望)。

我们还研究了其他产品,例如 AWS QLDB,但我们受到预算的限制,因此我们决定暂时使用 PostgreSQL,并在必要时重新评估。


问题

  1. 我们如何才能最好地构建数据库以确保最佳性能,同时还要记住开发人员的使用情况和易用性?
  2. 我们现有的构建数据库的想法是否可以实现最佳性能?它是否连贯?
  3. 我们可能会通过此计划为自己创造哪些限制和问题(除了磁盘空间)?

Lau*_*lbe 5

我将添加一个boolean名为最初的active表的列,并在删除或更新行版本时TRUE更改为。FALSE

这将使您的查询更简单,并且您可以使用部分索引来有效地仅搜索活动行。

您可以使用分区将活动行保留在与旧分区不同的分区中。这将使停用行的成本更高,但您可以直接在 SQL 语句中使用活动分区。这也将简化将旧数据移动到其他地方的过程(外部表可以是分区)。


Mic*_*een 5

这听起来像是时态表的用例。AFAIK PostgreSQL 本身没有实现这一点。有它的扩展。它可以用用户代码编写,通常带有触发器。

基本思想是数据表有两个日期/时间列。这些定义了行中的值适用的时间间隔 - 它是该时间间隔内该键的“该”值。我所说的键是指自然键,而不是代理行标记键,即id列,而不是key示例中的列。最好将此设置为“封闭-开放”间隔,以便包括开始时间但不包括结束时间。间隔开始和结束列的分辨率必须适合更新频率。

当该键的新值到达时,将更新现有行以设置其结束并写入新行。旧行的结束日期/时间和新行的开始日期/时间相等,因此是闭-开公式。

使用您的示例:第一行到达

? key ? id ? field1 ? field2 ? from                ? to
? 0   ? 0  ? a      ? b      ? 1970-01-01 00:00:01 ? 9999-12-31 23:59:59 ?
Run Code Online (Sandbox Code Playgroud)

一段时间后,该键的第二行到达。更新第一行,写入第二行

? key ? id ? field1 ? field2 ? from                ? to
? 0   ? 0  ? a      ? b      ? 1970-01-01 00:00:01 ? 1970-01-01 00:05:01 ?
? 2   ? 0  ? a      ? c      ? 1970-01-01 00:05:01 ? 9999-12-31 23:59:59 ?
Run Code Online (Sandbox Code Playgroud)

读取值

select <columns>
from <table>
where id = 0
and from <= <right now>
and to > <right now>
Run Code Online (Sandbox Code Playgroud)

占位符<right now>代表服务器上的当前时刻。它可以是本地的、UTC 或其他。它只需要与写入行时使用的内容一致。统一使用 UTC 使这更容易。

通过在谓词中使用该历史时间而不是 ,可以从历史上的任何点从数据库中读取一组一致的值<right now>

删除设置to为当前时间的值。

当然,也有缺点。每个表必须有额外的列。这些必须被索引。仅使用约束确保每个键的间隔不重叠是非常重要的。每个查询都必须有时间谓词,并且它们都必须使用完全相同的时间值。

可以通过添加存档表来减轻这些复杂性。to设置现有行的值后,该行也会从插入相应归档表的数据表中删除。所以每个id值在数据表中只会有一行,而在归档表中会有很多退休的行。查询不再需要额外的时间谓词。索引(在数据表上)不需要包括间隔列。历史查询变得更加复杂,存储的每个新值都会产生额外的删除和插入成本。