【问题标题】:What's the most efficient query?什么是最有效的查询?
【发布时间】:2011-02-19 18:23:14
【问题描述】:

我有一个名为 Projects 的表,它具有以下关系:

有很多贡献 有很多付款

在我的结果集中,我需要以下聚合值:

  • 唯一贡献者的数量(贡献表上的 DonorID)
  • 供款总额(供款表上的金额总和)
  • 支付总额(支付表上 PaymentAmount 的总和)

因为有太多的聚合函数和多个连接,所以使用标准聚合函数和 GROUP BY 子句会变得很麻烦。我还需要能够对这些字段进行排序和过滤。所以我想出了两个选择:

使用子查询:

SELECT Project.ID AS PROJECT_ID,
(SELECT SUM(PaymentAmount) FROM Payment WHERE ProjectID = PROJECT_ID) AS TotalPaidBack,
(SELECT COUNT(DISTINCT DonorID) FROM Contribution WHERE RecipientID = PROJECT_ID) AS ContributorCount,
(SELECT SUM(Amount) FROM Contribution WHERE RecipientID = PROJECT_ID) AS TotalReceived
FROM Project;

使用临时表:

DROP TABLE IF EXISTS Project_Temp;
CREATE TEMPORARY TABLE Project_Temp (project_id INT NOT NULL, total_payments INT, total_donors INT, total_received INT, PRIMARY KEY(project_id)) ENGINE=MEMORY;
INSERT INTO Project_Temp (project_id,total_payments)
 SELECT `Project`.ID, IFNULL(SUM(PaymentAmount),0) FROM `Project` LEFT JOIN `Payment` ON ProjectID = `Project`.ID GROUP BY 1;
INSERT INTO Project_Temp (project_id,total_donors,total_received)
 SELECT `Project`.ID, IFNULL(COUNT(DISTINCT DonorID),0), IFNULL(SUM(Amount),0) FROM `Project` LEFT JOIN `Contribution` ON RecipientID = `Project`.ID  GROUP BY 1
 ON DUPLICATE KEY UPDATE total_donors = VALUES(total_donors), total_received = VALUES(total_received);

SELECT * FROM Project_Temp;

两者的测试相当可比,在 0.7 - 0.8 秒范围内,1000 行。但我真的很关心可扩展性,我不想随着表的增长而重新设计所有内容。最好的方法是什么?

【问题讨论】:

    标签: sql function performance subquery aggregate


    【解决方案1】:

    一些想法:

    • 派生表的想法在其他平台上会很好,但是 MySQL 对派生表的问题与对视图的处理相同:它们没有索引。这意味着 MySQL 将在应用 WHERE 子句之前执行派生表的全​​部内容,这根本不会扩展。

    • 选项 1 有利于紧凑,但当您想开始将派生表达式放在 WHERE 子句中时,语法可能会变得棘手。

    • 物化视图的建议很好,但遗憾的是 MySQL 不支持它们。我喜欢使用触发器的想法。您可以将该临时表转换为持久的真实表,然后在 Payments 和 Contribution 表上使用 INSERT/UPDATE/DELETE 触发器来更新 Project Stats 表。

    • 1234563几分钟就可以完成您在上面的查询 #2 中指定的工作,但在真实表上除外。根据您的应用程序的细微差别,您的用户可能会或可能不会接受更新统计信息的这种轻微延迟。

    【讨论】:

      【解决方案2】:

      还有第三个选项是派生表:

      Select Project.ID AS PROJECT_ID
          , Payments.Total AS TotalPaidBack
          , Coalesce(ContributionStats.DonarCount, 0) As ContributorCount
          , ContributionStats.Total As TotalReceived
      From Project
          Left Join   (
                      Select C1.RecipientId, Sum(C1.Amount) As Total, Count(Distinct C1.DonarId) ContributorCount
                      From Contribution As C1
                      Group By C1.RecipientId
                      ) As ContributionStats
              On ContributionStats.RecipientId = Project.Project_Id
          Left Join   (
                      Select P1.ProjectID, Sum(P1.PaymentAmount) As Total
                      From Payment As P1
                      Group By P1.RecipientId
                      ) As Payments
              On Payments.ProjectId = Project.Project_Id
      

      我不确定它是否会表现得更好,但你可以试试看。

      【讨论】:

        【解决方案3】:

        我会采用第一种方法。您正在允许 RDBMS 完成它的工作,而不是试图为它完成它的工作。

        通过创建临时表,您将始终为每个查询创建完整表。如果您只想要一个项目的数据,您最终仍然会创建完整的表(除非您相应地限制每个 INSERT 语句。)当然,您可以对其进行编码,但它已经成为相当数量的代码和复杂性,以获得小的性能提升。

        使用 SELECT,数据库可以获取适当数量的数据,根据上下文优化整个查询。如果其他用户查询了相同的数据,它甚至可能被缓存(查询,可能还有数据,取决于您的数据库)。如果性能确实是一个问题,您可以考虑使用索引/物化视图,或在 INSERT/UPDATE/DELETE 触发器上生成表。横向扩展,您可以使用服务器集群和分区视图——我相信如果您正在创建临时表,这将是困难的。

        编辑:上面写的没有任何特定的rdbms,尽管OP补充说mysql是目标数据库。

        【讨论】:

        • 我喜欢使用视图的建议。我以前从未创建过一个,但似乎这是一个完美的应用。有趣的是,临时表似乎是一个多余的练习,做 mysql 无论如何都会做的事情。
        • 好的,它是 mysql - 您可能想将它添加到您的问题以及您正在使用的版本中。
        • Mysql 不支持索引视图——你可以创建一个视图,但它更像是一种隐藏细节的简单方法,所以查询的文本存在于你的数据库中,而不是你的代码中。视图还提供了一种将复杂查询分解为更小部分的方法。它们可以隐藏很多细节,这通常是一个好处,但如果不仔细管理,也会导致简单的选择隐藏复杂的查询。
        【解决方案4】:

        知道每 1K 行的时间安排很好,但真正的问题是如何使用它们。

        您是否打算将所有这些发送回 UI?谷歌每页发布 25 个结果;也许你也应该这样做。

        您打算在中间层进行计算吗?也许您可以在数据库上进行这些计算,而无需将所有这些字节都通过网络传输。

        我的意思是,如果您仔细考虑如何处理它们,您可能永远不需要处理 1,000 或 100 万行。

        您可以通过 EXPLAIN PLAN 来查看这两个查询之间的区别。

        【讨论】:

        • 感谢您的回复。不幸的是,其中一个要求是将所有这些数据发送到包含所有项目世界的地图,因此分页不够好。但是,假设这不是必需的,则每页有 16 个项目。您是否认为这样做不值得,而是为每个结果运行 3 或 4 个简单查询?这是每页 64 个查询,但如果它们很简单,也许它是微不足道的?
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-02-08
        • 2022-01-22
        • 2021-12-08
        • 1970-01-01
        • 2012-11-02
        相关资源
        最近更新 更多