3

We have a fairly complex query on SQL Server 2005 with these characteristics:

  • 11 table joins
  • 8 view joins
  • 7 non-correlated query joins

All of the joins have a common indexed column.

It normally runs in under 30 seconds, but when adding one more table join (the joined column indexed), it runs seemingly forever.

I noticed that if I remove one particular existing view join, it runs fast again even with the new join.

Below is a modified version of the query with most joins removed but it still demonstrates the performance issue.

--This runs super slow
SELECT 
    L.LoanID    
FROM  dbo.Loan L 
    JOIN dbo.Company C ON c.CompanyKey = l.CompanyKey
    JOIN dbo.Status S  on L.LoanID = S.LoanID
    JOIN dbo.Participation P on L.LoanID = P.LoanID
    JOIN dbo.Delinquent D on  L.LoanID = D.LoanID
    JOIN dbo.Property Pr on Pr.Loanid = L.Loanid
    JOIN dbo.MailingAddress ma ON ma.LoanID = L.LoanID  
    LEFT JOIN dbo.BorrowerPhonePivot bp ON bp.loanid = l.loanid
WHERE s.primstat=1 AND  DATEADD(d,16,L.DueDate) <= C.TransPostDate


--This runs fast
 SELECT 
    L.LoanID    
 FROM  dbo.Loan L 
    JOIN dbo.Company C ON c.CompanyKey = l.CompanyKey
    JOIN dbo.Status S  on L.LoanID = S.LoanID
    JOIN dbo.Participation P on L.LoanID = P.LoanID
    JOIN dbo.Delinquent D on  L.LoanID = D.LoanID
    JOIN dbo.Property Pr on Pr.Loanid = L.Loanid
    --JOIN dbo.MailingAddress ma ON ma.LoanID = L.LoanID    
    LEFT JOIN dbo.BorrowerPhonePivot bp ON bp.loanid = l.loanid
 WHERE s.primstat=1 AND  DATEADD(d,16,L.DueDate) <= C.TransPostDate


 --This runs fast
 SELECT 
    L.LoanID    
 FROM  dbo.Loan L 
    JOIN dbo.Company C ON c.CompanyKey = l.CompanyKey
    JOIN dbo.Status S  on L.LoanID = S.LoanID
    JOIN dbo.Participation P on L.LoanID = P.LoanID
    JOIN dbo.Delinquent D on  L.LoanID = D.LoanID
    JOIN dbo.Property Pr on Pr.Loanid = L.Loanid
    JOIN dbo.MailingAddress ma ON ma.LoanID = L.LoanID  
    --LEFT JOIN dbo.BorrowerPhonePivot bp ON bp.loanid = l.loanid
 WHERE s.primstat=1 AND  DATEADD(d,16,L.DueDate) <= C.TransPostDate

This problem isn't specific to this query as I have encountered this bizarre behavior before in other queries and have never been able to fully solve it. My theory is that SQL Server is hitting an internal query plan limit and goes stupid.

I filtered the query to one state (returned one record but took 10 minutes) in order to generate recommendations via the DTA. I added some needed indexes without any difference.

I examined the execution plan results but didn't find anything out of the ordinary.

Any suggestions on what else to look for?

This is what I have tried:

  • DTA - added one needed index out of 4 generated recommendations. (others are already covered)
  • MAXDOP 1 to see if it is threading issue per @AaronBertrand
  • UPDATE STATISTICS on tables per @AaronBertrand
  • Ran sqlplan (pastebin) in SQL sentry plan explorer per @AaronBertrand. The strange this is that the Actual Rows column shows a number that is an order of magnitude larger than reality. Somehow the query plan optimizer is getting confused.

