非关系型数据库结构设计的核心思路
很多人在用非关系型数据库时,第一反应还是套用MySQL那一套思维:建表、设字段、加外键。但NoSQL不是这么玩的。它更像是一种“按需存放”的方式,重点在于数据怎么用,而不是怎么规整。
先想清楚你的查询场景
比如你做个朋友圈功能,最常做的操作是什么?是看某个人发了哪些动态,还是查某条动态下面的所有评论?如果是前者,那完全可以把一个人的所有动态直接存成一个大文档,每次读取一次搞定,不用关联其他表。
在MongoDB里,可能长这样:
{
"user_id": "1001",
"username": "xiaoming",
"posts": [
{
"post_id": "p001",
"content": "今天天气真好",
"comments": [
{ "user": "xiaohong", "text": "是啊,适合出门" },
{ "user": "xiaoliang", "text": "我在家打游戏" }
],
"likes": 5
}
]
}
你看,一条数据就把用户和他的动态、评论全包了。虽然看起来有点重复,但在高并发下反而更快,因为不需要join多张表。
别怕嵌套,也别滥用嵌套
有些人一激动,恨不得把整个系统的数据都塞进一个文档。这不行。比如订单系统,如果把每个用户的全部历史订单、商品信息、店铺详情全都堆在一起,更新起来会很慢,还容易冲突。
合理做法是拆开。订单文档保留关键字段,其他信息通过ID去查。比如:
{
"order_id": "o123456",
"user_id": "u789",
"items": [
{ "product_id": "prod01", "name": "充电宝", "price": 99, "quantity": 1 }
],
"total": 99,
"status": "shipped",
"created_at": "2024-04-05T10:00:00Z"
}
商品详情可以单独存,订单里只留快照。这样即使商品下架了,订单记录还是完整的。
用ID代替关联,但要控制粒度
在关系型数据库里,订单和用户靠外键连。在NoSQL里,通常就直接存user_id。查的时候先拿订单,再根据user_id去用户集合查名字、电话这些。
这种“手动join”听起来麻烦,但在实际应用中,很多情况下前端只需要展示用户名,完全可以在下单时顺手把当时的用户名记下来,避免后续查询。
考虑数据增长和更新频率
有些字段变来变去,比如用户昵称。如果你把它复制到几十个地方,改一次就得全改一遍,肯定崩溃。这种动态信息最好只存一份,需要用的时候再取。
相反,像下单时的商品价格,属于历史快照,就应该固化下来,不然以后查记录发现价格对不上就尴尬了。
选择合适的存储模型
NoSQL有好几种类型,别一股脑都当文档数据库用。比如你要做实时排行榜,用Redis的有序集合(ZSET)比MongoDB快得多。
再比如日志系统,数据写得多、读得少,而且基本不更新,用时间序列数据库如InfluxDB更合适。硬塞进Mongo也不行,性能跟不上。
索引不是万能的,但也少不了
给字段加索引能提速,但每多一个索引,写入就越慢。尤其是复合索引,得想清楚查询条件的顺序。比如你总按状态+时间查任务,那就建一个{status: 1, created_at: -1}的索引。
但如果又经常按时间单独查,这个复合索引就不一定顶用,可能还得单独给created_at加一个。
实际开发中的小技巧
上线前模拟真实流量跑一轮,看看哪些查询慢。有时候你以为会频繁查的字段,实际上根本没人碰;而某个冷门接口却成了瓶颈。
另外,别一开始就追求完美结构。先按主要场景设计,跑起来后再根据实际使用调整。NoSQL的优势之一就是灵活,改结构不像MySQL那样动不动锁表。
比如一开始你把评论放在动态里,后来发现评论太多导致读取变慢,就可以拆出去,用post_id做分区键,查询时按post_id批量拉。