你好,游客 登录 注册 搜索
背景:
阅读新闻

关系数据库冗余设计的场景

[日期:2016-04-14] 来源:Coming X  作者: [字体: ]

  设计关系数据库的时候,表与表之间存在各种关联关系,例如设计一个论坛的数据库,用户与评论之间存在一对多的关系,那么需要评论表中设置用户表的id作为外键。为了使得数据库的读写操作尽可能优化,有时候不单单存储一个外键,需要冗余存储其他信息来提升数据查询的速度。下面说几个用到冗余存储的场景:

  1. 存储查询复杂的静态数据

对于评论回复另一条评论,通常在论坛中会引用被回复的评论内容、用户名、时间等信息,如下图汽车之家论坛。在设计评论表时,通常会设置reply_comment_id指向被回复评论对象,这里为了方便理解,我们把被回复的评论称为父评论。

数据库

  如果父评论还有父评论,那么就形成了一个回复链。例如网易新闻的盖楼模式:

  这两种模式在数据库存取上有什么差异呢?

  像汽车之家论坛,只显示父评论的信息,只需要reply_comment_id记录父评论的id即可。数据库获取的时候,需要遍历每一条评论去获取其父评论的信息。那么复杂度是O(n),n是每次获取评论的数量。如果reply_comment_id为0的话则不需要执行查询。

  但是像网易新闻,如果也只记录reply_comment_id指向其父评论的话,那么对于每一条评论需要递归其父评论到祖宗评论,直到reply_comment_id为0,即祖先。假设平均每条评论有5个父评论,那么复杂度是O(5 n ),n是每次获取评论的数量。这样的数据获取性能是比较差的,那如何设计来提升查询评论时的性能呢?

  答案:进行冗余存储。

  在评论表中使用reply_id_chain表示所有祖宗评论id列表,用逗号分隔,按照id大小顺序排列(及回复先后顺序),例如2,4,5,7,89。这样在获取一条评论的回复链的时候,只需要执行一次查询即可得到所有祖宗评论列表,并且排序按照楼层拍好。

  进一步思考,我们是不是可以把祖宗评论的其他信息也写入到评论表,例如父评论用户id、父评论发布时间、父评论内容?

  答案:看情况而定。

  如果要求查询时的性能优先,那么可以牺牲数据库的空间换取时间。

  有一点是需要注意的,冗余存储的必须是静态数据,即,一旦发生永不改变。例如发布一条回复,这个事件一旦发生不可能改变的,用户不可能再去修改已经发布的评论内容、评论时间或者彻底删除这条评论。但是用户的昵称和头像是可以改变的,如果把用户昵称也冗余存储了,就会出现父评论使用了已经废弃的昵称和头像,这在业务上有时候是不允许的。

  2. 存储计算复杂的动态数据

  论坛中用户会有等级,例如贴吧的LV10。等级一般是根据用户行为得分计算而来的,例如发文得10分、评论得1分,被赞得1分等等,数据库会记录用户发文、评论和被赞的详细流水。

  业务场景是,在评论列表中用户名后面显示用户的级别。

  获取评论列表是一个非常频繁的操作,且数据表关联较多。此时如果对每一个用户获取其发文数、评论数、被赞数,在进行一次公式计算,显然是很耗时耗资源的。为了提升读的速度,就要在考虑在写的时候优化,我们可以将计算过程放到发布文章、写入评论时。

  首先,在用户表增加一个字段score,表示用户行为得分,用于记录所有与等级相关的得分汇总。那么在每次获取用户等级时,只需要拿到这个得分,做一个简单的映射即可,1000分一下LV1、1000-2000LV2等等。而写入本身是一个相对数据操作压力较小的工作,还可以借助消息机制实现异步写入,这里不表了。

  3. 存储难以标准化的数据

  站内系统消息会提示用户“你的XXX文章被XX赞了”,“XX回复了你的文章XXX”,“XX关注了你”等等。这类数据一般是一部分可以标准化,一部分没法标准化。标准化的字段有:from_user_id、to_user_id、create_time、read_or_not, 不能标准化的字段有:行为类型可能是回复也可能是点赞、回复对象的类型可能是文章也可能是评论,另外还引用的一些附加信息,例如文章id、文章标题等等。

 

  除了标准化之外的字段,如何存储那些非标准化的数据呢?这里引入一个content字段,把非标准化的数据拼凑为可用的数据。为了让拼凑的content数据更加灵活,我们采用了json格式表示这些数据。

{
    "record_id": 13969,
    "from_user": {
        "user_id": 1991,
        "avatar": "http://wx.qlogo.cn/mmopen/lgHKmU7J0NFqcdnibBznGEWp9ibypKTJhny6kDmEoHIahia8kHyyAvkd1UJUBlCWib1OvddVjoG3ibWn1SjnpqyVga16kaDWrX3tq/0",
        "username": "Zero三藏",
        "is_certificated": "0",
        "color": "#818181"
    },
    "to_user": {
        "user_id": 3477,
        "avatar": "http://wx.qlogo.cn/mmopen/W4DF8ZKeejSnZFbrutxEVnu0UFj6c003vEh6iaPTJPDUEkvjaoFLBT6jzgHIxa1vTiaEZocV6CFvjw2U19TNx9xrD3uULRpXQ2/0",
        "username": "Caesar",
        "is_certificated": "1",
        "color": "#818181"
    },
    "type": "article_like",
    "content": {
        "reply_message": "",
        "to_message": "",
        "article_title": "世间好少年|你是我的浪漫主义企图心",
        "article_url": "http://app.athit.co/articles/article/19739",
        "article_id": 19739,
        "article_publish_time": "2016-04-13 09:32:02"
    }
}

这样的冗余存储使得查询用户消息的时候无须关联其他用户资料、文章、评论表,虽然有些数据是动态的,例如用户昵称、文章标题,但是用户可以理解这种消息只是一个快照,这一快照之后,用户昵称发生过改变。

本文主要讨论了业务中关系数据库冗余存储的一些适用场景,总结下来就是一句话,如果数据是静态的,利用冗余存储去提升查询性能是一件不错的事情。当下NoSQL的发展,使得冗余存储的设计更加重要,设计好冗余储存对于业务的性能将会有很大的提升。

——————————————–

本站除标注[FW]和资讯文章外都为原创文章,转载请注:

转载来源:Coming X

原文链接: http://blog.comingx.com/?p=3193





收藏 推荐 打印 | 录入:elainebo | 阅读:
本文评论   查看全部评论 (0)
表情: 表情 姓名: 字数
点评:
       
评论声明
  • 尊重网上道德,遵守中华人民共和国的各项有关法律法规
  • 承担一切因您的行为而直接或间接导致的民事或刑事法律责任
  • 本站管理人员有权保留或删除其管辖留言中的任意内容
  • 本站有权在网站内转载或引用您的评论
  • 参与本评论即表明您已经阅读并接受上述条款