我们在SQL Server 2005上有一个相当复杂的查询,具有以下特征:
所有联接都有一个共同的索引列。
它通常在30秒内运行,但是当再添加一个表连接(已连接的列索引)时,它似乎永远运行。
我注意到,如果删除一个特定的现有视图连接,即使使用新连接,它也会再次快速运行。
以下是查询的修改版本,删除了大多数联接但仍然显示性能问题。
--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
此问题并非特定于此查询,因为我在其他查询之前遇到过这种奇怪的行为,并且从未能完全解决它。我的理论是,SQL Server正在达到内部查询计划限制而变得愚蠢。
我将查询过滤到一个状态(返回一条记录但需要10分钟),以便通过DTA生成建议。我添加了一些所需的索引没有任何区别。
我检查了执行计划的结果,但没有发现任何异常。
关于还有什么要寻找的任何建议?
这就是我的尝试:
第一次查询的showplan:
|--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:
|--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)
答案 0 :(得分: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.
前缀(您应该get in the habit of doing),看看是否明确指出那些优化器的决定(或至少防止重复计划),并使用更短的别名是为了便于阅读。
另外不要忘记检查执行计划 - 可能是因为某些表的大小/基数,你得到了错误的连接类型。
编辑现在我们有了查询计划,我们可以看到糟糕的计划在它上面都有并行性。您可以尝试将MAXDOP 1
添加到查询中,但这只是暂时的缓解,不应该是真正的修复。我怀疑你看到了不良的基数估计和跨多个线程的不正确的工作分配(通常归咎于CXPACKET
等待,但他们的症状不是原因)。我会检查查询中各种表的统计信息。如果您在免费的SQL Sentry Plan Explorer中加载实际的执行计划,那么在Plan Tree选项卡上,您将能够立即看到估计的和实际的行计数在哪里可怕地关闭,这将指导您到哪个表需要statistics updated。至少,这可能是大约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);
这只是一个开始,当然不会解决你所有的问题。我不得不相信你并不真的想要一些连接表中的所有行。
答案 1 :(得分:2)
我有两点建议:
第一种方法是使用SQL Server Database Engine Tuning Advisor检查建议。它可能会检测需要添加的索引,这些索引会提高性能,而且不明显。
第二个选项只有在你可以处理稍微过时的数据时才会起作用,或者你可以触发我要描述的数据更新时发生的过程。它赢了“适用于定期更新的数据(每小时几次)。在我们的例子中,如果数据在24小时内准确就可以了,所以我们可以运行我即将每天描述一次的过程。如果你没有那种奢侈,请忽略其余部分。
我们遇到了类似的情况,无论我们做了什么,我们都无法提高性能。在很多大桌子上,连接太多了。从视图中返回431行花了五分钟左右。
因此,我们从BI(商业智能)团队的方法中抽取了一页,并根据我们的规范化数据构建了非规范化表。我们基本上创建了一个迷你版 - Data Warehouse。
我们的视图(需要永远运行)被命名为“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“可以快速查询。再次,只有在您的数据新鲜度需求不是实时的情况下,才能从视图创建非规范化表格。” >
答案 2 :(得分:2)
我怀疑@AaronBertrand对于过时的统计数据是正确的。
我要做的一件事就是删除一个隐式连接。我注意到当你混合使用隐式和显式连接时会发生不可思议的事情(虽然在结果集中比在性能上更多但是它仍然可以通过使优化器选择不是最优的东西来提高复杂查询的性能)。
我还想警告你,视图可能会导致缓慢,特别是如果它们是调用视图的视图。通过查看视图并查看是否可以使用查询中的直接代码替换它们,我看到查询加速了很多。如果每个视图调用相同的基表并且视图调用视图,则尤其如此。因此,如果更新统计信息没有帮助,请查看替换视图。