2009-10-26 251 views
0

我有一個使用Linq2SQL「尖刺」的項目,現在正在運行一些主要的查詢性能問題。去搞清楚。SQLServer查詢優化問題

Linq實際上在簡單的查詢和命令場景下工作得很好,但有一些過濾器激烈的查詢需要重寫爲Sprocs。

我想知道是否有人可以給我一些高級指針,以節省優化由Linq生成的怪物查詢的時間。

我認爲用臨時表替換所有的「連接(@ p1,@ p2)」內部連接的子句將是一個好的開始。

所有外鍵和where子句列都被編入索引。

任何見解都值得讚賞。

下面的代碼:

SELECT [t9].[ID], [t9].[Description], [t9].[AreaCodeID], [t9].[BedroomCodeID], [t9].[BathroomCodeID], [t9].[DwellingCodeID], [t9].[LandlordID], [t9].[ParsedItemID], [t9].[DeletedReasonID], [t9].[CoordinateID], [t9].[Address], [t9].[PhonePrefix1], [t9].[Phone1], [t9].[PhonePrefix2], [t9].[Phone2], [t9].[EmailAddress], [t9].[RentAmount], [t9].[SquareFeet], [t9].[DateAvailable], [t9].[DateCreated], [t9].[IsDeleted], [t9].[RowVersion], [t9].[ID2], [t9].[ParentAreaCodeID], [t9].[AreaGroupID], [t9].[Description2], [t9].[Order], [t9].[IsTopLevelArea], [t9].[IsDeleted2], [t9].[ID3], [t9].[CityID], [t9].[Description3], [t9].[Order2], [t9].[IsPrimary], [t9].[IsDeleted3], [t9].[ID4], [t9].[HostURL], [t9].[Description4], [t9].[Rate], [t9].[RateTax], [t9].[RateTaxCode], [t9].[Currency], [t9].[LogoImageFileName], [t9].[FlashQuotesFileName], [t9].[TestimonialQuotesFileName], [t9].[GoogleAnalyticsTrackingCode], [t9].[GoogleMapsAPIKey], [t9].[IDHash], [t9].[test], [t9].[ID5], [t9].[Description5], [t9].[ID6], [t9].[Description6], [t9].[Order3], [t9].[IsDeleted4], [t9].[ID7], [t9].[Description7], [t9].[IsDeleted5], [t9].[Order4] 
FROM (
    SELECT TOP (100) [t0].[ID], [t0].[Description], [t0].[AreaCodeID], [t0].[BedroomCodeID], [t0].[BathroomCodeID], [t0].[DwellingCodeID], [t0].[LandlordID], [t0].[ParsedItemID], [t0].[DeletedReasonID], [t0].[CoordinateID], [t0].[Address], [t0].[PhonePrefix1], [t0].[Phone1], [t0].[PhonePrefix2], [t0].[Phone2], [t0].[EmailAddress], [t0].[RentAmount], [t0].[SquareFeet], [t0].[DateAvailable], [t0].[DateCreated], [t0].[IsDeleted], [t0].[RowVersion], [t1].[ID] AS [ID2], [t1].[ParentAreaCodeID], [t1].[AreaGroupID], [t1].[Description] AS [Description2], [t1].[Order], [t1].[IsTopLevelArea], [t1].[IsDeleted] AS [IsDeleted2], [t2].[ID] AS [ID3], [t2].[CityID], [t2].[Description] AS [Description3], [t2].[Order] AS [Order2], [t2].[IsPrimary], [t2].[IsDeleted] AS [IsDeleted3], [t3].[ID] AS [ID4], [t3].[HostURL], [t3].[Description] AS [Description4], [t3].[Rate], [t3].[RateTax], [t3].[RateTaxCode], [t3].[Currency], [t3].[LogoImageFileName], [t3].[FlashQuotesFileName], [t3].[TestimonialQuotesFileName], [t3].[GoogleAnalyticsTrackingCode], [t3].[GoogleMapsAPIKey], [t3].[IDHash], [t5].[test], [t5].[ID] AS [ID5], [t5].[Description] AS [Description5], [t6].[ID] AS [ID6], [t6].[Description] AS [Description6], [t6].[Order] AS [Order3], [t6].[IsDeleted] AS [IsDeleted4], [t7].[ID] AS [ID7], [t7].[Description] AS [Description7], [t7].[IsDeleted] AS [IsDeleted5], [t7].[Order] AS [Order4] 
    FROM [dbo].[Listing] AS [t0] 
    INNER JOIN ([dbo].[AreaCode] AS [t1] 
     INNER JOIN ([dbo].[AreaGroup] AS [t2] 
      INNER JOIN [dbo].[City] AS [t3] ON [t3].[ID] = [t2].[CityID]) ON [t2].[ID] = [t1].[AreaGroupID]) ON [t1].[ID] = [t0].[AreaCodeID] 
    LEFT OUTER JOIN (
     SELECT 1 AS [test], [t4].[ID], [t4].[Description] 
     FROM [dbo].[BathroomCode] AS [t4] 
     ) AS [t5] ON [t5].[ID] = [t0].[BathroomCodeID] 
    INNER JOIN [dbo].[BedroomCode] AS [t6] ON [t6].[ID] = [t0].[BedroomCodeID] 
    INNER JOIN [dbo].[DwellingCode] AS [t7] ON [t7].[ID] = [t0].[DwellingCodeID] 
    WHERE (NOT ([t0].[IsDeleted] = 1)) AND (EXISTS(
     SELECT NULL AS [EMPTY] 
     FROM [dbo].[ListingMiscellaneousCode] AS [t8] 
     WHERE ([t8].[MiscellaneousCodeID] IN (@p0, @p1, @p2, @p3, @p4, @p5, @p6)) AND ([t8].[ListingID] = [t0].[ID]) 
     )) AND ([t0].[DwellingCodeID] IN (@p7)) AND ([t0].[AreaCodeID] IN (@p8, @p9, @p10, @p11, @p12, @p13, @p14, @p15, @p16, @p17, @p18, @p19, @p20, @p21, @p22, @p23, @p24, @p25, @p26, @p27, @p28, @p29, @p30, @p31, @p32, @p33, @p34, @p35, @p36, @p37, @p38, @p39, @p40, @p41, @p42, @p43, @p44, @p45, @p46, @p47, @p48, @p49, @p50, @p51, @p52, @p53, @p54, @p55, @p56, @p57, @p58, @p59, @p60, @p61, @p62, @p63, @p64, @p65, @p66, @p67, @p68, @p69, @p70, @p71, @p72, @p73, @p74, @p75, @p76, @p77, @p78, @p79, @p80, @p81, @p82, @p83, @p84, @p85, @p86, @p87, @p88, @p89, @p90, @p91, @p92, @p93, @p94, @p95, @p96, @p97, @p98, @p99, @p100, @p101, @p102, @p103, @p104, @p105, @p106, @p107, @p108, @p109, @p110, @p111)) 
    ) AS [t9] 
