2009-10-26 2 views
0

저는 Linq2SQL을 사용하여 "급증한"프로젝트를 가지고 있으며 이제 주요 쿼리 성능 문제가 발생합니다. 그림을 이동.SQLServer 쿼리 최적화 질문

Linq 실제로 간단한 쿼리 및 명령 시나리오에서 꽤 잘 작동하지만 Sprocs로 다시 작성해야하는 몇 가지 필터 강렬한 쿼리가 있습니다.

누군가 Linq에 의해 생성 된 괴물 쿼리를 최적화하는 데 시간을 절약 할 수있는 상위 수준 포인터를 줄 수 있는지 궁금합니다.

내 머리 꼭대기에서, "In (@ p1, @ p2)"대체는 내부 조인을 사용하는 where 절이 좋은 시작이라고 생각합니다.

모든 외래 키 및 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) 선택 ..."과 같이 높은 숫자로 변경하는 것입니다 ... "TOP (5000) ... "는 쿼리를 AT 전체에서 느려지지 않습니다. 이것은 나에게 이상한 일이며, 문제가 수정 된 SQL으로 고칠 수 있다고 생각하는 많은 증거입니다.

또한 특정 Where 절 (areacodeid)이 거의 100 개의 매개 변수를 쿼리하고 있음을 알 수 있습니다. 이는 의도적으로 설계된 동작입니다. 이제는 부모 테이블에서 해킹을하여 일부 비정규 화를 희생시키면서이를 줄이기 위해 사용할 수 있습니다.하지만 먼저 100 자의 params가있는 임시 테이블에 효율적으로 참여할 수있는 순수한 SQL 수정이 필요합니다. .

도움 주셔서 감사합니다.

+0

원본 Linq 쿼리 (또는 쿼리)와 함께 기본 스키마 (테이블 및 인덱스)를 포함 시키면 수정 될 수 있습니다. 특이한 것이 없기 때문에 성능에 문제가있는 경우 저장된 procs에 의지하지 않고 수정할 수 있습니다. – KristoferA

+0

... 아, 그리고 추가하는 걸 잊었습니다 ... 쿼리가 충분히 잘 수행되지 않으면 _find_ 먼저 성능 문제의 원인을 찾으십시오. 그런 다음 최적화하십시오. 시각 장애인에게 최적화하는 것은 시간 낭비입니다. 동일하게 도움이되는 쿼리에 대한 실행 계획 및 I/O 통계 [링크]를 포함 할 수있는 경우 – KristoferA

답변

0

거기에는 "나쁨"으로 보이는 것이 없습니다. 그것을 보면, 무서운 것 같지만, 일부를 다시 포맷하고 관계없는 괄호를 제거한 후에 그렇게 나쁘지는 않습니다. 나는 중첩 된 JOIN을 결코 좋아하지 않았고, 나는 그것을 정리하는 것을 보았습니다. 그러나 그것은 개인적인 취향이었습니다 : 나는 그것이 성능을 위해 무엇이든 할 것이라고 생각하지 않습니다.

그래서 ... WHERE 절을 추출하면 속도가 빨라지므로 인덱스와 암시 적 변환을 보게 될 것입니다. 첫 번째는 자명하다. 두 번째는 전에 탔습니다. 두 가지 모두 실행 계획을 분석하여 감지 할 수 있습니다.

사실 SQL Server가 데이터베이스 열과 비교할 매개 변수를 변환하는 대신 데이터베이스 열의 데이터를 변환 할 때 암시 적 변환이 잘못됩니다. 이것은 VARCHAR 데이터베이스 열이 NVARCHAR 매개 변수와 비교 될 때 발생할 수 있습니다. SQL Server는 VARCHAR! = NVARCHAR이므로 직선 비교를 수행 할 수 없기 때문에 비교를 수행하기 전에 테이블 열의 VARCHAR 데이터를 NVARCHAR로 승격합니다. 결과는 대형 테이블에 대한 성능을 저하시킬 수있는 인덱스 탐색보다는 전체 인덱스 스캔입니다.

나는 실행 계획을 살펴볼 것이다. 만약 당신이 어떤 인덱스를 가지고 있는지 알고 싶다면, 그 뒤에있는 데이터베이스 컬럼의 암시 적 변환이 있는지 살펴야한다.

1

WHERE 절 (ListMiscellaneousCode, MiscellaneousCodeID, DwellingCodeID, AreaCodeID)의 유용한 열에 인덱스가 있습니까? 100 개 이상의 개별 매개 변수 대신 하나의 문자열을 매개 변수 목록에 전달하는 것이 고려 되었습니까? .

CREATE FUNCTION dbo.SplitINTs 
(
    @List  VARCHAR(MAX), 
    @Delimiter NVARCHAR(10) 
) 
RETURNS TABLE 
AS 
    RETURN 
    (
     SELECT DISTINCT 
      [Value] = CONVERT(INT, LTRIM(RTRIM(
       SUBSTRING(@List, [Number], 
       CHARINDEX 
       (
        @Delimiter, @List + @Delimiter, [Number] 
       ) - [Number])))) 
     FROM 
      dbo.Numbers 
     WHERE 
      Number <= LEN(@List) 
      AND SUBSTRING 
      (
       @Delimiter + @List, [Number], LEN(@Delimiter) 
      ) = @Delimiter 
    ); 
GO 
:

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 

지금 문자열 목록을 구문 분석 할 수있는 기능을 만듭니다 반대하지만,이 경우 나는 그것이 정당화 될 수있다 생각합니다 우선은 숫자 테이블을 만들 것에 500 행은 아마 충분하다

이제 검색어는 다음과 같이 말할 수 있습니다 :

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)를 꺼내십시오 (유니 코드를 지원해야하는 경우를 대비하여 NVARCHAR을 사용할 수도 있습니다).

0

먼저, 당신은 거기에 버그가 있다고 생각합니다. SELECT TOP 100은 임의의 100을 가져오고 ORDER BY [t9]를 끌어 올 것입니다. [DateCreated] DESC가이를 정렬합니다. 이것은 마지막으로 생성 된 100을 제공하지 않습니다.

실제로 59 개의 열을 반환 할 필요가 없습니다. 이것을 제한하십시오.

나는

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

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

해야하며, [DBO]에 고유 인덱스가 있어야한다고 생각합니다. AreaCode] .ID

을 감안할 때 인덱스가 더 잘 수행하는 것이 그 사이에 철자가 아닌 모든 값이 나왔습니다. 100 값을 더 이상 무언가로 붕괴시킬 수 있는지 알았습니다. [t1]. [ID] BTP는 @ p1과 @ p2 및 [t1]. [ID] in (@ p3 .... 그건 너의 일부 코딩일지도 모른다.

하지만 저는 100 개의 지역 코드가 어디에서 왔는지보실 수 있습니다 ... 당신은 AreaCodeGroup의 개념을 가지고 있지만 실제로 사용되고있는 것처럼 보이지는 않습니다.

+0

안녕하세요. JBrooks, 당신이 맞습니다. 고마워. 난 그냥 쉽게 linq 전체 쿼리를 포함 시켰습니다. 심지어 첫 번째 테이블의 ID 만 선택하면 필터로 인해 긴 쿼리가 생성됩니다. 나는 궁극적으로 아래의 성능을 수정 한 3 가지를 제공했습니다. 감사합니다. – Scott