说到表设计,我们需要注意表是二维的,存在行和列。但二维也只是暂时的理解,之后会看到数据是无限维的,本文只考虑二维的理解,不再展开说明。
设计表时符合某一要求,称作满足某范式。满足范式越多,表的设计越好,这些范式都是从实际经验总结出来的。下面我列举了几个范式,从最基本的到高级的依次排开。
第一范式
确保每列数据都是原子的,不可再分割的。比如姓名、年龄、籍贯,要分别储存成三列,而不是混在一列中。
问题在于如何决定一份数据是否原子,这里要从实际需求出发。如果系统只需要取用户姓名,那姓名是原子的,单独储存一列。如果系统中有地方需要单独使用姓,或单独使用名,显然姓名不是原子的了。
这里也会有超前设计、反模式、冗余之类的问题。
超前设计,预见未来的需求,提前拆分字段。这种行为有缺点,你看当前需求是整体存取,而表设计成了更细的字段,那就需要在写代码时做这层转换、兼容才行,多了代码量。
反模式,需求说明了要分别使用姓和名,在表设计时依然只有一个姓名字段。这明显违反范式,但不代表这是错的设计。这样好处是简化表模型,维护数据库的时候更简单了。缺点也是需要写代码的时候做层转换,按照某种规则拆分姓名字段。
另一种更激进的反模式,在上一段中这个例子,其实姓和名并不好拆,因为姓氏千奇百怪的字数还不一样对吧。那么怎么办,我依旧不想存为两个字段,我可以在姓和名中间加分隔符辅助代码处理,比如加逗号。不过更好的做法是用json格式。
冗余,上一段我们成功把姓和名拆分储存了,但是每次我想整体存取姓名的时候,代码中还得做转换,好麻烦。怎么办,再加一个字段,用来储存整体的那个姓名,冗余一下就好了。但是这里需要注意数据一致性,需要更复杂的同步措施了,这样看来冗余没有太大好处,如果是为了读取性能,那冗余还是必要的。
第二范式
在满足第一范式的情况下,每列都和主键相关,由于表只能有一个主键,也就是说一个表只能存一类数据。也就因为这个范式,催生了主键的概念。
实际上这时候升维了,之前只是一堆字段,量再多也只是一维的设计,如果每个字段都关联各自的主键,主键的多与少很明显是另一维度的事情,所以才说表是二维的。
用户表只能存用户,不能存订单。与第一范式一样,我们需要决定什么样的数据是同一类数据,还是同样的思考方法,看系统需要。用户表真的不能存订单吗?如果存此用户所有订单的数量呢?系统需要到什么程度就设计到什么程度,刚刚好就行。
第二范式没什么特别注意的问题,第三范式涉及到外键了,它问题比较多,下文续上。
第三范式
在满足第二范式的情况下,每列都直接依赖主键,而非间接依赖。如果所有字段都直接依赖各自的主键,那问题的焦点就变成了主键与主键之间的关系,主键与外键的关系。
主键与主键之间的关系,其实也是表与表之间的关系,毕竟第二范式要求一个表只能有一个主键。表之间的关系有三种,一对一、一对多、多对多。下面介绍三种关系的实现方法。
一对一,比如用户表和地址表。在地址表里储存用户表的主键值,这就叫外键,反过来储存也是可以的,在用户表里储存地址表里的主键值。一对一的两张表直接合并成一张表也行,看具体需求。
一对多,如果一个用户有多个地址,在地址表里存用户表的主键值。像这样把外键储存在多的那一方,就可以实现一对多。反过来是不行的,因为用户表里没有一个列可以存多个地址的主键值啊,否则违反了第一范式要求的原子性。
多对多,要想实现这个必须违反第二范式,需要一个中间表,表有两列,分别储存两个表的外键,但这个中间表中的字段没有依赖同一个主键,违反了第二范式。但一般也不认为中间表是个实际的东西,从“中间表”的名字也能看出来。
一对多的这个可以反模式,我就反着存,在用户表里加个列,用来存此用户所有的地址,用json数据格式储存。这样也行,有的时候实在不想加多余的表。
第四范式
以上说的都是屁话,第四范式就是反模式,只要能实现,管他什么范式。
衍生出来的设计
字典表
习惯上,把诸如星座之类的,储存成一张表,称作字典表。这个自己体会。
模板表
新建一行数据时,如果有数据来源,把数据源复制过来改改就是新的一行了,数据源就是模板表。
历史表
模板表是把数据复制一份,之后随便修改不会影响到原数据。如果当初没有使用模板表的设计方式,而是通过外键引用了模板表,当然这时候它不能叫做模板表了。
被引用的这一行修改了会影响到所有引用它的地方,怎么办?只要模板表修改的时候复制一行,老的数据不受影响,保留修改历史,我称作历史表。
代码编写的时候需要总是获取最新的一行,防止拿到老的数据。
网友评论