showplan of first query:

  |--Nested Loops(Left Outer Join, OUTER REFERENCES:([L].[LoanID]))
   |--Nested Loops(Inner Join, WHERE:([Expr1056]<=[Service_Prod].[dbo].[Company].[TransPostDate] as [C].[TransPostDate]))
   |    |--Hash Match(Inner Join, HASH:([ma].[LoanID])=([D].[LoanID]), RESIDUAL:([Service_Prod].[dbo].[Delinquent].[LoanID] as [D].[LoanID]=[Service_Prod].[dbo].[MailingAddress].[LoanID] as [ma].[LoanID]))
   |    |    |--Hash Match(Inner Join, HASH:([ma].[LoanID])=([P].[LoanID]), RESIDUAL:([Service_Prod].[dbo].[Participation].[LoanID] as [P].[LoanID]=[Service_Prod].[dbo].[MailingAddress].[LoanID] as [ma].[LoanID]))
   |    |    |    |--Merge Join(Inner Join, MERGE:([Pr].[LoanID])=([ma].[LoanID]), RESIDUAL:([Service_Prod].[dbo].[Property].[LoanID] as [Pr].[LoanID]=[Service_Prod].[dbo].[MailingAddress].[LoanID] as [ma].[LoanID]))
   |    |    |    |    |--Merge Join(Inner Join, MERGE:([Pr].[LoanID])=([L].[LoanID]), RESIDUAL:([Service_Prod].[dbo].[Loan].[LoanID] as [L].[LoanID]=[Service_Prod].[dbo].[Property].[LoanID] as [Pr].[LoanID]))
   |    |    |    |    |    |--Merge Join(Inner Join, MERGE:([Pr].[LoanID])=([S].[LoanID]), RESIDUAL:([Service_Prod].[dbo].[Status].[LoanID] as [S].[LoanID]=[Service_Prod].[dbo].[Property].[LoanID] as [Pr].[LoanID]))
   |    |    |    |    |    |    |--Index Scan(OBJECT:([Service_Prod].[dbo].[Property].[aaaaaProperty_PK] AS [Pr]), ORDERED FORWARD)
   |    |    |    |    |    |    |--Index Seek(OBJECT:([Service_Prod].[dbo].[Status].[_dta_index_Status_114_1122103038__K13_K1_K2_K3] AS [S]), SEEK:([S].[PrimStat]=(1)) ORDERED FORWARD)
   |    |    |    |    |    |--Compute Scalar(DEFINE:([Expr1056]=dateadd(day,(16),[Service_Prod].[dbo].[Loan].[DueDate] as [L].[DueDate])))
   |    |    |    |    |         |--Index Scan(OBJECT:([Service_Prod].[dbo].[Loan].[_dta_index_Loan_K1_K66_K10_K23] AS [L]), ORDERED FORWARD)
   |    |    |    |    |--Index Scan(OBJECT:([Service_Prod].[dbo].[MailingAddress].[StatusMailingAddress] AS [ma]), ORDERED FORWARD)
   |    |    |    |--Index Scan(OBJECT:([Service_Prod].[dbo].[Participation].[Reference17] AS [P]))
   |    |    |--Index Scan(OBJECT:([Service_Prod].[dbo].[Delinquent].[aaaaaDelinquent_PK] AS [D]))
   |    |--Table Scan(OBJECT:([Service_Prod].[dbo].[Company] AS [C]))
   |--Stream Aggregate(GROUP BY:([l].[LoanID]))
        |--Filter(WHERE:([Service_Prod].[dbo].[Loan].[LoanID] as [l].[LoanID]=[Service_Prod].[dbo].[Loan].[LoanID] as [L].[LoanID]))
             |--Hash Match(Inner Join, HASH:([b].[LoanID], [b].[AssmRecCounter], [b].[BorrowerID])=([bp].[LoanID], [bp].[AssmRecCounter], [bp].[BorrowerID]), RESIDUAL:([Service_Prod].[dbo].[BorrowerPhone].[LoanID] as [bp].[LoanID]=[Service_Prod].[dbo].[Borrower].[LoanID] as [b].[LoanID] AND [Service_Prod].[dbo].[Borrower].[AssmRecCounter] as [b].[AssmRecCounter]=[Service_Prod].[dbo].[BorrowerPhone].[AssmRecCounter] as [bp].[AssmRecCounter] AND [Service_Prod].[dbo].[Borrower].[BorrowerID] as [b].[BorrowerID]=[Service_Prod].[dbo].[BorrowerPhone].[BorrowerID] as [bp].[BorrowerID]))
                  |--Hash Match(Inner Join, HASH:([Service_Prod].[dbo].[Company].[CompanyKey])=([l].[CompanyKey]))
                  |    |--Index Scan(OBJECT:([Service_Prod].[dbo].[Company].[aaaaaCompany_PK]))
                  |    |--Merge Join(Inner Join, MERGE:([s].[LoanID])=([b].[LoanID]), RESIDUAL:([Service_Prod].[dbo].[Status].[LoanID] as [s].[LoanID]=[Service_Prod].[dbo].[Borrower].[LoanID] as [b].[LoanID]))
                  |         |--Index Seek(OBJECT:([Service_Prod].[dbo].[Status].[_dta_index_Status_114_1122103038__K13_K1_K2_K3] AS [s]), SEEK:([s].[PrimStat]=(1)) ORDERED FORWARD)
                  |         |--Merge Join(Inner Join, MERGE:([l].[LoanID])=([b].[LoanID]), RESIDUAL:([Service_Prod].[dbo].[Loan].[LoanID] as [l].[LoanID]=[Service_Prod].[dbo].[Borrower].[LoanID] as [b].[LoanID] AND [Service_Prod].[dbo].[Loan].[AssmRecCounter] as [l].[AssmRecCounter]=[Service_Prod].[dbo].[Borrower].[AssmRecCounter] as [b].[AssmRecCounter]))
                  |              |--Index Scan(OBJECT:([Service_Prod].[dbo].[Loan].[_dta_index_Loan_K1_K66_K10_K23] AS [l]), ORDERED FORWARD)
                  |              |--Index Scan(OBJECT:([Service_Prod].[dbo].[Borrower].[aaaaaBorrower_PK] AS [b]), ORDERED FORWARD)
                  |--Index Seek(OBJECT:([Service_Prod].[dbo].[BorrowerPhone].[_dta_index_BorrowerPhone_K12_K1_K2_K3_K5] AS [bp]), SEEK:([bp].[ForeignPhone]=(0)),  WHERE:([Service_Prod].[dbo].[BorrowerPhone].[PhoneType] as [bp].[PhoneType]=(0) OR [Service_Prod].[dbo].[BorrowerPhone].[PhoneType] as [bp].[PhoneType]=(1)) ORDERED FORWARD)