ORDER BY [t9].[DateCreated] DESC, [t9].[RentAmount], [t9].[Description2] 

正如你可能已經猜到,這個問題部分完全位於where子句。刪除這會導致查詢進行得非常快。即使有了這個Where子句,它並不是很慢(大約1秒),但問題是,我還必須根據類似的樣式查詢返回當前數據的各種計數。整個過程由於具有較差的Where子句的多個查詢而花費超過5秒。

我不明白的另一件事是,改變查詢的頁面大小,即「...選擇TOP(100)...」到更高的數字,如「... Select TOP(5000) ...「不會減慢查詢的速度。這對我來說很奇怪,更多的證據表明,我認爲這個問題有望通過修改後的sql來解決。

您還會注意到,Where子句特別是(對於areacodeid)查詢了近100個參數。這是設計。現在我可以在父表中進行破解,以減少一些反規範化的代價,但我希望首先有一個純SQL修復,它可以讓我有效地加入到臨時表中,其中包含100個參數。

感謝您的幫助。

+0

如果您包含底層架構(它們上的表和索引)以及原始Linq查詢(或多個查詢),我相信它可以修復。看起來不像有什麼不尋常的東西,如果它有性能問題,它可以修復而不訴諸於存儲的特效。 – KristoferA 2009-10-27 03:13:04

+0

......哦,忘了補充......如果查詢性能不夠好,先查找性能問題的原因。然後優化。盲目優化只是浪費時間。如果您可以包含[鏈接]執行計劃和查詢的I/O統計信息,那也同樣有幫助。 – KristoferA 2009-10-27 03:19:28

回答

0

