根据 tag 找用户,怎么设计数据库会比较好呢 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
fffonion
V2EX    数据库

根据 tag 找用户,怎么设计数据库会比较好呢

  •  
  •   fffonion 2015-01-16 20:35:35 +08:00 2401 次点击
    这是一个创建于 3929 天前的主题,其中的信息可能已经有所发展或是发生改变。

    每个用户有数量不定的tag,比如长得帅,没朋友等;tag的数量可能随时会增加
    需求是想找出所有有没朋友的tag的用户,或者可能想找所有同时有长得帅和没朋友tag的用户,应该怎么设计数据库呢?

    目前想到的两种:
    第一种是按tag存,每个tag下存有这个tag的用户的id的列表,有用户添加标签之后就去追加这个列表(这样是不是比较适合用monodb?)
    第二种是存一个表,字段是用户id和用户tag,每个用户的每个tag就存一条记录,然后给tag字段加索引,然后select fileid from table where tag = 想查询的tag;

    大家觉得哪种更有优势,或者有更好的设计方法呢?

    34 条回复    2015-01-23 14:14:02 +08:00
    yeyuliu
        1
    yeyuliu  
       2015-01-16 21:05:30 +08:00
    第一种啊。redis 比较合适。如果不打算redis 落地的话。结合第二种做原始数据的存储。
    a2z
        2
    a2z  
       2015-01-16 21:06:33 +08:00
    这个不是mongodb专门干的活么
    fffonion
        3
    fffonion  
    OP
       2015-01-16 21:36:23 +08:00
    @yeyuliu get√

    @a2z mongo的话第一种有更好的解决方案嘛?对它不太熟
    a2z
        4
    a2z  
       2015-01-16 21:40:45 +08:00
    @fffonion

    每个用户加一个field 叫tags,比如 "tags":["长得帅","没朋友"]
    fffonion
        5
    fffonion  
    OP
       2015-01-16 21:46:43 +08:00
    @a2z 就是说除了tag对应哪些用户再加个反向的数据是吧
    Archangel_SDY
        6
    Archangel_SDY  
       2015-01-16 21:50:53 +08:00
    用第二种设计数据库,用第一种做缓存扔 Redis.
    fffonion
        7
    fffonion  
    OP
       2015-01-16 22:12:13 +08:00
    @Archangel_SDY good看来还是redis大法好
    kmvan
        8
    kmvan  
       2015-01-16 22:31:06 +08:00
    memcache 能否做到?
    如果能,是key = uid,还是key = tagId 呢?
    willwen
        9
    willwen  
       2015-01-16 22:31:58 +08:00 via iPhone
    不如用ardb存,另外用什存也根本不重要。

    用RDBMS的,就是表(users, tags)。如果是pg,就直接在users加字段指向tags。如果是mysql就第三表,存tag->user的。

    以後要分析是查都方便。
    caixiexin
        10
    caixiexin  
       2015-01-16 23:00:07 +08:00   1
    @willwen +1 目前做了一个跟lz很像的需求,mysql下就是按照三个表的方式实现的。tag,user,tage_user
    zado
        11
    zado  
       2015-01-16 23:16:27 +08:00
    我想到一种方法,用nosql,把所有所有名字+tga以及所有tga+名字两两组合成key,以后就按前缀来搜索。例如:
    "小明"[长的帅]
    "小明”[没朋友]
    "小明”[没有钱]
    [没朋友]"小明"
    [没有钱]"小明"
    [长得帅]"小明"
    搜"小明",能得到他的所有tga,搜[没朋友],能得到所有没朋友的人。
    a2z
        12
    a2z  
       2015-01-16 23:37:08 +08:00
    @zado
    你这样用户和tag多了后表行数直接指数增长了……
    letv
        13
    letv  
       2015-01-16 23:42:01 +08:00
    @a2z 那应该怎么设计呢?
    zado
        14
    zado  
       2015-01-16 23:47:43 +08:00
    @a2z nosql没有表行数限制,再大的表也能装下,同时查找也是非常迅速的。
    td width="10" valign="top">
    willwen
        15
    willwen  
       2015-01-16 23:47:59 +08:00 via iPhone
    @letv postgresql的做法是最雅的,也最近表形式。直接存tag_id到uset,保持增。
    zado
        16
    zado  
       2015-01-16 23:59:22 +08:00
    @a2z 假设有100万用户,每个用户有100个tga,那么也才2亿个key(100万 * 100 + 100*100万),况且不是每个用户都有100个tga的。
    fffonion
        17
    fffonion  
    OP
       2015-01-17 01:43:18 +08:00
    @willwen postgre没接触过,在user里存tagid可以通过tag找到用户吗?实际上是在底层实现了类似上面的第二种方法吧(猜的

    @zado 这样也不错就是看上去有点不舒服lol
    typcn
        18
    typcn  
       2015-01-17 02:10:01 +08:00
    @kmvan memcache 比 redis 功能少的多,而且还比 redis 慢 10% - 20%
    aliao0019
        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 ,写入时候更新两份,怎么样?
    puncsky
        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)。

    所以,
    假设时间比空间更宝贵,“读”、“增"要比"改"、"删"多,用第一种。
    如果”改“、”删“很频繁,可以考虑用第二种。
    puncsky
        21
    puncsky  
       2015-01-17 03:38:05 +08:00
    更正:这里我们应该着重于对“读”优化。 关键的地方出现了口误。。。 )逃
    plantain
        22
    plantain  
       2015-01-17 07:35:15 +08:00
    用户和Tag多对多啊
    zjmdp
        23
    zjmdp  
       2015-01-17 08:10:02 +08:00   1
    基于lucene的solr,也就是搜索引擎那套原理,对tag->用户建立反向索引
    semicircle21
        24
    semicircle21  
       2015-01-17 11:52:02 +08:00
    @zjmdp solr 支持 tag 的组合吗?
    (我猜是支持的, 就是想确认一下)
    semicircle21
        25
    semicircle21  
       2015-01-17 12:27:19 +08:00
    @zjmdp 我看了 solr 的 start guide, 确实是支持 组合 的, 但 reference 好长.. 感谢先.
    zjmdp
        26
    zjmdp  
       2015-01-17 12:58:10 +08:00
    @semicircle21 我最近刚在做这一块,对按tag筛选用户包(导出千万用户,用户规模在1亿左右),你这块对并发有要求么?
    felixzhu
        27
    felixzhu  
       2015-01-17 12:58:54 +08:00
    就用mongo,在用户表里面存一个tags就可以的,对tags做索引
    semicircle21
        28
    semicircle21  
       2015-01-17 14:28:48 +08:00
    @zjmdp 这个技术选型也和规模并发有关?
    目前我没这个需求, 以前遇到这个问题时, 我发现关系型数据库处理不好, 也不是关键场景, 然后就自己用 go 配合 redis 写了一套基于反查原理的, 规模不大, 支持了组合检索, 和一些 tag 间的逻辑, 当时找了很久的轮子, 没有发现 solr 这个.
    你目前使用 solr 有什么体会?
    zjmdp
        29
    zjmdp  
       2015-01-17 17:36:59 +08:00   1
    @semicircle21 数据迁移,schema调整都比较麻烦,系统比较复杂(各种参数调整),估计你要用的话需要折腾一阵子,本身通过tomcat之类的容器提供http服务,并发量应该也就这么回事
    gongweixin
        30
    gongweixin  
       2015-01-17 19:09:35 +08:00
    第一种是按tag存,每个tag下存有这个tag的用户的id的列表,有用户添加标签之后就去追加这个列表(这样是不是比较适合用mongodb?) 这个是什么意思?
    是两列,一列tag, 一列所有用户的id的字符串,类似用(,)号分割构成一个串
    还是 1 + n列,一列tag, n个用户n列?

    我们现在是用的第二种,然后缓存到了redis中。量不大的话有索引直接查库性能也不会太差。
    fffonion
        31
    fffonion  
    OP
       2015-01-18 00:24:36 +08:00
    @gongweixin 是两列,但是用nosql去存;第二种的话感觉不太……优雅 ?www

    @puncsky @plantain 同意,确实是要userIdsByTags和tagsByUserIds这样子,因为目前需求只是按tag找用户,所以就把tagsByUserIds省略了


    @zjmdp 查了一下solr,那个是像sphinx一类的东西吧,好像比较复杂,先mark着看有没轻量的解决方案
    memeda
        32
    memeda  
       2015-01-19 02:29:07 +08:00
    redis sadd
    gongweixin
        33
    gongweixin  
       2015-01-23 14:11:59 +08:00
    第一种方法基本可不取,一个tag有10W用户的话基本没法存取更新,但反过来可以,因为一个用户只会有几个tag, 再加上方法2,需要考虑性能的话,可以把查出来的数据缓存起来。这样正反查询都可以了。如果tag可修改的话就保存tag的id,因为你还要查询同时拥有多个tag的用户,方法1也实现不了这个需求。
    gongweixin
        34
    gongweixin  
       2015-01-23 14:14:02 +08:00
    如果用solr会更简单一点,只要存用户和用户拥有的tags就行了。
    关于     帮助文档     自助推广系统     博客     API   &bsp; FAQ     Solana     2304 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 40ms UTC 15:52 PVG 23:52 LAX 08:52 JFK 11:52
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86