showplan of 2nd query:

  |--Parallelism(Gather Streams)
   |--Hash Match(Left Outer Join, HASH:([L].[LoanID])=([l].[LoanID]), RESIDUAL:([Service_Prod].[dbo].[Loan].[LoanID] as [l].[LoanID]=[Service_Prod].[dbo].[Loan].[LoanID] as [L].[LoanID]))
        |--Parallelism(Repartition Streams, Hash Partitioning, PARTITION COLUMNS:([L].[LoanID]))
        |    |--Hash Match(Inner Join, HASH:([Pr].[LoanID])=([D].[LoanID]), RESIDUAL:([Service_Prod].[dbo].[Delinquent].[LoanID] as [D].[LoanID]=[Service_Prod].[dbo].[Property].[LoanID] as [Pr].[LoanID]))
        |         |--Bitmap(HASH:([Pr].[LoanID]), DEFINE:([Bitmap1065]))
        |         |    |--Hash Match(Inner Join, HASH:([Pr].[LoanID])=([P].[LoanID]), RESIDUAL:([Service_Prod].[dbo].[Participation].[LoanID] as [P].[LoanID]=[Service_Prod].[dbo].[Property].[LoanID] as [Pr].[LoanID]))
        |         |         |--Bitmap(HASH:([Pr].[LoanID]), DEFINE:([Bitmap1064]))
        |         |         |    |--Hash Match(Inner Join, HASH:([S].[LoanID])=([Pr].[LoanID]), RESIDUAL:([Service_Prod].[dbo].[Status].[LoanID] as [S].[LoanID]=[Service_Prod].[dbo].[Property].[LoanID] as [Pr].[LoanID]))
        |         |         |         |--Bitmap(HASH:([S].[LoanID]), DEFINE:([Bitmap1063]))
        |         |         |         |    |--Hash Match(Inner Join, HASH:([S].[LoanID])=([L].[LoanID]), RESIDUAL:([Service_Prod].[dbo].[Loan].[LoanID] as [L].[LoanID]=[Service_Prod].[dbo].[Status].[LoanID] as [S].[LoanID]))
        |         |         |         |         |--Bitmap(HASH:([S].[LoanID]), DEFINE:([Bitmap1062]))
        |         |         |         |         |    |--Parallelism(Repartition Streams, Hash Partitioning, PARTITION COLUMNS:([S].[LoanID]))
        |         |         |         |         |         |--Index Seek(OBJECT:([Service_Prod].[dbo].[Status].[IDX_Status_PrimStat_INCLoanID] AS [S]), SEEK:([S].[PrimStat]=(1)) ORDERED FORWARD)
        |         |         |         |         |--Parallelism(Repartition Streams, Hash Partitioning, PARTITION COLUMNS:([L].[LoanID]), WHERE:(PROBE([Bitmap1062],[Service_Prod].[dbo].[Loan].[LoanID] as [L].[LoanID])))
        |         |         |         |              |--Nested Loops(Inner Join, WHERE:([Expr1053]<=[Service_Prod].[dbo].[Company].[TransPostDate] as [C].[TransPostDate]))
        |         |         |         |                   |--Parallelism(Distribute Streams, RoundRobin Partitioning)
        |         |         |         |                   |    |--Table Scan(OBJECT:([Service_Prod].[dbo].[Company] AS [C]))
        |         |         |         |                   |--Compute Scalar(DEFINE:([Expr1053]=dateadd(day,(16),[Service_Prod].[dbo].[Loan].[DueDate] as [L].[DueDate])))
        |         |         |         |                        |--Index Scan(OBJECT:([Service_Prod].[dbo].[Loan].[_dta_index_Loan_K1_K66_K10_K23] AS [L]))
        |         |         |         |--Parallelism(Repartition Streams, Hash Partitioning, PARTITION COLUMNS:([Pr].[LoanID]))
        |         |         |              |--Index Scan(OBJECT:([Service_Prod].[dbo].[Property].[aaaaaProperty_PK] AS [Pr]),  WHERE:(PROBE([Bitmap1063],[Service_Prod].[dbo].[Property].[LoanID] as [Pr].[LoanID])))
        |         |         |--Parallelism(Repartition Streams, Hash Partitioning, PARTITION COLUMNS:([P].[LoanID]))
        |         |              |--Index Scan(OBJECT:([Service_Prod].[dbo].[Participation].[Reference17] AS [P]),  WHERE:(PROBE([Bitmap1064],[Service_Prod].[dbo].[Participation].[LoanID] as [P].[LoanID])))
        |         |--Parallelism(Repartition Streams, Hash Partitioning, PARTITION COLUMNS:([D].[LoanID]))
        |              |--Index Scan(OBJECT:([Service_Prod].[dbo].[Delinquent].[aaaaaDelinquent_PK] AS [D]),  WHERE:(PROBE([Bitmap1065],[Service_Prod].[dbo].[Delinquent].[LoanID] as [D].[LoanID])))
        |--Stream Aggregate(GROUP BY:([l].[LoanID]))
             |--Sort(ORDER BY:([l].[LoanID] ASC))
                  |--Parallelism(Repartition Streams, Hash Partitioning, PARTITION COLUMNS:([l].[LoanID]))
                       |--Hash Match(Inner Join, HASH:([b].[LoanID], [b].[AssmRecCounter], [b].[BorrowerID])=([bp].[LoanID], [bp].[AssmRecCounter], [bp].[BorrowerID]), RESIDUAL:([Service_Prod].[dbo].[BorrowerPhone].[LoanID] as [bp].[LoanID]=[Service_Prod].[dbo].[Borrower].[LoanID] as [b].[LoanID] AND [Service_Prod].[dbo].[Borrower].[AssmRecCounter] as [b].[AssmRecCounter]=[Service_Prod].[dbo].[BorrowerPhone].[AssmRecCounter] as [bp].[AssmRecCounter] AND [Service_Prod].[dbo].[Borrower].[BorrowerID] as [b].[BorrowerID]=[Service_Prod].[dbo].[BorrowerPhone].[BorrowerID] as [bp].[BorrowerID]))
                            |--Bitmap(HASH:([b].[LoanID], [b].[AssmRecCounter], [b].[BorrowerID]), DEFINE:([Bitmap1068]))
                            |    |--Parallelism(Repartition Streams, Hash Partitioning, PARTITION COLUMNS:([b].[LoanID], [b].[AssmRecCounter], [b].[BorrowerID]))
                            |         |--Hash Match(Inner Join, HASH:([s].[LoanID], [l].[AssmRecCounter])=([b].[LoanID], [b].[AssmRecCounter]), RESIDUAL:([Service_Prod].[dbo].[Status].[LoanID] as [s].[LoanID]=[Service_Prod].[dbo].[Borrower].[LoanID] as [b].[LoanID] AND [Service_Prod].[dbo].[Loan].[AssmRecCounter] as [l].[AssmRecCounter]=[Service_Prod].[dbo].[Borrower].[AssmRecCounter] as [b].[AssmRecCounter]))
                            |              |--Bitmap(HASH:([s].[LoanID], [l].[AssmRecCounter]), DEFINE:([Bitmap1067]))
                            |              |    |--Parallelism(Repartition Streams, Hash Partitioning, PARTITION COLUMNS:([s].[LoanID], [l].[AssmRecCounter]))
                            |              |         |--Hash Match(Inner Join, HASH:([Service_Prod].[dbo].[Company].[CompanyKey])=([l].[CompanyKey]))
                            |              |              |--Parallelism(Distribute Streams, Broadcast Partitioning)
                            |              |              |    |--Index Scan(OBJECT:([Service_Prod].[dbo].[Company].[aaaaaCompany_PK]))
                            |              |              |--Hash Match(Inner Join, HASH:([s].[LoanID])=([l].[LoanID]), RESIDUAL:([Service_Prod].[dbo].[Loan].[LoanID] as [l].[LoanID]=[Service_Prod].[dbo].[Status].[LoanID] as [s].[LoanID]))
                            |              |                   |--Bitmap(HASH:([s].[LoanID]), DEFINE:([Bitmap1066]))
                            |              |                   |    |--Parallelism(Repartition Streams, Hash Partitioning, PARTITION COLUMNS:([s].[LoanID]))
                            |              |                   |         |--Index Seek(OBJECT:([Service_Prod].[dbo].[Status].[IDX_Status_PrimStat_INCLoanID] AS [s]), SEEK:([s].[PrimStat]=(1)) ORDERED FORWARD)
                            |              |                   |--Parallelism(Repartition Streams, Hash Partitioning, PARTITION COLUMNS:([l].[LoanID]))
                            |              |                        |--Index Scan(OBJECT:([Service_Prod].[dbo].[Loan].[_dta_index_Loan_K1_K66_K10_K23] AS [l]),  WHERE:(PROBE([Bitmap1066],[Service_Prod].[dbo].[Loan].[LoanID] as [l].[LoanID])))
                            |              |--Parallelism(Repartition Streams, Hash Partitioning, PARTITION COLUMNS:([b].[LoanID], [b].[AssmRecCounter]))
                            |                   |--Index Scan(OBJECT:([Service_Prod].[dbo].[Borrower].[aaaaaBorrower_PK] AS [b]),  WHERE:(PROBE([Bitmap1067],[Service_Prod].[dbo].[Borrower].[LoanID] as [b].[LoanID],[Service_Prod].[dbo].[Borrower].[AssmRecCounter] as [b].[AssmRecCounter])))
                            |--Parallelism(Repartition Streams, Hash Partitioning, PARTITION COLUMNS:([bp].[LoanID], [bp].[AssmRecCounter], [bp].[BorrowerID]))
                                 |--Index Seek(OBJECT:([Service_Prod].[dbo].[BorrowerPhone].[_dta_index_BorrowerPhone_K12_K1_K2_K3_K5] AS [bp]), SEEK:([bp].[ForeignPhone]=(0)),  WHERE:(([Service_Prod].[dbo].[BorrowerPhone].[PhoneType] as [bp].[PhoneType]=(0) OR [Service_Prod].[dbo].[BorrowerPhone].[PhoneType] as [bp].[PhoneType]=(1)) AND PROBE([Bitmap1068],[Service_Prod].[dbo].[BorrowerPhone].[LoanID] as [bp].[LoanID],[Service_Prod].[dbo].[BorrowerPhone].[AssmRecCounter] as [bp].[AssmRecCounter],[Service_Prod].[dbo].[BorrowerPhone].[BorrowerID] as [bp].[BorrowerID])) ORDERED FORWARD)
