关于冗余设计的一点思考

模型设计,消除冗余

首先简单介绍一下数据库结构设计的范式:

  • 第一范式(1NF)
    强调的是列的原子性,即列不能够再分成其他几列。
    用于消除一列中存放多值可能产生的冗余情况。
    可以把常见的EXCEL表格中有合并单元格的那种直接排除了。
    举个例子,一个学生有班级和年级两个属性,那么就要设计两个列来放。
    (学生编号,年级,班级)
  • 第二范式(2NF)
    在第一范式基础上,增加两个条件:一是表必须有主键,二是没有包含在主键中的列必须完全依赖于主键,而不能只依赖于主键的一部分。
    第一条好理解,就是必须有一个唯一键用来做主键,可以是多个列的组合键。
    第二条举个例子,一个学生表(班级号,学号,学生姓名,班级名)的主键是(班级号+学号),如果该表有个列是班级名,那该表就不符合第二范式,因为班级别名仅依赖了主键中的班级号,而非完全依赖主键。此处班级别名即是冗余。应当拆分为班级表(班级号,班级名)、学生表(班级号、学号,学生姓名)。
  • 第三范式(3NF)
    在第二范式基础上,再增加条件:不能存在传递依赖。
    即不能存在:非主键列 A 依赖于非主键列 B,非主键列 B 依赖于主键的情况。
    举个例子,还是上面的学生表,对所有学生进行唯一编号后:学生表(学生编号,学生名字,班级号,班级名),这里学生编号是主键,其他列不可拆分且全部完全依赖学生编号,所以符合第二范式。但学生所在班级名字依赖于学生所在班级号,属于传递依赖,故不符合第三范式。应当拆分为学生表(学生编号,学生名字,班级号)和班级表(班级号,班级名)。
  • 其他范式
    即使符合第三范式依然还是可以产生冗余。如BCNF范式针对的是组合主键中的依赖关系,还有第四、五范式…不做延伸。

可以见得数据库范式的制定一直依赖追求的是在关系型结构下对空间的最优利用。

反模式,制造冗余

而空间和性能往往是相对,在实际的开发中,我们却经常特意制造一些冗余,以取得更好的性能。
接下来探讨下面的案例:一个聊天工具中的用户表,和用户的好友表。
用户表(用户ID,名称,头像,邮箱,手机号......)
好友表(用户ID,好友的用户ID,好友的名称,好友的头像)
这里的好友表是不符合第二范式的,因为我冗余了好友的名称、头像。这样当我在查询本人通讯录中的好友的时候就不需要连接用户表,甚至于我会将整个好友组缓存在一个内存的集合数据结构中。
而冗余带来的问题是数据同步问题。即“当好友修改了他的头像之后需要将好友表中的好友头像字段进行更新。”这里的同步仅仅是主从数据同步,冗余数据相当于缓存,因为无法依赖数据库完成数据同步,就用程序设计来处理,这里通常的解决方法是使用面向对象中的订阅模式。当好友对象产生的时候订阅用户的属性变更,于是当变更发生的时候可以响应处理数据同步。大多数消息队列都对该模式提供了很好的服务。这里的“数据同步”中的同步并不是“异步编程”中的同步的意思,冗余数据的同步实质上是异步进行的,需要允许一定程度上的实时性、可靠性缺失。
行到这里,我们的聊天工具已经可以很好地响应通讯录上小伙伴们的头像、名称修改了。梳理一下做法,首先是使用面向对象方法,将表转化为实体类,数据行转化为实例对象,列作为对象的属性,属性的修改即“写”权仅掌握在该实例对象手中,而其被其他实体的引用作为冗余副本通过订阅来更新。

回归现实,接受冗余

继续上面的好友表例子。
是的,我拥有了一个能及时响应好友变化的通讯录。而且我还有一个很喜欢换头像以及名字的好友,所以我经常会认不出我的好友究竟谁是谁。
于是我的产品经理提议增加一个备注的功能,多么妙的设定!于是我头脑中的好友画像通过备注,这个美妙的功能,跟通讯录中常常变换的头像建立了联系。
然而,剧情推进,在第二个大版本更新的时候我们的聊天工具加入了群聊,于是陌生人出现了。那些群友,并不在我的通讯录中,他们的头像也是常常变化的,OMG。那些陌生的群友就好像柯南中的黑影,我根本放弃了去取分谁是谁是谁在发言。
于是我天才的产品经理说:需要给群成员增加一个备注功能!
“異議あり!”
我马上反对了这个提议,一般的群成员名主键应该是(群ID+用户ID),而我对群成员备注的话主键则是(我的用户ID+群ID+群成员的用户ID),可是我的需求是对陌生人增加备注,主键是(我的用户ID+陌生人的用户ID)。一般做法中的群成员名只有在群规严格的群中才有用,而后两者基本可以说离谱了,无论哪一种其实都不好满足我的需求。
那么,我们现实中是怎么面对陌生人的呢?还是得对陌生人在脑中建立画像吧,这画像就像是对陌生人的部分信息的浅拷贝,就是冗余。而冗余的更新不是被动的订阅响应,是由我主动观察所得。此时,冗余副本的更新即“写”的主动权掌握在了“我”这个实例手中。
回到对陌生群友的处理。先增加一份陌生人数据实体类型(或者说表),与好友表冗余的用户基本信息相同,不同的是并不订阅其用户基本信息的变更而且不展示在通讯录上。于是,作为用户的我,就能看到一个头像、名字并不乱变的陌生人。那么什么时候应该同步呢,就是在“我”下次决定好好观察下这个陌生人的时候,也就是当我下次点开陌生人的名片的时候。由于是用户的主动行为,陌生人在用户脑中的画像能很好地更新与陌生人新头像之间的关联。
我的产品经理似乎快被我说服了,但他还是有点不服气。
好吧,系统设计的系统分析中有一个环节是对现有系统的分析,包括当前同类产品尤其龙头的分析。我们一起来看看张小龙是怎么做的吧。

你可能感兴趣的:(关于冗余设计的一点思考)