什么是最有效的查询?

时间:2010-05-14 23:48:35

标签: sql function performance subquery aggregate

我有一个名为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秒范围内有1,000行。但我真的很关心可扩展性,而且我不想在桌面增长时重新设计所有内容。什么是最好的方法?

4 个答案:

答案 0 :(得分:2)

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

您打算将所有这些发送回用户界面吗?谷歌每页发布25个结果;也许你也应该。

您打算在中间层进行计算吗?也许你可以在数据库上进行那些计算,并保存自己将所有这些字节带到网上。

我的观点是,如果您仔细考虑使用它们,您可能永远不需要使用1,000或100万行。

您可以使用EXPLAIN PLAN查看两个查询之间的区别。

答案 1 :(得分:1)

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

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

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

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

答案 2 :(得分:1)

第三个选项是派生表:

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 :(得分:1)

一些想法:

  • 派生表的想法在其他平台上会很好,但是MySQL与派生表有相同的问题:它们没有被编入索引。这意味着MySQL将在应用WHERE子句之前执行派生表的完整内容,而WHERE子句根本不会扩展。

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

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

  • 最后,如果您不想搞乱触发器,并且如果您不太关心新鲜度,那么您可以始终拥有单独的stats表并将其更新为脱机,具有每个运行的cron作业几分钟完成您在上面的查询#2中指定的工作,除了在真实表上。根据您的申请的细微差别,更新统计数据的这种轻微延迟可能会或可能不会被您的用户接受。