4

3 回答 3

3

这听起来很愚蠢,但是尝试各种组合并添加所有连接条件,以使优化器有最佳匹配机会。我已经看到这有助于优化器选择更好的计划,这仅仅是因为表的顺序和基数。

SELECT ac.AccountNum
    FROM dbo.Account AS ac
    INNER JOIN dbo.Address AS ad
      ON ad.AccountNum = ac.AccountNum
    INNER JOIN dbo.PhoneView AS p
      ON p.AccountNum = ac.AccountNum
      AND p.AccountNum = ad.AccountNum; -- extra help here

也可以按相反的顺序尝试:

SELECT ac.AccountNum
    FROM dbo.Account AS ac
    INNER JOIN dbo.PhoneView AS p
      ON p.AccountNum = ac.AccountNum
    INNER JOIN dbo.Address AS ad
      ON ad.AccountNum = p.AccountNum
      AND ad.AccountNum = ac.AccountNum; -- extra help here

我已经dbo.为每个表添加了前缀(您应该养成这样做的习惯),以查看是否明确指定这些前缀可以改善优化器的决策(或至少防止重复计划),并使用较短的别名来提高可读性。

也不要忘记检查执行计划——可能是因为其中一些表的大小/基数,你得到了错误的连接类型。

编辑现在我们有查询计划,我们可以看到坏计划到处都有并行性。您可以尝试添加MAXDOP 1到查询中,但这只是暂时的缓解,不应该是真正的解决方法。我怀疑您看到错误的基数估计和跨多个线程的工作分配不当(通常归咎于CXPACKET等待,但它们是症状而不是原因)。我会检查查询中各种表的统计信息。如果您在免费的SQL Sentry Plan Explorer中加载实际执行计划,在“计划树”选项卡上,您将立即能够看到估计的和实际的行数严重偏离的地方,这应该会指导您需要哪些表更新统计信息. 至少,这可能是大约 90% 的时间执行可怕的并行性的原因。

