【问题标题】:SQLServer Query optimization questionSQLServer 查询优化问题
【发布时间】:2009-10-27 07:28:06
【问题描述】:

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

Linq 实际上在简单查询和命令场景中运行良好,但有几个过滤器密集查询需要重写为 Sproc。

我想知道是否有人可以给我一些高级指针,以节省优化 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)。 ..” 不会减慢查询速度。这对我来说很奇怪,并且更多地证明我认为这个问题可以通过修改后的 sql 来解决。

您还会注意到一个特别的 Where 子句(用于 areacodeid)正在查询近 100 个参数。这是设计使然。现在我可以在父表中加入 hack 以减少这种情况,但会以一些非规范化为代价,但我希望首先有一个纯 sql 修复程序,它可以让我有效地加入一个具有 100 个参数的临时表.

感谢您的帮助。

【问题讨论】:

  • 如果您将底层架构(表和索引)与原始 Linq 查询(或多个查询)一起包含,我相信它可以修复。看起来没有什么异常,所以如果它有性能问题,它可以在不诉诸存储过程的情况下修复。
  • ...哦,忘了加...然后优化。盲目优化只是浪费时间。如果您可以包含 [a link to] 查询的执行计划和 I/O 统计信息,那将同样有帮助。

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


【解决方案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)。

【讨论】:

    【解决方案2】:

    那里没有任何看起来“糟糕”的东西。看着它,这似乎很可怕,但是在重新格式化并删除了多余的括号之后,它并没有那么糟糕。我从不喜欢嵌套的 JOIN,我会考虑清理它,但这是个人喜好:我认为它不会对性能有任何影响。

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

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

    我会查看执行计划,看看您是否有任何索引搜索,如果有,请查看它们背后是否发生了数据库列的任何隐式转换。

    【讨论】:

      【解决方案3】:

      首先,我认为你有一个错误...... 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 and @p2 and [t1].[ID] in (@p3....这可能是你的一些编码。

      但我真的会看看 100 个区号的来源......你有 AreaCodeGroup 的概念,但它看起来并没有被使用。

      【讨论】:

      • 您好 JBrooks,您对错误的看法是正确的。感谢那。为了方便起见,我只是包含了完整的 linq 查询。由于过滤器,即使只选择第一个表的 ID 也会导致长查询。我提供了最终修复以下性能的 3 件事。谢谢。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-10-31
      相关资源
      最近更新 更多