這裏沒有什麼東西看起來「不好」。看着它,它看起來很可怕,但在做一些重新格式化和刪除無關的括號後,並沒有那麼糟糕。我從來不喜歡嵌套的JOIN,我會考慮清理它,但這是個人偏好:我不認爲它會爲性能做任何事情。

所以...如果取出WHERE子句會加快速度,我會查看索引和隱式轉換。第一個是不言自明的;第二個我以前被燒過了。兩者都可以通過分析執行計劃來檢測。

實際上,當SQL Server轉換數據庫列中的數據而不是轉換與數據庫列進行比較的參數時,隱式轉換很糟糕。當將VARCHAR數據庫列與NVARCHAR參數進行比較時會發生這種情況:由於VARCHAR!= NVARCHAR,SQL Server無法進行直接比較,所以在比較之前將表列中的VARCHAR數據提升爲NVARCHAR。結果是一個完整的索引掃描,而不是索引查找,這可能會對大型表格進行性能測試。

我會看看執行計劃,看看你是否有索引查找,如果你這樣做,看看是否有任何隱式轉換的數據庫列發生在他們身後。

1

是否在WHERE子句(ListingMiscellaneousCode,MiscellaneousCodeID,DwellingCodeID,AreaCodeID?)中的任何有用列上都有索引?您是否考慮過爲參數列表傳遞單個字符串,而不是具有100多個單獨參數? 。相反的,但在這種情況下,我認爲這可能是合理的首先,我將創建一個數字表,500行可能已經足夠:

SET NOCOUNT ON; 
DECLARE @UpperLimit INT; 
SET @UpperLimit = 500; 

    WITH n AS 
    (
     SELECT 
      x = ROW_NUMBER() OVER 
      (ORDER BY s1.[object_id]) 
     FROM  [master].sys.columns AS s1 
     CROSS JOIN [master].sys.columns AS s2 
    ) 
    SELECT [Number] = x 
     INTO dbo.Numbers 
     FROM n 
     WHERE x BETWEEN 1 AND @UpperLimit; 
    GO 
    CREATE UNIQUE CLUSTERED INDEX n ON dbo.Numbers([Number]); 
    GO 

現在創建一個可解析字符串列表的功能:

現在您的查詢可以說:

DECLARE @MiscCodeIDs VARCHAR(MAX), @AreaCodeIDs VARCHAR(MAX); 
SELECT @MiscCodeIDs = '1,2,3,4,5...', @AreaCodeIDs = '6,7,8,9,10...'; 

SELECT <obnoxiously large column list> 
FROM 
... 
INNER JOIN dbo.AreaCodes AS t1 
ON ... 
INNER JOIN dbo.SplitInts(@AreaCodeIDs, N',') AS acs 
ON t1.AreaCodeID = acs.[Value] 
... 

AND 
(
    EXISTS 
    (
       SELECT 1 
      FROM [dbo].[ListingMiscellaneousCode] AS [t8] 
      INNER JOIN dbo.SplitInts(@MiscCodeIDs, N',') AS m 
      ON m.[Value] = t8.MiscellaneousCodeID 
      AND ([t8].[ListingID] = [t0].[ID]) 
    ) 
) 
... 

我假設這些ID是INT。如果它們是字符串,只需取出函數中的CONVERT(INT)(如果需要支持Unicode,則可能需要使用NVARCHAR)。

0

首先,我認爲你有一個錯誤在那裏... SELECT TOP 100將拉回一個隨機100,然後是ORDER BY [t9]。[DateCreated] DESC會對它們進行排序。這不會給你創建的最後100個。

你實際上不需要返回59列嗎?限制這個。

我覺得

([t0].[AreaCodeID] IN (@... 

應該

[t1].[ID] IN (@... 

而且應該對[DBO]唯一索引。[AREACODE] .ID

鑑於指標執行一個更好BETWEEN運行,而不是所有的值拼寫出來,我也會看到,如果我可以將100個值合併爲更多的值: [t1]。[ID] BETWEEN @ p1和@ p2和[t1]。[ID] in (@ p3 ....這可能是您的部分編碼。

但我真的會看100個區域代碼來自哪裏....你有AreaCodeGroup的概念,但它看起來並不像它正在使用。

+0

嗨JBrooks, 你是對的錯誤。感謝那。 我只是包含了完整的linq查詢以便於使用。即使僅選擇第一個表的ID,也會由於過濾器而導致長查詢。我已經提供了最終解決以下性能的三件事。 謝謝。 – Scott 2009-10-27 21:52:43