【问题标题】:How to hande joins in Mongodb?如何在 Mongodb 中处理连接?
【发布时间】:2012-07-09 07:54:58
【问题描述】:

我在 PostgreSQL 中有两个表: urls(带有索引页面的表,主机是索引列,3000 万行) 主机(包含主机信息的表,主机是索引列,100 万行)

我的应用程序中最常见的 SELECT 之一是:

SELECT urls.* 
FROM urls 
JOIN hosts ON urls.host = hosts.host 
WHERE urls.projects_id = ? 
  AND hosts.is_spam IS NULL 
ORDER by urls.id DESC, LIMIT ?

在 urls 表中有超过 100 000 行的项目中,查询执行速度非常慢。

由于表已经增长,查询的执行速度越来越慢。我已经阅读了很多关于 NoSQL 数据库(如 MongoDB)的内容,这些数据库旨在处理如此大的表,并且我正在考虑将我的数据移动到 MongoDB。如果我在从 urls 表中选择数据时不必检查主机表,那么一切都会很容易。我听说 MongoDB 不支持连接,所以我的问题是如何解决上述问题?我可以将有关主机的信息放在 urls 集合中,但字段 hosts.is_spam 可以由用户更新,我必须更新整个 urls 集合。我不知道这是正确的解决方案。

如果有任何建议,我将不胜感激。

【问题讨论】:

  • 100k 行几乎没有,30M 是正常的数据量,应该不是任何数据库的问题。您能否向我们展示 EXPLAIN ANALYZE 的结果以查看查询计划和时间安排?

标签: mongodb postgresql join database


【解决方案1】:

如果您不使用连接,那么关系数据库也可以很快工作。我认为,在这种情况下,您需要为了性能而进行非规范化。

选项 1

is_spam 列复制到 urls 表中。当主机的这个值发生变化时,更新所有相关的 url。如果你不经常这样做也没关系。

选项 2

我不了解您的应用,但我认为垃圾邮件主机的数量相对较少。在这种情况下,您可以将他们的 id 放入内存存储(memcached、redis、...),查询所有 url 并过滤掉应用程序中的垃圾邮件 url。这样一来,您的分页就会有点损坏,但有时这是一个可行的选择。

【讨论】:

  • 我个人喜欢选项 1。当 hosts.is_spam 更新时,您必须在触发 url 更新方面做更多的事情,但它肯定会使读取变得方便快捷。
【解决方案2】:

您是正确的,问题在于连接,但我的猜测是它只是错误的连接类型。正如 Frank H. 所提到的,PostgreSQL 应该能够根据hosts.is_spam 的频率相当轻松地处理这种类型的查询。您可能希望将urls 表聚集在id 上,以优化按限制排序阶段。由于您只关心urls.*,因此您可以通过在hosts.host 上创建部分索引来最小化磁盘io,其中is_spam is not null 可以轻松获得要避免的主机的简短列表。

试试这个:

select urls.* 
from urls 
left join hosts 
   on urls.host = hosts.host 
   and hosts.is_spam is not null
where urls.projects_id = ? 
and hosts.host is null

或者这个:

select * 
from urls
where urls.projects_id = ? 
and not exists (
   select 1
   from hosts
   where hosts.host = urls.hosts
   and hosts.is_spam is not null
)

这将允许 PostgreSQL 使用反连接来仅提取未映射到已知垃圾邮件主机的 url。如果 URL 的主机为空或无效,则结果可能与您的查询不同。

【讨论】:

    【解决方案3】:

    MongoDB 确实不支持连接。在这种情况下,我会像这样构建我的 urls 集合

    urls : {
        name,
        some_other_property,
        host
    }
    

    然后,您可以获取特定 URL 的主机,并检查 hosts 集合中的 is_spam 字段。请注意,这需要由查询数据库的客户端完成,而不能像使用 JOIN 那样在数据库本身完成。

    【讨论】:

    • 我仍然会将它放在 urls 集合中,但只需将 hosts -> host 更改为单个 id
    【解决方案4】:

    与@xbones 的回答类似,但有具体示例

    在您的urls 文档中添加host_id 字段是一种方法。这将要求您首先提取 url 文档的结果,然后提取垃圾邮件主机的结果,然后在您的客户端代码中进行本地过滤

    大致:

    var urls = db.urls.find({projects_id:'ID'}, {_id: 1, host_id: 1});
    var hosts = db.hosts.find({is_spam: 1}, {_id: 1});
    
    # psuedocode
    ids_array = _id for _id in urls if host_id is not in hosts
    
    urls = db.urls.find({_id: {$in: ids_array}});
    

    或者:

    var urls = db.urls.find({projects_id:'ID'});
    var hosts = db.hosts.find({is_spam: 1}, {_id: 1});
    
    # psuedocode
    urls = url for url in urls if host_id is not in hosts
    

    第一个示例假设 project_id 查询的结果可能很大(并且您的 url 文档更大)并且您只想获取尽可能少的数据,然后您在本地过滤,然后批量获取完整最终 url 文件。

    第二个示例只是获取完整的 url 文档以启动,并在本地过滤它们。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-12-29
      • 2020-07-10
      • 2020-07-26
      • 2012-12-30
      • 1970-01-01
      • 2016-09-09
      相关资源
      最近更新 更多