几个建议。由于公司只有一行,因此绝对不需要在查询中包含公司。相反,将截止日期拉入一个变量,减去 16 天,现在您可以使用 Loan.DueDate 上的索引(如果存在):

DECLARE @d SMALLDATETIME;

SELECT @d = DATEADD(DAY, -16, TransPostDate) FROM dbo.Company;

SELECT L.LoanID
FROM dbo.Loan AS L
  INNER JOIN dbo.[Status] AS S                 ON L.LoanID  = S.LoanID
  INNER JOIN dbo.Participation AS P            ON L.LoanID  = P.LoanID
  INNER JOIN dbo.Delinquent AS D               ON L.LoanID  = D.LoanID
  INNER JOIN dbo.Property AS Pr                ON Pr.LoanID = L.LoanID
  INNER JOIN dbo.MailingAddress AS ma          ON ma.LoanID = L.LoanID  
  LEFT OUTER JOIN dbo.BorrowerPhonePivot AS bp ON bp.loanid = L.loanid
WHERE 
  s.primstat = 1 
  AND L.DueDate <= @cutoff;

由于您只返回 LoanID,这不是做同样的事情吗?

DECLARE @d SMALLDATETIME;

SELECT @d = DATEADD(DAY, -16, TransPostDate) FROM dbo.Company;

