数据库结构设计

2014-10-28

最近在项目中使用到aerospike和mongodb,不同的数据模型,一个是key-value的,一个是document的。做了些数据结构的设计,记录一些有意思的地方。

有一个需求是用户在一段时间内的访问次数,比如最近一个小时内访问了多少次,或者一天内多少次,一星期内多少次。利用记录的过期时间做这个很巧妙。用户首次访问后,生成三条记录,过期时间分别是小时,天,星期。key是用户uid,value是访问次数。后续访问中,原子增加value值,但是不要更新过期。等记录自然过期后就清零了开始下一轮统计。

分布式环境下的批量查询,由于批查的key是被分布到不同实例上面的,会跨机。有个性能要求很严格的场景,用key-hash结构避免跨机。比如(field1+field2,value)的key-value结构,批查询时field1+field2作为key会分布到不同机器上。而使用key-hash结构,field1作为key,value是一个hash。filed2作为hash结构的key,value为hash结构的value,field1固定,这种结构对field2进行批查时不会跨机,性能可能会好一些。

mongodb的结构好灵活,比如说一对多关系,可以这么存:

{
    bagid : xxx,
    items : [item1_id, item2_id, item3_id],
}
{   
    itemid : item1_id,
}

也可以这么存:

{
    bagid : xxx,    
}
{   
    itemid : item1_id,
    bagid : xxx
}

哪种更合适呢?假设要获取某个背包中的物品列表,用第一种方式存是需要两次查表的:

items = db.bag.find({"bagid":xxx}, {"items": 1})
db.item.find({"itemid": {$in: items}})

而第二种方式只需要一次查表:

db.item.find({"bagid":xxx})

而且维护起来也是第二种方式更方便,比如在某背包中加入一个item,第一次方式除了需要改item表加入一项,还需要同时在bag表中对应项加入一条,而第二种方式只需要改item表。

但是也需要注意到,第二种方式如果没对bagid项建索引,查询其实是需要扫描的,而第一种方式则可以利用到主键索引。

多对多关系不要用引用或嵌入方式搞,虽然使用时查表可能方便一点点,但关系维护起来会非常滴蛋疼。还是老老实实地把关系拆到单独的表中维护。

web后端开发这边,一些项目就是围绕数据库的CURD操作,没什么技术含量,尤其是没有性能约束的时候想怎么整就怎么整。相对重要一点的就是数据库结构的设计要做好。核心部分是维护好规则。这里所谓的规则就是关系约束。

我觉得应该分为三层吧,最下层就是数据库表的设计要合理,有些冗余查起来会方便很多,但维护起来却很不爽,取折中的。在这个基础上封装提供各个表的CURD操作。

中间一层是最重要的,也是最麻烦的,就是维护操作的规则,不要破坏关系。比如读取一个表项,用户是否有操作权限。因为有些约束并不是数据库能直接维护的,可能有些级联的操作。比如添加一个用户的操作,不单单是在用户表中添加一项,可能还要修改对应的权限表,用户所属的群组之类的。

最上层就是提供给前端的接口了,需要什么样就做成什么样。中间层已经把操作的约束维护过,这一层就是给前端无脑调用了,前端不需要考虑约束什么的。

database