数据库模型设计——历史与版本设计
在企业数据库设计中,经常会遇到一个需求,就是希望把操作之前的数据保留下来,能够看到操作之前是什么数据,操作之后是什么数据。对于这种需求,我们可以使用保留历史数据或者使用版本来实现。
为了能够保留历史数据,在版本设计时有以下方案:
一、使用版本号
版本号是一种常见的版本设计方案,就是在要进行历史数据保留的表上面增加一个版本号字段,该字段可以是DateTime类型,也可以是int类型,每进行数据操作时,都是创建一个新的版本,版本是只增不减的,所以只需要拿到最大一个版本号,就能得到最新的业务数据。
版本号除了能够用于留存历史数据外,还有一个功能就是避免并发编辑操作。比如我们有一个对象A,当前的版本是1,两个用户同时打开了该对象的编辑页面,进行数据更改。先是甲用户提交更改,这个时候系统把对象的ID和版本进行查询,发现要修改的数据最新版本是1,所以成功修改,保存了对象A的新版本2。这个时候用户乙也提交了修改。系统把对象的ID和版本1进行查询,发现要修改的数据最新版本是2,不符合要求,所以拒绝用户乙的修改。用户乙只有刷新界面,拿到最新的版本2,再进行修改。
ID | 单号 | 金额 | 版本号 |
1 | EXP123 | 100 | 1 |
在使用版本号的情况下,对单据的金额进行修改,修改后创建新的版本号2:
ID | 单号 | 金额 | 版本号 |
1 | EXP123 | 100 | 1 |
2 | EXP123 | 120 | 2 |
二、使用生效、失效时间
保存历史数据的第二办法是使用生效失效时间来表示一个版本。要进行历史数据记录的表增加“生效时间”“失效时间”两个字段,两个字段不允许为空。对于刚创建的数据,生效时间是创建该数据的时间,失效时间是9999-12-31。现在对这条数据进行了修改,那么我们只需要将当前时间设置为上一个版本的失效时间,同时创建一条新数据,生效时间是当前时间,失效时间是9999-12-31即可。
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 失效时间即可。
使用前两种方案都需要一个业务主键来标识具体的一个业务数据。如果我们要记录的实体没有明确的“单号”、“订单号”这类的业务主键该怎么办?我们可以使用创建数据时的数据库主键作为业务主键。
员工ID | 姓名 | 生日 | 业务ID | 版本号 |
1 | 张三 | 1984/12/29 | 1 | 1 |
比如我们有个员工表,记录员工基本信息,在创建张三这个员工的数据时,其在数据库的ID为1,那么可以将其业务ID也设置为1。接下来对张三的属性进行更改,记录了版本,那么就会创建新的版本,其主键“员工ID”会变化,但是其业务主键“业务ID”始终是1,不会变化的。
员工ID | 姓名 | 生日 | 业务ID | 版本号 |
1 | 张三 | 1984/12/29 | 1 | 1 |
2 | 张三 | 1985/1/9 | 1 | 2 |
使用前面两个方案虽然能够很好的记录历史数据,但是每次修改数据都会导致新版本生成保存,所以每个版本的ID都是新的,所以必须有一个业务主键来标识一个实体,这里的两个例子“单号”就是其业务主键。主键的变动使得所有关联的对象都得变动,从而形成连锁效应,使得各个关联的对象也生成新的版本。比如我们有个订单系统,里面有订单表和订单明细表。现在我们要对订单的修改记录历史版本,所以增加了生效时间和实效时间,并使用订单号作为业务主键。现在有一个订单A,下面有100条明细,如果要对订单进行修改,将某一条明细的属性进行修改,从而导致整个订单的变化,那么我们就需要创建新的订单数据行,由于主键变动,所以订单明细都需要变动,所以100条明细都需要创建新的版本,新版本的订单明细中,“订单ID”指向了新的版本的订单数据的ID。
这样的设计造成的问题就是订单明细表会极速膨胀,如果一个订单有1000条明细,我们只是修改了订单本身的属性,并不修改订单明细,也会造成对这1000条明细做Copy,然后保存。那怎么办呢?我们可以使用以下办法:
1.对订单明细建立版本字段,将版本的粒度细化到订单明细,而不是订单。订单与订单明细不存在数据库级的外键关系,只存在业务级的外键关系。也就是说订单明细表中增加生效时间、失效时间之外,还需要增加“订单号”这个字段,用于表名该明细是属于哪个订单的。
我们这么修改后,如果订单对象进行了修改,订单明细没有修改(比如改了一下收件人信息),那么只需要在订单表中生成新的一行数据,订单明细不会Copy生成新的数据。如果我们对某一条订单明细进行了更改(比调整了单价、数量)那么只需要对具体修改的那条订单明细进行更改,而不需要对整个订单的所有明细进行更改。
使用这种设计后,查询订单及其明细,需要对两个表执行生效失效时间的过滤,而且明细的获取是通过订单号去取,而不是通过订单ID去取。
将版本控制的粒度细化到订单明细时,后台程序的逻辑也会更加复杂。用户在界面上操作的是订单对象,系统会将整个修改后的订单对象传到后台,后台程序需要对每个订单项进行对比,如果发现订单项进行了修改,那么就会调用生成新版本订单明细的方法。
2.使用单独的历史表
这是另外一种实现历史版本记录的方法:
三、使用单独的历史表
使用历史表其实就是建立完全相同Schema的表(当然,也可以添加更多的字段用于记录额外的历史版本信息),该表只保留历史版本的数据。这有点像一个归档逻辑,所有历史版本我们认为都应该是不经常访问的,所有可以扔到单独的表,对于现有生效的版本,仍然保留在原表中,如果需要查询历史版本,那么就从历史表中查询。
使用单独的历史表有以下好处:
- 业务数据表的数据量不会因为历史版本记录而膨胀。因为历史数据都记录到了另外一个表中,所以业务数据表只记录了一份数据。
- 业务数据表的Schema不需要调整,增加额外的版本字段。由于对原有数据表不做Schema变更,所以原有查询逻辑也不用更改。对于一个现有的数据库设计,在增加历史数据记录功能时更简单。
- 业务数据表可以直接进行update操作,不会生成新的ID。由于ID不会变,所以我们并需要业务主键应用到程序逻辑中。
使用历史表记录历史版本主要是要对数据操作方法(增加、删除、修改)进行修改,使得每次数据操作时,先在历史表中留痕,然后再进行数据操作。另外就是对查询历史版本功能进行修改,因为历史数据在另外一个表中,所以对于的SQL是不一样的。当然,我们也可以创建历史版本数据库,里面保存了所有的历史表。
数据库模型设计——历史与版本设计的更多相关文章
- Atitit.操作注册表 树形数据库 注册表的历史 java版本类库总结
Atitit.操作注册表 树形数据库 注册表的历史 java版本类库总结 1. 注册表是树形数据库 1 2. 注册表的由来 1 3. Java 操作注册表 2 3.1. 使用Preferences ...
- CentOS以及Oracle数据库发展历史及各版本新功能介绍, 便于构造环境时有个对应关系
CentOS版本历史 版本 CentOS版本号有两个部分,一个主要版本和一个次要版本,主要和次要版本号分别对应于RHEL的主要版本与更新包,CentOS采取从RHEL的源代码包来构建.例如CentOS ...
- 数据库模型设计PowerDesigner
Power Designer 是Sybase公司的CASE工具集,使用它可以方便地对管理信息系统进行分析设计,他几乎包括了数据库模型设计的全过程.利用Power Designer可以制作数据流程图.概 ...
- One to One 的数据库模型设计与NHibernate配置
在数据库模型设计中,最基本的实体关系有三种:一对一.一对多.多对多.关于一对多和多对多使用的情况较多,之前也有过一些讨论,现在来说明一下在数据库中一对一的模型设计. 首先,关系数据库中使用外键来表示一 ...
- HTTP 协议的历史演变和设计思路
HTTP 协议是互联网的基础协议,也是网页开发的必备知识,最新版本 HTTP/2 更是让它成为技术热点. 本文介绍 HTTP 协议的历史演变和设计思路. 一.HTTP/0.9 HTTP 是基于 TCP ...
- ECSHOP 数据库结构说明 (适用版本v2.7.3)
ECSHOP 数据库结构说明 (适用版本v2.7.3) 1.account_log 用户账目日志表 字段 类型 Null/默认 注释 log_id mediumint(8) 否 / 自增 ID 号 u ...
- ASP.NET MVC+EF框架+EasyUI实现权限管理系列(15)-用户登录详细错误和权限数据库模型设计
原文:ASP.NET MVC+EF框架+EasyUI实现权限管理系列(15)-用户登录详细错误和权限数据库模型设计 ASP.NET MVC+EF框架+EasyUI实现权限管系列 (开篇) ...
- 从HTTP/0.9到HTTP/2:一文读懂HTTP协议的历史演变和设计思路
本文原作者阮一峰,作者博客:ruanyifeng.com. 1.引言 HTTP 协议是最重要的互联网基础协议之一,它从最初的仅为浏览网页的目的进化到现在,已经是短连接通信的事实工业标准,最新版本 HT ...
- [转帖]从HTTP/0.9到HTTP/2:一文读懂HTTP协议的历史演变和设计思路
从HTTP/0.9到HTTP/2:一文读懂HTTP协议的历史演变和设计思路 http://www.52im.net/thread-1709-1-2.html 本文原作者阮一峰,作者博客:r ...
随机推荐
- border-style 属性
border-style 属性用于设置元素所有边框的样式,或者单独地为各边设置边框样式. 只有当这个值不是 none 时边框才可能出现. 例子 1 border-style:dotted solid ...
- 《第一行代码》学习笔记——第1章 开始启程,你的第一行Android代码
1.3 创建你的第一个Android项目 1.3.1 创建HelloWorld项目 1.Application Name代表应用名称,手机上显示的就是它: 2.Project Name代表项目名称,其 ...
- 树链剖分求LCA
树链剖分中各种数组的作用: siz[]数组,用来保存以x为根的子树节点个数 top[]数组,用来保存当前节点的所在链的顶端节点 son[]数组,用来保存重儿子 dep[]数组,用来保存当前节点的深度 ...
- redis主从 以及认证配置
以前用redis用的很多,各种数据类型用的飞起,算是用得很溜了.不过那都是封装好的方法,自己直接调用.以前的公司比较规范,开发只是开发,很少去做跟运维相关的事情. 换了一份工作,不过这边项目刚开始起步 ...
- PHP内核探索之变量(7)- 不平凡的字符串
切,一个字符串有什么好研究的. 别这么说,看过<平凡的世界>么,平凡的字符串也可以有不平凡的故事.试看: (1) 在C语言中,strlen计算字符串的时间复杂度是?PHP中呢? ...
- Lucene.net站内搜索—5、搜索引擎第一版实现
目录 Lucene.net站内搜索—1.SEO优化 Lucene.net站内搜索—2.Lucene.Net简介和分词Lucene.net站内搜索—3.最简单搜索引擎代码Lucene.net站内搜索—4 ...
- Java经典实例:进阶版堆栈实现,支持任何对象类型
支持任何对象类型,有更多的错误检查. package Stack; /** * Created by Frank */ public class MyStack<T> implements ...
- js 字符串 replace replaceAll
var str = "男的女的老的少的"; alert(str.replace('的','')); 可以看到替换后的str的值为"男女的老的少的",replac ...
- css知多少(3)——样式来源与层叠规则
上一节<css知多少(2)——学习css的思路>有几个人留言表示思路很好.继续期待,而且收到了9个赞,我还是比较欣慰的.没看过的朋友建议先去看看上一节. 这一节就开始实践上一节的思路! 1 ...
- jQuery cxScroll 间歇式无缝滚动
版本: jQuery v1.7+ jQuery cxScroll v1.2.2 注意事项: 内部会自动创建 prev 及 next 切换按钮,也可以在外部直接创建,若外部已创建或设置prevBtn: ...