优化查询 - 使用字段或使用其他表

时间:2011-02-28 10:01:39

标签: sql hibernate database-design jpa

我有一个需要很长时间的查询,我想优化它。我正在寻找最有效的方法。

我正在使用Postgresql DB处理Hibernate / JPA,但任何解决方案都应该是一个通用的JPA解决方案。

术语

  • 用户:系统中的用户。
  • 朋友:用户的朋友。用户将有N个朋友。
  • 会话:使用系统的会话。可以打开或关闭。
  • 上下文:会话的上下文。用户可以在任何给定时间内每个上下文具有一个打开会话,并且每个上下文可以具有许多过去关闭的会话。

查询

我需要实现一个查询,给定一个用户名,给我以下内容:

  • 获取该用户的所有朋友
  • 对于每个朋友:
    • 如果朋友有任何打开的会话,请获取所有打开的会话(针对所有上下文)
    • 否则,请从所有情境中获取朋友的最新会话。

请注意,友情存储在不同的数据库中,因此无论如何我都无法将其合并到一个大查询中。

示例

用户A有三个朋友:B,C,D。有两种情况,1和2.朋友们有以下数据:

(下面的格式是会话ID - 用户,上下文)

  • 1 - B,1:开放会话
  • 2 - B,2:2月27日开始的闭门会议
  • 3 - B,2:2月26日开始的闭门会议
  • 4 - C,1:闭门会议于2月27日开始
  • 5 - C,1:2月26日开始的闭门会议
  • 6 - C,2:2月26日开始的闭门会议
  • 7 - C,2:2月25日开始的闭门会议
  • 8 - D,1:开放会话
  • 9 - D,2:开放会话

查询应该让我: B:第1节(所有公开会议) C:第4场会议(最新闭幕会议) D:会议8,9(所有公开会议)

当前状态

我的查询分三步进行:

  1. 获取用户的所有朋友
  2. 对于每个朋友:
    1. 获取朋友的所有公开会话
    2. 如果有任何打开的会话,则返回所有打开的会话
    3. 获取该朋友的最新会话,返回该会话
  3. 显然这是很多疑问。 对于初学者,我将采取上面的第2步并将其转换为单个查询。我的担忧与第二个查询有关。问题是 - 如何使其更加优化。因此可以改写这个问题:

    “给定一组N个朋友ID,获取所有这些朋友的所有开放会话或最新会话。”

    建议的解决方案

    基本上我们提出了两种解决方案,我们正在考虑哪种方法会更好。

    表解决方案说要保留一个新表,该表将关联用户,上下文和最新会话。这个解决方案的含义是:

    • 创建一个新实体& “最新会议”表格
    • 该表将包含以下列:
      • 用户
      • 上下文
      • 最新会话ID
    • 该表将由post persist上的会话实体更新,以便任何新持久的会话将自动更新此表。
    • 新查询将从此表中获取用户的所有朋友的所有记录,并处理它们以创建最终结果。

    列解决方案表示在会话表上保留“最新”标志列。这个解决方案的含义是:

    • 为最新(布尔值)
    • 创建一个新字段
    • 该列将由会话实体的post persist设置,以便前“最新”会话将不再是最新的,新会话将成为最新会话。
    • 新查询将从原始会话表中获取用户的所有朋友的所有最新记录(通过将新列合并到语句的条件中)并处理它们以创建最终结果。

    这些都有利弊,我们似乎还没有赢家。显然,我们还没有考虑过其他更好的解决方案。我想看到的是上面哪一个更好,为什么,或者你自己的更好的方法。

3 个答案:

答案 0 :(得分:1)

两种解决方案之间的差异应该是微不足道的。根据活动,表解决方案可能更清洁。

然而,请注意'你做错了'(根据理论)。

RDBMS应用程序设计原则明确指出,您不应该尝试指定查询的执行方式,而应该指定您想要的数据。数据库将找到解决方案的最佳路径(RDBMS最接近数据,并且根据您的体系结构可能会节省网络往返,存储往返等等;可伸缩性可能会在这里严重削弱,如果可能,您可能不会意识到这一点你不进行适当的压力测试;此外,RDBMS知道索引和内部统计数据,它们确定扫描或搜索是否更有效,并且知道如何最佳地执行连接。)

在实践中,尝试提出为什么不同的数据库为友谊提出问题? (它在同一个数据库中是不同的db或不同的模式?)。

此外,如果你真的想采用这种方式(禁止RDBMS寻找最佳执行计划),那么最重要的因素是:

  • 索引(将影响数量级的性能)
  • 使用模式(索引将提高SELECT的性能,但过多的索引会降低更新速度)
  • 应用程序/客户端层缓存(可能会影响数量级的性能和可伸缩性)

编辑: 因此,考虑“给定一组N个朋友ID,获取所有这些朋友的所有开放会话或最新会话”。这是一个在引入新结构之前应该进行测试的查询

会话(会话ID,用户,上下文,开始,结束)

SELECT *
FROM Sessions s
WHERE s.End IS NULL 
      AND s.User IN (:friendsList)
UNION ALL
SELECT *
FROM Sessions s
WHERE s.User NOT IN (SELECT User 
                     FROM Sessions s2
                     WHERE s2.User IN (:friendsList)
                           AND s2.End IS NULL)
      AND s.User IN (:friendsList)          
      AND s.End IN (SELECT MAX(End) 
                    FROM Sessions s2 
                    WHERE s2.User = s.User)

有更多方法可以编写上述内容以尝试帮助优化器,特别是如果您的数据库支持CTE,则可以更有效地重写上述内容。

注意: :friendsList - 朋友用户列表 另外,我假设开放会话的NULL为开放会话的End值。您可能已经选择了其他方法(可能您有一个表示它的字段;或者有两个表,一个用于打开会话,一个用于关闭)

以上查询将受益于某些索引(原则是首先尝试使用索引进行优化,然后进行重组;我会尝试的第一个索引是User, End上的复合索引)和相对较少数量的朋友(假设从它作为一个字符串传递的事实来看,这应该已经很好地执行了。

答案 1 :(得分:0)

为什么不缓存对象?您无需点击数据库。

答案 2 :(得分:0)

您的主要瓶颈似乎是您需要的信息分布在两个数据库中。因此,您获取了一个朋友列表并通过它们进行了迭代。

我建议您尝试删除itteration,将其减少为单个查询。

我实现此目的的方法是构建一个以逗号分隔的用户ID字符串,并将该字符串传递给第二个数据库。然后,第二个数据库中的sql(例如,使用函数)将字符串intol转换为单个字段的id,并加入其中。

对我来说这感觉非常不合理,但这是我一直在做的事情。

我使用的唯一实用替代方法是构建一个将ID插入表中的单个查询,然后加入该表。临时表或具有SessionID字段的永久表,允许多个会话同时使用它。

无论您使用何种方法,对步骤2进行单一查询,使用基于集合的方法而非迭代,都应产生显着的效益。