前言:
不知道现在的大学里面,在学习关系数据库时,还提不提数据库三范式
第一范式:列不可拆
第二范式:主键唯一
第三范式:外键关联,避免数据冗余
数据库发展到现在,无论是从数据库本身的设计层面,还是业界对于数据库的使用层面,上述三范式中,除了主键唯一这个范式还被遵守,其余的两个都已经被突破了。首先,"列不可拆",MySQL 5.7 之后引入JSON类型,突破。其次,"外键关联,避免数据冗余"这点,在分布式应用,分库分表的场景下,比如陆金所,应用层禁止一切关联查询。其他的一些大厂,禁止三张表以上的关联查询,而且为了减少DB IO次数,在设计上会支持一定程度的数据冗余,第三范式也被突破。
业务需求变动的时候数据库设计面临的问题
很多的系统,从分层上都是这样的,见下图。
有句话叫“世界上唯一不变的是变化本身”,软件系统也不例外,当系统上线之后,产品经理跑过来说"XXYYZZ,很简单的。",然后你从DB开始进行变更,然后到SQL,加上DAO,SERVICE,WEB层的各种DO,DTO一路改下去;没过几天,产品经理又来了,循环往复。那么从设计上有没有办法来规避呢?
数据库设计如何应对需求变动
1.主表,拓展表
在设计的时候,将主要属性,或者说第一个版本的属性,直接放入主表,后续有新加的字段,直接放入拓展表中。
以员工表为例,主表中存姓名,年龄,入职日期这些字段,后续需求变动,产生的新字段直接以key,value的形式存在拓展表中。比如需要加一个职级,就在拓展表里加一条记录,key="emp_level",value=”研究员“。
那么在应用层面,拓展表的信息是怎么体现的呢?在DO层面有一个Map属性,用来存拓展信息
public class Employees {
private Integer empNo;
private Date birthDate;
private String firstName;
private String lastName;
private String gender;
private Date hireDate;
private Map<String, String> extInfo = new HashMap<String, String>();
}
每一个查询,需要两个sql,一个sql查主表,一个sql去查拓展表,查完之后,再将拓展表的查询结果装配进extInfo属性中。
这种方案我个人认为比较复杂,数据库层面多了一个表,而且一组key:value就需要增加一条记录,会增加数据量级;应用层面的问题是,需要有一个装配拓展属性的工作量。
2.JSON类型的字段
这种方案是,在主表上直接预留一个JSON类型的字段,如果数据库版本不支持,直接使用String字段。
需求变动的时候,json本身就是一个变化的结构,可以很好的应对。
这时候,DO变成了下面这样,之前的Map属性直接变成一个json串。
public class Employees {
private Integer empNo;
private Date birthDate;
private String firstName;
private String lastName;
private String gender;
private Date hireDate;
private String extInfo;
}
相较于主表,拓展表的形式,此种方案在表结构上,就一个表;应用层面,一次查询即可,也无需开发属性填充的功能。如果调用方可以直接解析json,这种模式在面对变动的时候,一行代码都不用改。
总结:面对需求变动,如果一条线改到底,开发工作量大,测试风险也会变大。主表拓展表的方案,在数据库和应用层,复杂度提高了,我个人认为最优解是json结构的字段。当然具体问题还需要具体分析,每一种方案都有其缺点,以json方案为例,如果需要对json中的字段作为查询条件,那么其性能还需要进行特殊处理,后续有时间会再研究下mysql的json字段。