为啥加入从视图中选择前 N 比加入视图要快得多?

Posted

技术标签:

【中文标题】为啥加入从视图中选择前 N 比加入视图要快得多?【英文标题】:Why is joining on select top N from view much faster than joining on just the view?为什么加入从视图中选择前 N 比加入视图要快得多? 【发布时间】:2015-03-06 14:38:22 【问题描述】:

我有一个视图,其中有一个未编入索引的列在另一个查询中加入。我们无法在视图中创建索引,因为它使用了外连接。

外部查询本质上是:

select * from SomeTable AS T INNER JOIN
v_SomeView AS V ON T.Column = V.Column

查询非常慢,通过一些实验我们发现将查询更改为:

select * from SomeTable AS T INNER JOIN
(select TOP 10000000 * from v_SomeView) AS V ON T.Column = V.Column

大大加快了查询速度(10 分钟到大约 5 秒)。

视图中只有大约 1000 行。

有人能解释一下导致性能如此巨大差异的原因吗?

执行计划:

http://dropcanvas.com/011il

这是实际视图v_SecurityClassificationResult

SELECT        rc.SecurityId, rc.Status, rc.ExpiryDate, rc.StartDate, rc.ClassificationValue AS RiskClass, sg.ClassificationValue AS SecurityGroup, 
                         br.ClassificationValue AS BondRating, cur.ClassificationValue AS Currency, cod.ClassificationValue AS Country, reg.ClassificationValue AS Region
FROM            dbo.SecurityClassificationResult AS rc LEFT OUTER JOIN
                         dbo.SecurityClassificationResult AS sg ON rc.SecurityId = sg.SecurityId AND rc.ExpiryDate = sg.ExpiryDate AND sg.SecurityClassificationFieldId =
                             (SELECT        SecurityClassificationFieldId
                               FROM            dbo.SecurityClassificationField
                               WHERE        (ClassificationField = 'SecurityGroup')) LEFT OUTER JOIN
                         dbo.SecurityClassificationResult AS br ON rc.SecurityId = br.SecurityId AND sg.ExpiryDate = br.ExpiryDate AND br.SecurityClassificationFieldId =
                             (SELECT        SecurityClassificationFieldId
                               FROM            dbo.SecurityClassificationField
                               WHERE        (ClassificationField = 'BondRating')) LEFT OUTER JOIN
                         dbo.SecurityClassificationResult AS cur ON rc.SecurityId = cur.SecurityId AND br.ExpiryDate = cur.ExpiryDate AND cur.SecurityClassificationFieldId =
                             (SELECT        SecurityClassificationFieldId
                               FROM            dbo.SecurityClassificationField
                               WHERE        (ClassificationField = 'Currency')) LEFT OUTER JOIN
                         dbo.SecurityClassificationResult AS cod ON rc.SecurityId = cod.SecurityId AND cur.ExpiryDate = cod.ExpiryDate AND cod.SecurityClassificationFieldId =
                             (SELECT        SecurityClassificationFieldId
                               FROM            dbo.SecurityClassificationField
                               WHERE        (ClassificationField = 'CountryOfDomicile')) LEFT OUTER JOIN
                         dbo.SecurityClassificationResult AS reg ON rc.SecurityId = reg.SecurityId AND cod.ExpiryDate = reg.ExpiryDate AND reg.SecurityClassificationFieldId =
                             (SELECT        SecurityClassificationFieldId
                               FROM            dbo.SecurityClassificationField
                               WHERE        (ClassificationField = 'Region'))
WHERE        (rc.SecurityClassificationFieldId =
                             (SELECT        SecurityClassificationFieldId
                               FROM            dbo.SecurityClassificationField
                               WHERE        (ClassificationField = 'RiskClass')))

以及从中选择的另一个查询:

