每个用户有数量不定的tag,比如长得帅,没朋友等;tag的数量可能随时会增加
需求是想找出所有有没朋友的tag的用户,或者可能想找所有同时有长得帅和没朋友tag的用户,应该怎么设计数据库呢?
目前想到的两种:
第一种是按tag存,每个tag下存有这个tag的用户的id的列表,有用户添加标签之后就去追加这个列表(这样是不是比较适合用mongodb?)
第二种是存一个表,字段是用户id和用户tag,每个用户的每个tag就存一条记录,然后给tag字段加索引,然后select fileid from table where tag = 想查询的tag;
大家觉得哪种更有优势,或者有更好的设计方法呢?
1
yeyuliu 2015-01-16 21:05:30 +08:00
第一种啊。redis 比较合适。如果不打算redis 落地的话。结合第二种做原始数据的存储。
|
2
a2z 2015-01-16 21:06:33 +08:00
这个不是mongodb专门干的活么
|
6
Archangel_SDY 2015-01-16 21:50:53 +08:00
用第二种设计数据库,用第一种做缓存扔 Redis.
|
7
fffonion OP @Archangel_SDY good看来还是redis大法好
|
8
kmvan 2015-01-16 22:31:06 +08:00
memcache 能否做到?
如果能,是key = uid,还是key = tagId 呢? |
9
willwen 2015-01-16 22:31:58 +08:00 via iPhone
還不如用ardb來存,另外用什麼存也根本不重要。
用RDBMS的話,就是雙表(users, tags)。如果是pg,就直接在users裡加個數組字段指向tags。如果是mysql就連第三個表,存tag->user的關係。 這種以後要分析還是查詢都方便。 |
10
caixiexin 2015-01-16 23:00:07 +08:00 1
@willwen +1 目前做了一个跟lz很像的需求,mysql下就是按照三个表的方式实现的。tag,user,tage_user
|
11
zado 2015-01-16 23:16:27 +08:00
我想到一种方法,用nosql,把所有所有名字+tga以及所有tga+名字两两组合成key,以后就按前缀来搜索。例如:
"小明"[长的帅] "小明”[没朋友] "小明”[没有钱] [没朋友]"小明" [没有钱]"小明" [长得帅]"小明" 搜"小明",能得到他的所有tga,搜[没朋友],能得到所有没朋友的人。 |
15
willwen 2015-01-16 23:47:59 +08:00 via iPhone
@letv postgresql的做法是最優雅的,也最貼近實際表現形式。直接存tag_id到uset裡,保持係數級增長。
|
16
zado 2015-01-16 23:59:22 +08:00
@a2z 假设有100万用户,每个用户有100个tga,那么也才2亿个key(100万 * 100 + 100*100万),况且不是每个用户都有100个tga的。
|
17
fffonion OP |
19
aliao0019 2015-01-17 02:59:53 +08:00 via iPhone
用 pg 的话不应该用户和 tag 是多对多么,关系放到第三个双主键的表里面 user_id tag_id 两个字段;如果只是在用户下存了 tag_ids 数组那通过 tag 查用户的时候还要把 tag_ids 拿出来手动筛;mongo 的话既在 user 下存 tag_ids 又在 tag 下存 user_ids ,写入时候更新两份,怎么样?
|
20
puncsky 2015-01-17 03:36:41 +08:00 2
如果规模很大,先明确不要用join操作。一般情况下“写”比“读”慢40倍左右,而“读”在一般的互联网产品中,比如twitter,读写比是100:1到1000:1。这里的情况假设也是读远高于写(用户经常看到tag但是很少改tag)。这里我们应该着重于对“写”优化。
你的第一种做法,userIdsByTags,是对“求某一tag对应所有用户"很好的读优化,搜索是O(1)。增是O(1),删改是O(k) k是这个tag里面的用户数。相应的tradeoff是userId有很多冗余存在这些lists里面,不方便“求某一用户所有的tag”,搜索是O(m*k),m是tag的个数,k是tag的平均长度。当然我们可以再冗余一点,加一个tagsByUserIds的表,这样就方便读了。不方便的是写的时候要维护两张表。 你的第二种做法,(_id, userId, tag)的schema,给tag加index,对于“求某一tag对应所有用户",额外的空间B tree换来的搜索时间复杂度O(logn),(如果错误请指正),冗余是两个fields要存很多重复的值。如果同时给userId加index,“求某一用户所有的tag”也是O(logn). 这时候写可能是O(logn + logk)或者O(logn + k)。 所以, 假设时间比空间更宝贵,“读”、“增"要比"改"、"删"多,用第一种。 如果”改“、”删“很频繁,可以考虑用第二种。 |
21
puncsky 2015-01-17 03:38:05 +08:00
更正:这里我们应该着重于对“读”优化。 关键的地方出现了口误。。。 )逃
|
22
plantain 2015-01-17 07:35:15 +08:00
用户和Tag多对多啊
|
23
zjmdp 2015-01-17 08:10:02 +08:00 1
基于lucene的solr,也就是搜索引擎那套原理,对tag->用户建立反向索引
|
24
semicircle21 2015-01-17 11:52:02 +08:00
@zjmdp solr 支持 tag 的组合吗?
(我猜是支持的, 就是想确认一下) |
25
semicircle21 2015-01-17 12:27:19 +08:00
@zjmdp 我看了 solr 的 start guide, 确实是支持 组合 的, 但 reference 好长.. 感谢先.
|
26
zjmdp 2015-01-17 12:58:10 +08:00
@semicircle21 我最近刚在做这一块,对按tag筛选用户包(导出千万用户,用户规模在1亿左右),你这块对并发有要求么?
|
27
felixzhu 2015-01-17 12:58:54 +08:00
就用mongo,在用户表里面存一个tags就可以的,对tags做索引
|
28
semicircle21 2015-01-17 14:28:48 +08:00
@zjmdp 这个技术选型也和规模并发有关?
目前我没这个需求, 以前遇到这个问题时, 我发现关系型数据库处理不好, 也不是关键场景, 然后就自己用 go 配合 redis 写了一套基于反查原理的, 规模不大, 支持了组合检索, 和一些 tag 间的逻辑, 当时找了很久的轮子, 没有发现 solr 这个. 你目前使用 solr 有什么体会? |
29
zjmdp 2015-01-17 17:36:59 +08:00 1
@semicircle21 数据迁移,schema调整都比较麻烦,系统比较复杂(各种参数调整),估计你要用的话需要折腾一阵子,本身通过tomcat之类的容器提供http服务,并发量应该也就这么回事
|
30
gongweixin 2015-01-17 19:09:35 +08:00
第一种是按tag存,每个tag下存有这个tag的用户的id的列表,有用户添加标签之后就去追加这个列表(这样是不是比较适合用mongodb?) 这个是什么意思?
是两列,一列tag, 一列所有用户的id的字符串,类似用(,)号分割构成一个串 还是 1 + n列,一列tag, n个用户n列? 我们现在是用的第二种,然后缓存到了redis中。量不大的话有索引直接查库性能也不会太差。 |
31
fffonion OP @gongweixin 是两列,但是用nosql去存;第二种的话感觉不太……优雅 ?www
@puncsky @plantain 同意,确实是要userIdsByTags和tagsByUserIds这样子,因为目前需求只是按tag找用户,所以就把tagsByUserIds省略了 @zjmdp 查了一下solr,那个是像sphinx一类的东西吧,好像比较复杂,先mark着看有没轻量的解决方案 |
32
memeda 2015-01-19 02:29:07 +08:00
redis sadd
|
33
gongweixin 2015-01-23 14:11:59 +08:00
第一种方法基本可不取,一个tag有10W用户的话基本没法存取更新,但反过来可以,因为一个用户只会有几个tag, 再加上方法2,需要考虑性能的话,可以把查出来的数据缓存起来。这样正反查询都可以了。如果tag可修改的话就保存tag的id,因为你还要查询同时拥有多个tag的用户,方法1也实现不了这个需求。
|
34
gongweixin 2015-01-23 14:14:02 +08:00
如果用solr会更简单一点,只要存用户和用户拥有的tags就行了。
|