SQLServer查询优化问题

时间:2009-10-26 21:59:34

标签: sql-server-2005 linq-to-sql optimization

我有一个使用Linq2SQL“尖刺”的项目,现在遇到了一些主要的查询性能问题。去图。

Linq在简单的查询和命令方案中实际上运行良好,但有一些过滤器需要重写为Sprocs。

我想知道是否有人可以给我一些高级指针来节省时间来优化由Linq生成的怪物查询。

在我的脑海中,我认为将所有“In(@ 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 Where子句,它也不是那么慢,(大约1秒),但问题是,我还必须根据类似的样式查询返回当前数据的各种计数。整个过程花费的时间超过5秒,原因是多个查询条件较差的Where子句。

我不明白的另一件事是将查询的页面大小,即“...选择TOP(100)...”更改为更高的数字,例如“...选择TOP(5000)。 ..“不会减慢查询速度。这对我来说很奇怪,更多的证据我认为这个问题可以通过修改的sql来解决。

您还会注意到一个Where子句(对于isacodeid)正在查询近100个参数。这是设计的。现在我可以在父表中放入一个hack,以牺牲一些去规范化来减少这个,但我希望首先有一个纯sql修复,它可以让我有效地加入到具有100s params的临时表中

感谢您的帮助。

3 个答案:

答案 0 :(得分: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

现在创建一个可以解析字符串列表的函数:

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

现在您的查询可以说:

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)。

答案 1 :(得分:0)

那里没有什么东西看起来很糟糕。看着它,看起来很可怕但是在做了一些重新格式化并删除了无关的括号之后,它并没有那么糟糕。我从来都不喜欢嵌套的JOIN,我会考虑清理它,但这是个人偏好:我不认为它会对性能做任何事情。

所以...如果取出WHERE子句会加快速度,我会查看索引和隐式转换。第一个是不言自明的;第二个我以前被烧过。两者都可以通过分析执行计划来检测。

实际上,当SQL Server转换数据库列中的数据而不是转换与数据库列进行比较的参数时,隐式转换是错误的。将VARCHAR数据库列与NVARCHAR参数进行比较时会发生这种情况:SQL Server无法进行直接比较,因为VARCHAR!= NVARCHAR,因此在进行比较之前,它会将表列中的VARCHAR数据提升为NVARCHAR。结果是完整的索引扫描而不是索引搜索,它可以屠杀大型表的性能。

我会查看执行计划,看看你是否有任何索引搜索,如果你这样做,请查看是否有任何隐含的数据库列转换发生在它们后面。

答案 2 :(得分: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的概念,但它看起来并没有被使用。