SELECT        count(*)
FROM            dbo.Fund_RelevantSecurity AS FRS INNER JOIN
                         dbo.SourceSystemImportLog AS SSIL ON FRS.SourceSystemImporLogtId = SSIL.SourceSystemImporLogtId INNER JOIN
                         dbo.FundInstanceHolding AS FIH ON FRS.FundInstanceHoldingId = FIH.FundInstanceHoldingId INNER JOIN
                         dbo.FundInstance AS FI ON FIH.FundInstanceId = FI.FundInstanceId INNER JOIN
                         dbo.SourceSystemImportLog AS SSILOrig ON SSILOrig.SourceSystemImporLogtId = FI.SourceSystemImportLogId INNER JOIN
                         dbo.Security ON FRS.SecurityId = dbo.Security.SecurityId INNER JOIN
                         dbo.Fund ON FRS.FundId = dbo.Fund.FundId INNER JOIN
                         dbo.BusinessUnitFund ON dbo.Fund.FundId = dbo.BusinessUnitFund.FundId INNER JOIN
                         dbo.BusinessUnit ON dbo.BusinessUnit.BusinessUnitId = dbo.BusinessUnitFund.BusinessUnitId RIGHT OUTER JOIN
                         (select top 2147483647 * from dbo.v_SecurityClassificationResult) AS classificationResult ON FRS.SecurityId = classificationResult.SecurityId AND GETDATE() 
                         <= classificationResult.ExpiryDate AND GETDATE() >= classificationResult.StartDate
WHERE        (SSIL.ImportStatusId <> 5)

【问题讨论】:

能否贴出相关的执行计划? 您查看是否包含任何函数调用? @Tanner 不,它没有。 实际计划而不是估计计划如何?还可以查看视图的代码可能有助于了解一些情况。 我创建了表,并且在连接列(在 T.Column = V.Column 上)有和没有主键的情况下,我得到了相同的查询计划。我什至将选择更改为 select * from SomeTables AS T where x1 in (select x1 from v_SomeView) 并且仍然得到相同的查询计划。我在 SQL Server 2012 中运行。您在哪个 SQL Server 版本中运行?是否存在您没有向我们展示的 where 条件? 【参考方案1】:

我建议您重建索引并刷新统计信息。通过执行 TOP N,您的优化器采取了另一个计划,并且似乎这个计划要好得多。尝试检查预期和实际的记录数量,以找出计划错误的地方。 另一件事是,如果您重写视图,您可能会获得更好的性能。您可以执行以下操作(无法检查语法和逻辑,因为没有提供结构和数据):

SELECT
    rc.SecurityId ,
    rc.Status ,
    rc.ExpiryDate ,
    rc.StartDate ,
    s.ClassificationValue AS RiskClass ,
    MIN(CASE WHEN s.ClassificationValue = 'SecurityGroup' THEN s.ClassificationValue END) AS SecurityGroup ,
    MIN(CASE WHEN s.ClassificationValue = 'BondRating' THEN s.ClassificationValue END) AS BondRating ,
    MIN(CASE WHEN s.ClassificationValue = 'Currency' THEN s.ClassificationValue END) AS Currency ,
    MIN(CASE WHEN s.ClassificationValue = 'CountryOfDomicile' THEN s.ClassificationValue END) AS Country ,
    MIN(CASE WHEN s.ClassificationValue = 'Region' THEN s.ClassificationValue END) AS Region
FROM
    dbo.SecurityClassificationResult AS rc
    LEFT OUTER JOIN
    (
    SELECT s.ClassificationValue,
            sf.ClassificationField
    FROM  dbo.SecurityClassificationResult AS s
    JOIN dbo.SecurityClassificationField AS sf
      ON sf.SecurityClassificationFieldId = s.SecurityClassificationFieldId
    WHERE sf.ClassificationField IN ( 'SecurityGroup', 'BondRating', 'Currency', 'CountryOfDomicile' 'Region')

    ) s
    ON s.SecurityId = rc.SecurityId
WHERE
    ( rc.SecurityClassificationFieldId = (
                                           SELECT
                                            SecurityClassificationFieldId
                                           FROM
                                            dbo.SecurityClassificationField
                                           WHERE
                                            ( ClassificationField = 'RiskClass' )
                                         ) )
GROUP BY     rc.SecurityId ,
    rc.Status ,
    rc.ExpiryDate ,
    rc.StartDate ,
    s.ClassificationValue       

【讨论】:

以上是关于为啥加入从视图中选择前 N 比加入视图要快得多?的主要内容,如果未能解决你的问题,请参考以下文章

为啥使用声明的变量作为参数调用 UDF 比使用硬编码参数调用要快得多

为啥分配堆内存比分配堆栈内存快得多?

AVX 标量运算要快得多

react-native 渲染速度比从 firebase 加载数据要快得多

为啥从大表中查询 COUNT() 比 SUM() 快得多

Android:为什么本机代码比Java代码要快得多