ctw*_*els 2 postgresql database-design
多年来,我们一直在使用具有糟糕且低效的数据库结构的单体程序。现在我们终于用我们自己的程序摆脱了它。
我们唯一遇到困难的领域是数据库。我们的数据是财务数据,我们现有的程序主要通过覆盖 ( UPDATE
, DELETE
)导致数据丢失。我们决定采用仅追加的结构来保存我们积累的所有数据。如果仓库增长超过我们最初的估计,我们可能会考虑使用它,但与此同时,我们预计数据库会在几年内增长到一些较大表的数百万行;在这一点上规模不大。
我们使用PostgreSQL,将被拒绝修改特权,比如UPDATE
与DELETE
用户,以确保数据的一致性历史。
我们认为我们可以将所有当前和历史数据包含在一个表中,以便我们查询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,并在必要时重新评估。
我将添加一个boolean
名为最初的active
表的列,并在删除或更新行版本时TRUE
更改为。FALSE
这将使您的查询更简单,并且您可以使用部分索引来有效地仅搜索活动行。
您可以使用分区将活动行保留在与旧分区不同的分区中。这将使停用行的成本更高,但您可以直接在 SQL 语句中使用活动分区。这也将简化将旧数据移动到其他地方的过程(外部表可以是分区)。
这听起来像是时态表的用例。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
值在数据表中只会有一行,而在归档表中会有很多退休的行。查询不再需要额外的时间谓词。索引(在数据表上)不需要包括间隔列。历史查询变得更加复杂,存储的每个新值都会产生额外的删除和插入成本。