SELECT L.LoanID
FROM dbo.Loan AS L
  WHERE L.DueDate <= @cutoff
  AND EXISTS (SELECT 1 FROM dbo.Status         WHERE LoanID = L.LoanID AND primstat = 1)
  AND EXISTS (SELECT 1 FROM dbo.Participation  WHERE LoanID = L.LoanID)
  AND EXISTS (SELECT 1 FROM dbo.Delinquent     WHERE LoanID = L.LoanID)
  AND EXISTS (SELECT 1 FROM dbo.Property       WHERE LoanID = L.LoanID)
  AND EXISTS (SELECT 1 FROM dbo.MailingAddress WHERE LoanID = L.LoanID);

这只是一个开始,当然不会解决你所有的问题。我必须相信您并不真正想要其中一些联接表中的所有行。

于 2012-05-30T20:07:45.243 回答
2

我有两个建议:

第一种是使用SQL Server 数据库引擎优化顾问来检查建议。 它可能会检测到需要添加的索引,这些索引会提高性能,并且不明显。

第二个选项只有在您可以处理稍微陈旧的数据时才有效,或者您可以在数据更新时触发我将要描述的过程。 它不适用于定期更新的数据(一小时左右几次)。在我们的例子中,如果数据在 24 小时内准确无误,那么我们可以每天运行一次我将要描述的过程。如果您没有那种奢侈,请忽略其余的。

