【问题标题】:Performance tuning of row-based subqueries: LEFT OUTER JOIN and OUTER APPLY, alternatives?基于行的子查询的性能调整:LEFT OUTER JOIN 和 OUTER APPLY,替代方案?
【发布时间】:2014-02-05 17:38:52
【问题描述】:

某个查询(在 Dynamics CRM 2011 数据库上)的性能非常糟糕。由于它是一个规范化的数据模型,但需要对此数据(SSRS 报告)进行扁平化视图,因此我使用 SELECT TOP (1) 子查询做了很多 (12) LEFT OUTER JOIN,例如:

LEFT JOIN Filterednew_rates FRates ON FRates.new_ratesid =
    (SELECT TOP (1)
        FRR.new_ratesid
     FROM Filterednew_rates FRR
     WHERE
        FRR.new_contractid = FContract.contractid
        AND FRR.statuscode <> 803270000 -- NOT Obsolete
     ORDER BY FRR.new_startdate DESC
    )

这适用于少量结果行(例如 3 行需要 10 秒),但我已经在大约 100 个预期结果行上运行了 45 分钟(源数据量相同,只是 WHERE 不同条款)。因此,我开始寻找“强制”SQL Server 每行运行子查询的方法(从逻辑上讲,这对我来说是线性扩展的)。

然后我阅读了The power of T-SQL's APPLY operator 并设法将上面的内容更改为

OUTER APPLY (
    SELECT TOP (1)
        FRR.*
     FROM Filterednew_rates FRR
     WHERE
        FRR.new_contractid = FContract.contractid
        AND FRR.statuscode <> 803270000 -- NOT Obsolete
     ORDER BY FRR.new_startdate DESC
) AS FRates

这使得执行时间与结果记录的数量大致呈线性关系(100 行约 3:30 分钟,3 行约 6 秒)。不知何故,这使得 SQL Server 决定更好地更改查询执行计划!

在 SQL 中是否有任何其他方法可以在不借助集成/分析服务的情况下“扁平化”规范化数据模型?

编辑:

感谢@Aaron 和@BAReese 的输入。我将尝试应用 PIVOT/UNPIVOT 和窗口函数并报告查询性能差异。

根据大众的要求,查询的很大一部分。我尝试将查询“匿名化”一下,因此实际的查询属性更具描述性。

OUTER APPLY (
    SELECT TOP (1)
        FCO.*
     FROM Filterednew_contractoption FCO
     WHERE
        FCO.new_contractid = FContract.contractid
        AND FCO.new_included = 1 -- Is Included
        AND FCO.new_optionidname = 'SomeOption1'
) AS FOptionSomeOption1
OUTER APPLY (
    SELECT TOP (1)
        FCO.*
     FROM Filterednew_contractoption FCO
     WHERE
        FCO.new_contractid = FContract.contractid
        AND FCO.new_included = 1 -- Is Included
        AND FCO.new_optionidname = 'SomeOption2'
) AS FOptionSomeOption2
OUTER APPLY (
    SELECT TOP (1)
        FCD.*
     FROM FilteredContractDetail FCD
     JOIN FilteredProduct FProd ON FCD.productid = FProd.productid
     WHERE
         FContract.contractid = FCD.contractid
         AND FCD.new_included = 1 -- Is Included
         AND FProd.productnumber IN ('COLDEL1', 'COLDEL2', 'COLDEL3', 'COLDEL4')
) AS FColDelContractDetail
LEFT JOIN FilteredProduct FColDelProduct ON FColDelContractDetail.productid = FColDelProduct.productid
OUTER APPLY (
    SELECT TOP (1)
        FCO.*
     FROM Filterednew_contractoption FCO
     JOIN Filterednew_contractdetail_new_contractoptions FCD_CO ON FCO.new_contractoptionid = FCD_CO.new_contractoptionid
     WHERE
        FCD_CO.contractdetailid = FColDelContractDetail.contractdetailid
        AND FCO.new_included = 1 -- Is Included
        AND FCO.new_optionidname LIKE 'Input1'
) AS FColDelInput1Option
OUTER APPLY (
    SELECT TOP (1)
        FCO.*
     FROM Filterednew_contractoption FCO
     JOIN Filterednew_contractdetail_new_contractoptions FCD_CO ON FCO.new_contractoptionid = FCD_CO.new_contractoptionid
     WHERE
        FCD_CO.contractdetailid = FColDelContractDetail.contractdetailid
        AND FCO.new_included = 1 -- Is Included
        AND FCO.new_optionidname LIKE 'Input2'
) AS FColDelInput2Option
OUTER APPLY (
    SELECT TOP (1)
        FCO.*
     FROM Filterednew_contractoption FCO
     JOIN Filterednew_contractdetail_new_contractoptions FCD_CO ON FCO.new_contractoptionid = FCD_CO.new_contractoptionid
     WHERE
        FCD_CO.contractdetailid = FColDelContractDetail.contractdetailid
        AND FCO.new_included = 1 -- Is Included
        AND FCO.new_optionidname LIKE 'Input3'
) AS FColDelInput3Option
OUTER APPLY (
    SELECT TOP (1)
        FCP.*
     FROM Filterednew_price FCP
     WHERE FCP.new_contractid = FContract.contractid
     AND FCP.statuscode <> 803270000 -- NOT Obsolete
     ORDER BY FCP.new_validfrom DESC
) AS FPrice
OUTER APPLY (
    SELECT TOP (1)
        FCFR.*
     FROM Filterednew_contractforecastresult FCFR
     WHERE FCFR.new_contractid = FContract.contractid
     ORDER BY FCFR.createdon DESC
) AS FForecastResult

【问题讨论】:

  • 您是否考虑过 PIVOT/UNPIVOT 而不是 12 个外连接?此外,当我们不知道原始数据是什么样子、输出应该是什么样子等时,我们很难考虑替代方案。您只向我们展示了一个外连接,其他 11 个过滤条件有何不同?
  • +1 从表面上看,这似乎是一种奇怪的数据处理方式

标签: sql sql-server tsql reporting-services sql-server-2008-r2


【解决方案1】:

由于您使用的是 SQL Server,这将是使用windowing functions 提高效率的绝佳机会。

这样的事情可能会帮助它运行得更快:

LEFT JOIN
    (
    SELECT FRR.new_contractid, ROW_NUMBER() over(partition by FRR.new_contractid
                                            order by FRR.new_startdate DESC) as Last_ID
    FROM Filterednew_rates as FRR
    WHERE FRR.statuscode <> 803270000 -- NOT Obsolete
    ) AS FRates
    ON FRates.new_contractid = FContract.contractid
    and FRates.Last_ID = 1

这应该做的是允许派生表生成所有合同ID的列表,但给出优先级列表。从理论上讲,这在服务器上会更容易,并且您不会比必要的次数更多地敲桌子。您可以做的另一件事是将 SET STATISTICS IO ONSET STATISTICS TIME ON 添加到查询的顶部(假设您在 SQL Server Management Studio 中对此进行测试)。如果在 SSMS 中,您将在 [Messages] 选项卡上获得一个日志,其中说明每个表的逻辑/物理读取计数是多少,以及查询所花费的时间。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-14
    • 2015-01-10
    相关资源
    最近更新 更多