V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
fffonion
V2EX  ›  数据库

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

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

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

    目前想到的两种:
    第一种是按tag存,每个tag下存有这个tag的用户的id的列表,有用户添加标签之后就去追加这个列表(这样是不是比较适合用mongodb?)
    第二种是存一个表,字段是用户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没有表行数限制,再大的表也能装下,同时查找也是非常迅速的。
    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   ·   FAQ   ·   实用小工具   ·   994 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 23:28 · PVG 07:28 · LAX 15:28 · JFK 18:28
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.