数据库模型设计——历史与版本设计

在企业数据库设计中,常常会遇到一个需求,就是但愿把操做以前的数据保留下来,可以看到操做以前是什么数据,操做以后是什么数据。对于这种需求,咱们可使用保留历史数据或者使用版原本实现。数据库

为了可以保留历史数据,在版本设计时有如下方案:并发

 

1、使用版本号

版本号是一种常见的版本设计方案,就是在要进行历史数据保留的表上面增长一个版本号字段,该字段能够是DateTime类型,也能够是int类型,每进行数据操做时,都是建立一个新的版本,版本是只增不减的,因此只须要拿到最大一个版本号,就能获得最新的业务数据。数据库设计

版本号除了可以用于留存历史数据外,还有一个功能就是避免并发编辑操做。好比咱们有一个对象A,当前的版本是1,两个用户同时打开了该对象的编辑页面,进行数据更改。先是甲用户提交更改,这个时候系统把对象的ID和版本进行查询,发现要修改的数据最新版本是1,因此成功修改,保存了对象A的新版本2。这个时候用户乙也提交了修改。系统把对象的ID和版本1进行查询,发现要修改的数据最新版本是2,不符合要求,因此拒绝用户乙的修改。用户乙只有刷新界面,拿到最新的版本2,再进行修改。spa

ID 单号 金额 版本号
1 EXP123 100 1

在使用版本号的状况下,对单据的金额进行修改,修改后建立新的版本号2:设计

ID 单号 金额 版本号
1 EXP123 100 1
2 EXP123 120 2

2、使用生效、失效时间

保存历史数据的第二办法是使用生效失效时间来表示一个版本。要进行历史数据记录的表增长“生效时间”“失效时间”两个字段,两个字段不容许为空。对于刚建立的数据,生效时间是建立该数据的时间,失效时间是9999-12-31。如今对这条数据进行了修改,那么咱们只须要将当前时间设置为上一个版本的失效时间,同时建立一条新数据,生效时间是当前时间,失效时间是9999-12-31便可。3d

ID 单号 金额 生效时间 失效时间
1 EXP123 100 2013/9/1 15:30:00 9999/12/31 23:59:59

好比上面一条单据,是2013-9-1建立的,后来在2013-9-9 15:00:00对该单据进行修改,将金额从100修改成120,保存时建立的新数据以下:版本控制

ID 单号 金额 生效时间 失效时间
1 EXP123 100 2013/9/1 15:30:00 2013/9/9 15:00:00
2 EXP123 120 2013/9/9 15:00:00 9999/12/31 23:59:59

使用了生效、失效时间后,咱们能够查询任意时刻数据库中数据的值,只须要把要查询的时刻传入,而后between 生效时间 and 失效时间便可。对象

使用前两种方案都须要一个业务主键来标识具体的一个业务数据。若是咱们要记录的实体没有明确的“单号”、“订单号”这类的业务主键该怎么办?咱们可使用建立数据时的数据库主键做为业务主键。blog

员工ID 姓名 生日 业务ID 版本号
1 张三 1984/12/29 1 1

好比咱们有个员工表,记录员工基本信息,在建立张三这个员工的数据时,其在数据库的ID为1,那么能够将其业务ID也设置为1。接下来对张三的属性进行更改,记录了版本,那么就会建立新的版本,其主键“员工ID”会变化,可是其业务主键“业务ID”始终是1,不会变化的。ci

员工ID 姓名 生日 业务ID 版本号
1 张三 1984/12/29 1 1
2 张三 1985/1/9 1 2

使用前面两个方案虽然可以很好的记录历史数据,可是每次修改数据都会致使新版本生成保存,因此每一个版本的ID都是新的,因此必须有一个业务主键来标识一个实体,这里的两个例子“单号”就是其业务主键。主键的变更使得全部关联的对象都得变更,从而造成连锁效应,使得各个关联的对象也生成新的版本。好比咱们有个订单系统,里面有订单表和订单明细表。如今咱们要对订单的修改记录历史版本,因此增长了生效时间和实效时间,并使用订单号做为业务主键。如今有一个订单A,下面有100条明细,若是要对订单进行修改,将某一条明细的属性进行修改,从而致使整个订单的变化,那么咱们就须要建立新的订单数据行,因为主键变更,因此订单明细都须要变更,因此100条明细都须要建立新的版本,新版本的订单明细中,“订单ID”指向了新的版本的订单数据的ID。

image

这样的设计形成的问题就是订单明细表会极速膨胀,若是一个订单有1000条明细,咱们只是修改了订单自己的属性,并不修改订单明细,也会形成对这1000条明细作Copy,而后保存。那怎么办呢?咱们可使用如下办法:

1.对订单明细创建版本字段,将版本的粒度细化到订单明细,而不是订单。订单与订单明细不存在数据库级的外键关系,只存在业务级的外键关系。也就是说订单明细表中增长生效时间、失效时间以外,还须要增长“订单号”这个字段,用于表名该明细是属于哪一个订单的。

image

咱们这么修改后,若是订单对象进行了修改,订单明细没有修改(好比改了一下收件人信息),那么只须要在订单表中生成新的一行数据,订单明细不会Copy生成新的数据。若是咱们对某一条订单明细进行了更改(比调整了单价、数量)那么只须要对具体修改的那条订单明细进行更改,而不须要对整个订单的全部明细进行更改。

使用这种设计后,查询订单及其明细,须要对两个表执行生效失效时间的过滤,并且明细的获取是经过订单号去取,而不是经过订单ID去取。

将版本控制的粒度细化到订单明细时,后台程序的逻辑也会更加复杂。用户在界面上操做的是订单对象,系统会将整个修改后的订单对象传到后台,后台程序须要对每一个订单项进行对比,若是发现订单项进行了修改,那么就会调用生成新版本订单明细的方法。

2.使用单独的历史表

这是另一种实现历史版本记录的方法:

3、使用单独的历史表

使用历史表其实就是创建彻底相同Schema的表(固然,也能够添加更多的字段用于记录额外的历史版本信息),该表只保留历史版本的数据。这有点像一个归档逻辑,全部历史版本咱们认为都应该是不常常访问的,全部能够扔到单独的表,对于现有生效的版本,仍然保留在原表中,若是须要查询历史版本,那么就从历史表中查询。

使用单独的历史表有如下好处:

  • 业务数据表的数据量不会由于历史版本记录而膨胀。由于历史数据都记录到了另一个表中,因此业务数据表只记录了一份数据。
  • 业务数据表的Schema不须要调整,增长额外的版本字段。因为对原有数据表不作Schema变动,因此原有查询逻辑也不用更改。对于一个现有的数据库设计,在增长历史数据记录功能时更简单。
  • 业务数据表能够直接进行update操做,不会生成新的ID。因为ID不会变,因此咱们并须要业务主键应用到程序逻辑中。

使用历史表记录历史版本主要是要对数据操做方法(增长、删除、修改)进行修改,使得每次数据操做时,先在历史表中留痕,而后再进行数据操做。另外就是对查询历史版本功能进行修改,由于历史数据在另一个表中,因此对于的SQL是不同的。固然,咱们也能够建立历史版本数据库,里面保存了全部的历史表。

相关文章
相关标签/搜索