MongoDB 文档结构设计

2022-10-18 22:07:28 +08:00
 naijoag

mongodb 文档结构设计

需求: mongodb 记录用户搜索日志并提供用户和管理后台查询

query+qty 唯一

用户端需要展示一个这样的表格:

query (搜索型号) qty (搜索数量) weekly_search_times (最近 7 天搜索次数) last_search_time (最近搜索时间)
型号 1 1 10 2022-10-13 00:00:00
型号 1 2 4 2022-10-18 00:00:00
型号 2 3 4 2022-10-18 01:00:00

开始是设计这样的结构,之前没怎么用过 mongo ,就用了这种扁平化的结构。

{
    "_id": xxx,
    "query": "xxx",
    "user": 1,
    "qty": 100
}

这种当然是最简单方便的,但是领导说不行,这样还不如用 mysql ,这样的化存储的数据量大,后期查询会慢。说要设计一种结构,最好是一个用户一个文档,这个文档除了放用户的搜索历史,后面还可能放一些其他日志类的数据。 但是一个 mongo 文档又有 16M 的大小限制,说是设置一个容量,如果最多存 1000 个搜索日志,超过了清除。于是就有了下面这种结构:

{
    "_id": xxx,
    "user": 1000,
    "search_history": [{"query": "xxx", "time": 1660000, "qty": 1}]
}

这样子呢,相对上一种方法大大减少了文档数,一个用户一个文档,查询效率貌似变高了,因为只要查到一个文档就行,但是查询时无法直接对 search_history 进行过滤, 要把整个文档查询出来然后在内存中实现过滤(时间筛选)、分组( group by query+qty )、分页。 而且查询某个搜索词被哪些用户搜索过不方便实现。 所以这种方案不行。

搜索接口的访问量挺大的,所以是这个日志是写多读少的情况。

所以应该设计怎样的一个结构较为合理,各位大佬赐教。

2032 次点击
所在节点    程序员
15 条回复
naijoag
2022-10-18 22:20:48 +08:00
{
"_id": xxx,
"query": "xxx",
"user": 1,
"qty": 100
}
少了一个 time 字段(时间戳)这样的结构也是需要在代码中操作的 开始不知道前端需要展示那样的一个表格(数据要聚合)
naijoag
2022-10-18 22:23:44 +08:00
用户端:需要查询时间范围内的日志 (时间范围是日期)
AS4694lAS4808
2022-10-18 22:56:30 +08:00
如果是哪些词被用户搜索过,应该一个词一个文档?这个文档包含搜索过的用户的 id ?去重或者不去重?
mysql 用关系表+索引搜索也很快。
另外第一种方式查询起来性能还可以,除非真有上千万的用户每天查 N 次。。
naijoag
2022-10-18 23:02:42 +08:00
@AS4694lAS4808 意思是再弄一个集合:一个词一个文档 用来满足后台的需求吗
第一种我是觉得还可以 倒没有那么多用户 但是领导说就是第一种方式的话就没必要用 mongo 了 因为 mysql 也可以实现
wxf666
2022-10-19 00:16:52 +08:00
数据库新手求问,为嘛不能用 MySQL 呢?如果用 MySQL ,这种表结构行不行:

搜索日志表(
  用户 ID INT ,时间 TIMESTAMP ,型号 TINYTEXT ,数量 INT ,
   PRIMARY KEY (用户 ID ,时间,型号,数量),
   INDEX (型号,数量)


理由:

1. 每个用户搜索的内容,会尽量聚集在几页(有点类似『一个用户一个文档』),并按时间排序(尽量顺序插入),方便用户端按时间搜索(分组只能临时计算。但问题不大,反正一个用户一段时间内应该也没多少数据)

2. 后台查询某关键词被谁搜索过,走索引也很快
lithiumii
2022-10-19 00:23:06 +08:00
查询时可以过滤的,你搜搜 mongo 的 query 怎么写
caotian
2022-10-19 00:26:48 +08:00
$elemMatch
naijoag
2022-10-19 09:45:29 +08:00
@lithiumii @caotian 我看了$elemMatch 好像只是说匹配 search_history 里面满足条件的 doc 我拿到了 doc 还得在代码里对 search_history 进行过滤等操作
lithiumii
2022-10-19 12:09:11 +08:00
@naijoag 先 match 再 unwind 再 match 呢?
w07128597
2022-10-19 13:08:38 +08:00
第二种方法几年前用过,存的用户的消息记录,可以不用全查出来在内存中筛选分页的,mongodb 自带的函数可以实现
naijoag
2022-10-19 14:09:25 +08:00
@w07128597 什么函数
AS4694lAS4808
2022-10-19 14:16:39 +08:00
@naijoag 要只是为了搜索这个功能启用 mongo 我觉得没必要。。增加了项目复杂度

如果非要用 mongo ,就是一个词一个文档,每次更新用户的 id 和查询时间之类的,甚至对用户 id 去重和时间排序(用户少可以在内存里干,用户多就得用 mongo 函数)
w07128597
2022-10-19 18:44:12 +08:00
@naijoag 不是一个函数,也是类似于 9 楼的多个操作合集,具体我也忘了,好几年了,不过我当时全部是用 mongodb 本身的功能实现的,查询效率确实高
naijoag
2022-10-19 21:27:32 +08:00
目前利用 aggregate ,pipline 里面经历了 objectToArray 和 unwind 两次实现了如下转换。不知道你说的是不是也是这样。

```javascript
{
"_id": 10000,
"search_history": {
"query1": [{"qty": 1, "time": 1666185797}, {"qty": 2, "time": 1666185798}]
}
}
```

===>


```javascript
[
{"_id": 10000, "qty": 1, "time": 1666185797},
{"_id": 10000, "qty": 2, "time": 1666185798}
]
```
naijoag
2022-10-19 21:27:41 +08:00

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/887922

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX