【问题标题】:Optimizing a query - using a field or using another table优化查询 - 使用字段或使用另一个表
【发布时间】:2011-02-28 10:01:38
【问题描述】:

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

我正在使用 Postgresql DB 开发 Hibernate/JPA,但任何解决方案都应该是通用 JPA 解决方案。

术语

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

查询

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

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

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

示例

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

(下面的格式是 Session ID - User,Context)

  • 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: Session 4 (最近的闭幕会议) D: Sessions 8,9(所有开放的会话)

当前状态

我的查询分三个步骤进行:

  1. 获取用户的所有好友
  2. 对于每个朋友:
    1. 获取朋友的所有开放会话
    2. 如果有任何打开的会话,返回所有打开的会话
    3. 获取朋友的最新会话,返回该会话

显然这是很多查询。 首先,我将执行上面的第 2 步并将其转换为单个查询。我的担忧与第二个查询有关。问题是 - 如何使其更加优化。因此问题可以重新表述:

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

建议的解决方案

基本上我们想出了两个解决方案,我们正在考虑什么会更好。

表格解决方案表示要保留一个新表格,该表格将在用户、上下文和最新会话之间建立关联。该解决方案的含义是:

  • 为“最新会话”创建一个新实体和表
  • 该表将包含以下列:
    • 用户
    • 上下文
    • 最新会话 ID
  • 该表将在 post persist 时由会话实体更新,因此任何新持久化的会话都将自动更新该表。
  • 新查询将从该表中获取用户所有朋友的所有记录,并对其进行处理以创建最终结果。

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

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

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

【问题讨论】:

  • 如何使用带有函数的视图?如果可能的话,缓存也可能有很大帮助。
  • 据我了解,视图只会以一种或另一种方式运行复杂的查询,我试图通过在数据库更新期间进行更多管理来避免这种情况,从而改善查询的性能。
  • 友谊数据库为什么不同?真的是不同的数据库还是不同的架构?

标签: sql hibernate database-design jpa


【解决方案1】:

您的两种解决方案之间的差异应该很小。根据活动,表解决方案可能更清洁。

但是,请注意“你做错了”(根据理论)。

RDBMS 应用程序设计原则明确指出,您不应尝试指定查询的执行方式,而应指定您想要的数据。数据库将为您的解决方案找到最佳路径(RDBMS 位于最靠近数据的位置,并且根据您的架构可能会节省网络往返、存储往返等;可扩展性在这里可能会严重受损,您可能不会意识到如果您没有进行像样的压力测试;此外,RDBMS 知道索引和内部统计信息,这些统计信息确定扫描或查找是否会更有效,并且它知道如何以最佳方式执行连接)。

在实践中,试着提出一个问题,为什么不同的友谊数据库? (它真的是不同的数据库还是同一个数据库上的不同架构?)。

此外,如果您真的想按照自己的方式去做(禁用 RDBMS 以寻找最佳执行计划),那么最重要的因素是:

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

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

会话(SessionID、用户、上下文、开始、结束)

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 - 好友用户列表。
另外,我假设打开会话的 End 的值为 NULL 以用于打开会话。您可能已经在选择其他方法(也许您有一个表示它的字段;或者有两个表,一个用于打开会话,一个用于关闭会话)

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

【讨论】:

  • 谢谢。如您所见,我并没有尝试优化给定的查询 - 我知道查询很复杂,因此我尝试向数据库添加更多信息以简化它。如果您愿意,我将向数据库添加数据以简化最终的查询逻辑。根据理论,这是错误的吗?
  • @Eldad Mor,是的,根据良好的设计原则,这是错误的。原因 a) 您已经将本来应该是一个查询的内容拆分为三个 b) 现在以改善您开始构建缓存结构的三个查询的性能不佳。现在这确实在实践中有时是显着提高性能的唯一方法;但是,由于您没有从单个查询开始,因此这是一种过早优化的情况,您可能正在为不存在的问题(实际上存在,但自制)开发解决方案。
  • 好吧,我同意你的观点,虽然它是 2 个查询而不是 3 个。我基本上认为在给定现有数据库模式的情况下创建第二个查询在 SQL 方面既复杂又在性能方面冗长。我同意避免过早优化,但这将是迄今为止最复杂的查询 - 我相信通过向数据库添加一些数据,我会大大简化它。
  • @Eldad Mor,我已经用您的第二个查询在没有其他结构的情况下看起来的方式更新了答案。事实上,SQL 比额外的表或字段更复杂。然而,它不是很复杂。您还应该比较整体复杂性 - 您的解决方案将需要额外的代码(并且它将在其他地方 - 在应用程序级别/除非您使用触发器;此代码可能需要维护或可能被 DBA 绕过等)。或者,为了简化查询,您可以创建一个 VIEW。
【解决方案2】:

为什么不缓存对象?你不需要打数据库。

【讨论】:

  • 我正在使用缓存,但是这个查询并不常见。用户会偶尔使用一次,但不够频繁,无法让缓存真正提高性能。
  • 虽然这个查询不是很频繁,因为你说对象已经在缓存中,那么为什么不使用它呢?如果以下对象在缓存中 - User,Friends(User), Session 那么它的简单对象查找。但是,使用您概述的数据库选项
  • 1.创建一个新表会增加会话保存的延迟,并且您还需要在会话到期时消除它。它有点开销。是的,选择会更快。它还引入了一些数据冗余。如果 Session 表有大量记录,那么这种方法可能会更好。 2. 没有太多额外的开销,因为它是更新中的另一列。但是,如果 Sessions 表有大量数据,那么查询会变慢。
【解决方案3】:

您的主要瓶颈似乎是您需要的信息分布在两个数据库中。因此,您获取朋友列表并遍历他们。

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

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

我觉得这很不雅,但这是我一直在做的事情。

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

无论您使用什么方法,对第 2 步进行一次查询,使用基于集合的方法而不是迭代,应该会产生显着的好处。

【讨论】:

  • 也许我不清楚 :-) 我不想遍历朋友并查询每个人。我确实打算在朋友的完整列表上运行一个查询。我无法合并这两个数据库,这是给定的,但我可以将整个过程转换为两个查询 - 一个用于获取朋友,另一个用于获取会话。这是我关心的第二个查询。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-11-01
  • 1970-01-01
  • 2016-10-24
  • 2013-02-05
  • 2016-01-26
  • 1970-01-01
相关资源
最近更新 更多