【问题标题】:How to design the user table for an online dating site?如何设计在线约会网站的用户表?
【发布时间】:2010-12-16 10:00:09
【问题描述】:

我正在开发基于 PHP 和 MySQL 的本地在线约会网站的下一个版本,我想把事情做好。用户表非常庞大,预计新版本会增加更多,因为在推广上会花费大量资金。

我猜是 7-8 岁的当前版本可能是由对 PHP 和 MySQL 不太了解的人完成的,所以我必须从头开始。

该社区目前拥有 20 万以上的用户,预计在未来一两年内将增长到 50 万至 100 万。每个用户的个人资料有 100 多个属性,我必须能够搜索至少 30-40 个属性。

您可以想象,我对制作一个包含 20 万行和 100 列的表格有点谨慎。我的前任将用户表分成两份……一份包含最常用和搜索最多的列,另一份包含其余(和大量)列。但这会导致两个表之间存在很大的同步问题。

那么,您认为最好的方法是什么?

【问题讨论】:

    标签: php mysql database-design


    【解决方案1】:

    这不是一个答案本身,但由于这里很少有答案建议属性值模型,我只是想跳进去谈谈我的生活经历。

    我曾经尝试过使用这个模型和一个具有 120 多个属性(每年增长 5-10 个)的表,并添加大约 100k+ 行(每 6 个月),索引变得如此之大以至于它需要永远添加或更新单个user_id

    我发现这种设计的问题(并不是说它完全不适合任何情况)是您需要在第二张表的user_id,attrib 上放置一个主键。在不知道属性的潜在长度的情况下,您通常会使用更大的长度值,从而增加索引。就我而言,属性可能有 3 到 130 个字符。此外,value 肯定会遭受同样的假设。

    正如 OP 所说,这会导致同步问题。想象一下,如果每个属性(或者说至少 50%)都需要存在。

    另外,正如 OP 所建议的,搜索需要在 30-40 个属性上完成,我无法想象 30-40 个连接将如何有效,甚至由于长度限制而使用 group_concat()

    我唯一可行的解​​决方案是返回一个包含与属性一样多的列的表。我的索引现在大大缩小了,搜索也更容易了。

    编辑:此外,没有标准化问题。要么拥有属性值的查找表,要么拥有它们ENUM()

    编辑 2: 当然,可以说我应该有一个查找表来查找属性可能值(减少索引大小),但我应该在那个表上进行连接。

    【讨论】:

    • @stereofrog:我不会说“一点实际经验”,只是说它可能不适用于这种情况。
    • @stereofrog:政治上正确的说......也许人们没有仔细阅读OP的必要条件,否则,我同意“一点实践经验”。
    【解决方案2】:

    您可以做的是将用户数据拆分到两个表中。

    1) 表:用户

    这将包含有关用户的“核心”固定信息,例如名字、姓氏、电子邮件、用户名、角色 ID、注册日期和类似的东西。

    个人资料相关信息可以放在自己的表格中。这将是一个具有 key => val 性质的无限可扩展表。

    2) 表格:user_profile

    字段:user_id、选项、值

    user_id: 1

    选项:profile_image

    值:/uploads/12/myimage.png

    user_id: 1

    选项:questions_answered

    值:24

    希望这会有所帮助, 保罗。

    【讨论】:

    • 这可能是你最好的选择,因为它遵循标准化范式,这可能会产生一些开销,但从长远来看,它比单一的 - 数百 - 更容易管理和更快列数表。
    • 这确实是 IMO 的必经之路,我也会这样做。
    • 这比 OP 更好,但我 attribute->value 与规范化数据库设计相比,有其自身的问题(查询复杂性、域验证、完整性约束和性能的可表达性)。
    【解决方案3】:

    实体-属性-值模型可能很适合您:

    http://en.wikipedia.org/wiki/Entity-attribute-value_model

    不要有 100 列且不断增长的列,而是添加一个包含三列的表:

    user_idpropertyvalue

    【讨论】:

    • 您的回答毫无意义。这是关系型的,而且当涉及到大型生产站点时,在 RDBMS 上实现无模式设计通常比 NoSQL 软件更好。只有少数大型网站有充分的理由使用它们。
    【解决方案4】:

    一般来说,您不应该为了性能而牺牲数据库完整性。

    我要做的第一件事是创建一个包含 100 万行虚拟数据的表,并使用像 ab 这样的压力工具测试一些典型的查询。它很可能会证明它执行得很好 - 100 万行对 mysql 来说是小菜一碟。因此,在尝试解决问题之前,请确保您确实拥有它。

    如果您发现性能很差并且数据库确实成为瓶颈,请考虑进行一般优化,例如缓存(在所有级别,从 mysql 查询缓存到 html 缓存),获得更好的硬件等。这应该可以解决大多数情况。

    【讨论】:

    • 这是一个很好的观点。我想我会采用现有数据和性能测试所有建议的解决方案。乍一看,EAV 似乎有点问题,因为我无法想象具有 40 个条件的搜索会是什么样子。答案可能在 symcbean 的答案之内,但我真的不擅长 SQL 来理解它,而无需进行一些研究。感谢大家的贡献。
    【解决方案5】:

    一般来说,在您担心性能之前,您应该始终让架构正确无误!

    这样您就可以就调整架构以解决特定的性能问题做出明智的决定,而不是猜测。

    您绝对应该走 2 桌路线。这将显着减少存储量、代码复杂性以及更改系统以添加新属性的工作量。

    假设每个属性都可以用Ordinal number 表示,并且您只是在寻找对称匹配(即您试图根据相似属性来匹配人,而不是意图表达)... .

    简单来说,查找合适匹配项的查询可能非常昂贵。实际上,您正在寻找 N 维空间中相同邻近的节点,不幸的是大多数关系数据库并没有真正为这种操作设置(我相信 PostgreSQL 支持这一点)。所以大多数人可能会从以下内容开始:

    SELECT candidate.id, 
     COUNT(*)
    FROM users candidate,
      attributes candidate_attrs,
      attributes current_user_attrs
    WHERE current_user_attrs.user_id=$current_user 
      AND candidate.user_id<>$current_user
      AND candidate.id=candidate_attrs.user_id
      AND candidate_attrs.attr_type=current_user.attr_type
      AND candidate_attrs.attr_value=current_user.attr_value
    GROUP BY candidate.id
    ORDER BY COUNT(*) DESC;
    

    但是,这会强制系统比较每个可用的候选人以找到最佳匹配。应用一些启发式方法,您可以获得非常有效的查询:

    SELECT candidate.id, 
     COUNT(*)
    FROM users candidate,
       attributes candidate_attrs,
       attributes current_user_attrs
    WHERE current_user_attrs.user_id=$current_user 
      AND candidate.user_id<>$current_user
      AND candidate.id=candidate_attrs.user_id
      AND candidate_attrs.attr_type=current_user.attr_type
      AND candidate_attrs.attr_value 
         BETWEEN current_user.attr_value+$tolerance
         AND current_user.attr_value-$tolerance
    GROUP BY candidate.id
    ORDER BY COUNT(*) DESC;
    

    ($tolerance 的值会影响返回的行数和查询性能——如果你有 attr_type 的索引,attr_value)。

    这可以进一步细化为积分系统:

    SELECT candidate.id, 
      SUM(1/1+
          ((candidate_attrs.attr_value - current_user.attr_value)
            *(candidate_attrs.attr_value - current_user.attr_value))
      ) as match_score
    FROM users candidate,
      attributes candidate_attrs,
      attributes current_user_attrs
    WHERE current_user_attrs.user_id=$current_user 
      AND candidate.user_id<>$current_user
      AND candidate.id=candidate_attrs.user_id
      AND candidate_attrs.attr_type=current_user.attr_type
      AND candidate_attrs.attr_value 
       BETWEEN current_user.attr_value+$tolerance
       AND current_user.attr_value-$tolerance
    GROUP BY candidate.id
    ORDER BY COUNT(*) DESC;
    

    这种方法可以让您做很多不同的事情 - 包括按属性子集进行搜索,例如

    SELECT candidate.id, 
      SUM(1/1+
          ((candidate_attrs.attr_value - current_user.attr_value)
            *(candidate_attrs.attr_value - current_user.attr_value))
      ) as match_score
    FROM users candidate,
      attributes candidate_attrs,
      attributes current_user_attrs,
      attribute_subsets s
    WHERE current_user_attrs.user_id=$current_user 
      AND candidate.user_id<>$current_user
      AND candidate.id=candidate_attrs.user_id
      AND candidate_attrs.attr_type=current_user.attr_type
      AND candidate_attrs.attr_value
      AND s.subset_name=$required_subset
      AND s.attr_type=current_user.attr_type 
       BETWEEN current_user.attr_value+$tolerance
       AND current_user.attr_value-$tolerance
    GROUP BY candidate.id
    ORDER BY COUNT(*) DESC;
    

    显然,这不包含非序数数据(例如出生标志、最喜欢的流行乐队)。如果不了解更多关于现有数据的结构,很难确切地说这将是多么有效。

    如果您想添加更多属性,则无需对 PHP 代码或数据库架构进行任何更改 - 它可以完全由数据驱动。

    另一种方法是识别刻板印象 - 即 N 维空间内的参考点,然后计算出特定用户最接近其中的哪一个。您将所有属性分解为一个复合标识符 - 然后您只需要应用相同的方法在也与原型匹配的候选人子集中找到最佳匹配。

    【讨论】:

      【解决方案6】:

      没有看到架构就无法真正提出任何建议。通常 - Mysql 数据库必须至少标准化为 3NF 或 BNCF。听起来它现在没有标准化,一张表中有 100 列。

      另外 - 您可以使用事务和 INNODB 引擎轻松地通过外键强制实施参照完整性。

      【讨论】:

        猜你喜欢
        • 2013-04-29
        • 2011-02-20
        • 1970-01-01
        • 2011-09-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多