数据库设计杂谈
2018-03-22 08:58 Fururur 阅读(2581) 评论(11) 编辑 收藏 举报注:本人开发经验尚浅,下文主要谈的是自己的一些想法,不足之处请指出。
最近半年时间都花在管理系统的开放上面,对数据库的设计有一些自己的想法,在我看来数据库设计的key point就是妥协。一个设计的比较好的数据库都是在业务逻辑、设计规约和便于开发这三者之前来回考量,从而获得3-win的结果。下面主要是在思考和总结的点。
如何设计出高灵活性的数据库
可以说在项目交付前,需求不断在变,如何在需求改变的同时尽可能减少对表结构的修改是我现在考虑的问题。对于一般情况而言,在设计的时候我们可以适当添加一些预留的字段,需求改变的时候可以用上,或者说是添加不足的字段。但是也会出现一些比较麻烦的情况——旧表不足以进行修改维护,需要局部重新设计,这就会给开发带来比较多的麻烦,因为大多现在我们都会使用ORM。
eg1:
在贷款的业务流程中,每一笔借款申请需要进行审核。
起初,参与的审核角色只有2名,所以我选择了,直接添加字段到贷款申请表中。
之后,参与审核的角色数变多,并且审核产生的业务参数变多(例如各类附件、意见等),直接添加字段到贷款表中显得不那么合理,同时不利于审核流程的体现。因此,这一部分需要单独重新设计,根据业务流的需要,将审核的事件单独拉出来一张表,通过外键关联到申请表中。(流程复复杂可参考工作流引擎的设计思路)
这个案例里,最初的设计,觉得既然审核人数少,直接把审核信息添加到申请表中即可,不需要单独建表,也方便了开发,但是后来的需求改变就有单措手不及了。这里就感觉很矛盾,同时也发现数据库的设计居然也和软件工程那一套理论惊人的相似。在软件工程里面,我们经常会听到一句话“低耦合、高内聚”,一般是提醒我们,尽可能保证模块与模块之间相互独立,减少不必要的依赖关系。
这也就是困扰这我的地方,如果一开始设计就考虑单一职责,分成两张表,未免显得有些复杂化了,也不利于开发(需要维护1-n的关系)。毕竟代码一定程度上是可以重构的。
数据库原理在实际设计中的运用
我们在《数据库原理与设计》中学过很多的概念,由于实验环境的限制(数据量小、课设的业务偏简单etc.),导致很多概念在那时真的就变成了一个死记硬背的概念。这点或许随着开发经验的增长会豁然开朗吧。下面介绍几个,替他们刷一下存在感,内容都很浅,但也希望对在学习数据库的有一点帮助吧。
范式(NF)
1NF的定义为:符合1NF的关系中的每个属性都不可再分
2NF在1NF的基础之上,消除了非主属性对于码的部分函数依赖。
3NF在2NF的基础之上,消除了非主属性对于码的传递函数依赖。
...
范式的概念大家肯定背的很熟,本人研究生面试也考了这个。这一点其实大家在按设计数据库的时候都已经用上了。明确实体间的关系,画E-R图,其实就是对范式的运用。
索引(Index)
Index,面试的时候也经常会问到,但是呢学他的时候基本上感觉不到他的存在,毕竟测试的数据集太小了。底层的原理牵扯到数据结构,这里就不展开了。但是需要知道的是:
- 需要做表连接的字段,需要添加index。
- 经常需要查询的字段,需要添加index。
- 很多字段需要联查的时候,需要添加组合查询。
下面是一个实际项目中的例子,可以感受下:
有一个地方需要数据展示,需要多表联查。下面就是建索引和不建索引的区别了。
事务(Transaction)
数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。 事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使应用程序更加可靠。一个逻辑工作单元要成为事务,必须满足所谓的ACID(原子性、一致性、隔离性和持久性)属性。事务是数据库运行中的逻辑工作单位,由DBMS中的事务管理子系统负责事务的处理。
这个写过管理系统的应该都明白。大概就是下面这个模型。JDBC中就有,在ssh或ssm中可以配合注解或配置文件来实现。
BeginTransaction{
// to-do
commit
}catch{
rollback
}
一般在多表操作时必须使用事务!例如级联删除,多表修改。如果不使用,一旦出现异常,会导致数据不一致!
具体可参考:
锁(lock)
锁根据不同的法,可以分为乐观锁、悲观锁。也可以分为共享锁和排它锁。
一般在出现并发问题的时候会用到锁,具体可以看我另外一篇博客——并发初体验,解决小规模并发下单问题。里面详细讲了锁的描述和实现。
笔者经验有限,其实还有很多概念类似,数据库安全,容灾备份等没有接触到的,希望以后有机会可以补全。
冗余字段的利用
冗余字段的利用,其实仅仅是为了开发方便的一个tip。
具体可以看上面一个小例子。一笔借款需要经过很多人的审核才能通过。因此存在借款表和审核流程表,两者一对多。通过借款单号可以去审核表中查询其所有的审核情况。
那么此时,我选择在借款表中存审核流程表id(外键)的同时,存下当前这一笔借款的审核信息。这么做的好处就是,我不需要去做表连接,直接可以查询到借款的当前进行到哪一流程,或者说申请失败,失败在哪一流程。从开发的角度,如果有一个页面需要显示出借款的当前流程,那就可以轻松查询到了。
毕竟,处理表连接操作是相对麻烦的,同时效率并不高。因此在存储条件允许的情况下,可以通过适当冗余来减少开发中的麻烦。这里就体现了数据库设计的一种妥协,现在存在冗余是违反了2-NF的,所以有的时候就需要凭经验来进行调整。对于不是频繁需要修改的字段,是可以适当冗余存取的,反之,经常需要修改的字段,如果冗余存取,一不小心很容易造成数据的不一致,就不建议了。
视图与冗余表
在谈冗余表前,先来看几个在Java OO中的概念,抛砖引玉一下。
VO(View Object):视图对象,用于展示层,它的作用是把某个指定页面(或组件)的所有数据封装起来。
DTO(Data Transfer Object):数据传输对象,这个概念来源于J2EE的设计模式,原来的目的是为了EJB的分布式应用提供粗粒度的数据实体,以减少分布式调用的次数,从而提高分布式调用的性能和降低网络负载,但在这里,我泛指用于展示层与服务层之间的数据传输对象。
DO(Domain Object):领域对象,就是从现实世界中抽象出来的有形或无形的业务实体。
PO(Persistent Object):持久化对象,它跟持久层(通常是关系型数据库)的数据结构形成一一对应的映射关系,如果持久层是关系型数据库,那么,数据表中的每个字段(或若干个)就对应PO的一个(或若干个)属性。
首先,这些对象其实是和数据库中数据表存在一些关系的。我想说的是,在数据展示或数据传输时,可能会涉及多张表,这个时候我们会做的就是创建一个新的Object,然后讲多张表对应的持久化对象放进去,或者直接从多个表的持久化对象中抽取需要的字段。这个对象也就是上面提到的VO和DTO了。
这种做法同时也可以在数据库进行实现,那就是冗余表。其实也就是把很多张表的数据统一到一张表中去,为的也是查询和更新的方便。
之前遇到以下这个业务:
存在订单、订单明细之前一对多,订单又需要和卡绑定,一对多。现在我需要将数据一起显示出来看,同时粒度要最细,以订单明细和卡号的形式显示出来,t同时可更新。
eg:有订单记录A ,订单明细B1、B2,绑定了两张卡C1、C2,那么显示效果如下
C1 A B1
C1 A B2
C2 A B1
C2 A B2
这个时候考虑到如果创建一个DTO来保存这些数据的话,在查询组装上需要花很多的时间,于是就直接创建了一张冗余表,用于维护这层关系。那么通过这层冗余表查询出来的结果就是可以直接用于显示的,更新的时候也只需要直接找到外键进行更新就可以了。
我觉得使用DTO还是冗余表,完全就是取决于开发上的的便利以及是否需要对数据进行持久化了。
同样的,之前的例子提到是需要对数据进行更新或删除的,如果仅仅是为了数据的展示,例如一些统计数据的展示。那么就不需要冗余表了。数据库里面有个专有的概念——视图。比如说查询每天每年每季度的销售情况等等,直接用sql创建视图,查询以天为单位的结果,展示的时候直接从view中取数据即可,将复杂的sql语句就留在数据库那边了。
当然创建视图若使用大量表连接时,记得创建index进行优化,否则查询效率会很低。
数据字典与代码表
数据字典与代码表其实存储的就是一些常量了。
数据字典一般两张表,一张存类别,一张存键值对。数据字典一般用于存取一些不太会变的数据,例如性别、订单状态etc。而且这些数据量比较小,可以统一存储。
代码表,一般都是各管各存储的。例如文章分类表、标签表、省市区代码、商品分类etc。这些本来数据就大,并且有一定的业务性,有些会随着业务的扩大而变动,因此单独存表会比较好。
总结
一下子码了很多字,主要写了一些数据库中的一些概念在实际应用上的体现。表达了最近一段时间对数据库设计上面的一些思考和见解。人是会思想的芦苇,实践是检验真理的唯一标准,嗯……
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载,演绎或用于商业目的,但是必须保留本文的署名Fururur(包含链接),如您有任何疑问或者授权方面的协商,请给我留言。