我们遇到了类似的情况,无论我们做什么,我们都无法提高性能。在太多的大表上,连接太多了。从视图返回 431 行花了大约 5 分钟。

因此,我们借鉴了 BI(商业智能)团队的方法,并根据我们的规范化数据构建了非规范化表。我们基本上创建了一个小型数据仓库

我们的视图(需要永远运行)被命名为“vw_StoreData”,我们使用语句导出它

IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[tmpCompiledStoreData]') AND type in (N'U'))
DROP TABLE [dbo].[tmpCompiledStoreData]


Select * Into tmpCompiledStoreData FROM vw_StoreData

IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[CompiledStoreData]') AND type in (N'U'))
DROP TABLE [dbo].[CompiledStoreData]


exec sp_rename 'tmpCompiledStoreData', 'CompiledStoreData'

GRANT SELECT ON [dbo].[CompiledStoreData] TO [SomeUser]
GRANT SELECT ON [dbo].[CompiledStoreData] TO [SomeOtheruser]
...etc

与视图相比,生成的表 CompiledStoreData 查询起来快如闪电。同样,从视图创建非规范化表仅在您的数据新鲜度需求不是实时的情况下才有效。

于 2012-05-30T19:15:28.560 回答
2

我怀疑@AaronBertrand 关于统计数据已过时是正确的。

我要做的一件事是删除一个隐式连接。我注意到,当您混合隐式和显式连接时,可能会发生不稳定的事情(尽管在不良结果集中的情况下比性能更多,但它仍然可以通过使优化器选择不太理想的东西来提高复杂查询的性能)。

我还想警告您,视图可能会导致速度变慢,尤其是当它们是调用视图的视图时。通过查看视图并查看是否可以用查询中的直接代码替换它们,我发现查询速度大大加快。如果每个视图调用相同的基表并且视图调用视图时,这可能尤其如此。因此,如果更新统计信息没有帮助,请考虑替换视图。

于 2012-05-30T21:59:18.213 回答