【问题标题】:Change in query plan and execution time with TOP and ESCAPE使用 TOP 和 ESCAPE 更改查询计划和执行时间
【发布时间】:2011-09-14 02:05:06
【问题描述】:

其中一个查询(如下所示)需要 90 多秒才能执行。它从一个相当大的表 LogMessage 中返回约 500 行。如果从查询中删除ESCAPE N'~',它将在几秒钟内执行。同样,如果TOP (1000) 被删除,它会在几秒钟内执行。查询计划在第一种情况下显示Key Lookup (Clustered) PK_LogMessage, Index Scan (NonClustered) IX_LogMessage and Nested Loops (Inner Join)。删除子句 ESCAPE N'~'TOP (1000) 后,查询计划会更改并显示 Clustered Index Scan (Clustered) PK_LogMessage。虽然我们正在考虑添加更多索引(可能在 ApplicationName 上),但我们想了解当前的情况。

查询是从Entity Framework 生成的,以防您想知道为什么要这样写。此外,实际查询更复杂,但这是表现出相同行为的最短版本。

查询:

SELECT TOP (1000) 
    [Project1].[MessageID] AS [MessageID], 
    [Project1].[TimeGenerated] AS [TimeGenerated], 
    [Project1].[SystemName] AS [SystemName], 
    [Project1].[ApplicationName] AS [ApplicationName]
FROM
    (
        SELECT
            [Project1].[MessageID] AS [MessageID],
            [Project1].[TimeGenerated] AS [TimeGenerated],
            [Project1].[SystemName] AS [SystemName],
            [Project1].[ApplicationName] AS [ApplicationName]
        FROM
        (
            SELECT 
                [Extent1].[MessageID] AS [MessageID], 
                [Extent1].[TimeGenerated] AS [TimeGenerated], 
                [Extent1].[SystemName] AS [SystemName], 
                [Extent1].[ApplicationName] AS [ApplicationName]
            FROM
                [dbo].[LogMessage] AS [Extent1]
            INNER JOIN
                [dbo].[LogMessageCategory] AS [Extent2]
            ON
                [Extent1].[CategoryID] = [Extent2].[CategoryID]
            WHERE
                ([Extent1].[ApplicationName] LIKE N'%tier%' ESCAPE N'~')
        )  AS [Project1]
    )  AS [Project1]
ORDER BY
    [Project1].[TimeGenerated] DESC

表日志消息:

CREATE TABLE [dbo].[LogMessage](
    [MessageID] [int] IDENTITY(1000001,1) NOT NULL,
    [TimeGenerated] [datetime] NOT NULL,
    [SystemName] [nvarchar](256) NOT NULL,
    [ApplicationName] [nvarchar](512) NOT NULL,
        [CategoryID] [int] NOT NULL,
 CONSTRAINT [PK_LogMessage] PRIMARY KEY CLUSTERED 
(
    [MessageID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF,
    ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON, FILLFACTOR = 90) ON [PRIMARY]
) ON [PRIMARY]

ALTER TABLE [dbo].[LogMessage]  WITH CHECK ADD CONSTRAINT [FK_LogMessage_LogMessageCategory] FOREIGN KEY([CategoryID])
    REFERENCES [dbo].[LogMessageCategory] ([CategoryID])

ALTER TABLE [dbo].[LogMessage] CHECK CONSTRAINT [FK_LogMessage_LogMessageCategory]

ALTER TABLE [dbo].[LogMessage] ADD  DEFAULT ((100)) FOR [CategoryID]

CREATE NONCLUSTERED INDEX [IX_LogMessage] ON [dbo].[LogMessage] 
(
    [TimeGenerated] DESC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF,
    IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON,
    ALLOW_PAGE_LOCKS  = ON, FILLFACTOR = 90) ON [PRIMARY]

表LogMessageCategory:

CREATE TABLE [dbo].[LogMessageCategory](
    [CategoryID] [int] NOT NULL,
    [Name] [nvarchar](128) NOT NULL,
    [Description] [nvarchar](256) NULL,
 CONSTRAINT [PK_LogMessageCategory] PRIMARY KEY CLUSTERED 
(
    [CategoryID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

查询计划 1(需要 90 多秒)

查询计划 2(大约需要 3 秒)

【问题讨论】:

  • 你能发布执行计划 1 和 2 的确切代码吗?该计划似乎与发布的查询不匹配。该查询与 LogMessageCategory 表有一个连接,但在执行计划中没有表示(应该是一个 INNER JOIN)。 LogMessage 表也没有 CategoryID。
  • 我也对 ESCAPE 的使用感到困惑。通常,ESCAPE 用于使通配符成为搜索的一部分。例如,您可以使用 LIKE '%50~%%' ESCAPE '~' 来搜索所有包含“50%”的字符串。但是您的 LIKE 语句不包含 '~'
  • @8kb,这是SQLMS中显示的查询计划。
  • @amit_g:你确定吗?例如,查询计划 1 扫描非聚集索引 IX_LogMessage,然后针对 LogMessage 中的聚集索引进行书签查找。那是执行计划中的内连接。但是您发布的查询也加入了 LogMessageCategory 表。此联接未在执行计划中表示。我在尝试在我的系统上重现查询时注意到了这一点。如果您将 INNER JOIN 取出到 LogMessageCategory,则查询计划与您所拥有的相匹配。
  • @David,我想了解这里发生了什么。此查询由 EF 生成,比我发布的查询复杂得多,因此我们无能为力。真正的问题是它为什么会发生。解决方法不是问题。我们会将查询移动到存储过程并调用它,而不是让 EF 生成它。

标签: sql sql-server performance sql-server-2008-r2 sql-execution-plan


【解决方案1】:

对我来说,这看起来像是一个直接的参数嗅探问题。

如您所愿,由TimeGenerated 排序的TOP 1000 SQL Server 可以向下扫描TimeGenerated 上的索引并对基表进行查找以评估ApplicationName 上的谓词并在找到第1,000 行时停止或者它可以进行聚集索引扫描,找到与ApplicationName 谓词匹配的所有行,然后对这些进行TOP N 排序。

SQL Server 维护有关字符串列的统计信息。如果第一个计划认为许多行最终将匹配ApplicationName 谓词,则更有可能选择第一个计划,但是该计划并不真正适合作为参数化查询重用,因为在以下情况下它可能是灾难性的低效几行匹配。如果少于 1,000 个匹配,它肯定需要执行与表中的行一样多的键查找。

通过对此的测试,我找不到任何添加或删除冗余ESCAPE 改变SQL Server 的基数估计的情况。当然,更改参数化查询的文本意味着无法使用原始计划,它需要编译一个不同的计划,这可能更适合当前考虑的特定值。

【讨论】:

  • 感谢您的回复。此查询未使用,现在表上还有一些索引,因此我无法重现完全相同的计划,但删除/添加“ESCAPE N'~'”的行为仍然会改变计划(类似于如上所示)和查询执行时间(同样不完全是上面发布的内容,但相对来说是相似的)。
【解决方案2】:

为什么所有这些嵌套查询? 下面的代码做同样的工作

        SELECT TOP(1000)
            [Extent1].[MessageID] AS [MessageID], 
            [Extent1].[TimeGenerated] AS [TimeGenerated], 
            [Extent1].[SystemName] AS [SystemName], 
            [Extent1].[ApplicationName] AS [ApplicationName]
        FROM
            [dbo].[LogMessage] AS [Extent1]
        INNER JOIN
            [dbo].[LogMessageCategory] AS [Extent2]
        ON
            [Extent1].[CategoryID] = [Extent2].[CategoryID]
        WHERE
            ([Extent1].[ApplicationName] LIKE N'%tier%' ESCAPE N'~')
        ORDER BY [Extent1].[TimeGenerated] DESC

我也同意 ESCAPE N'~' 可以省略,因为我找不到使用它的理由。

【讨论】:

  • 嵌套查询是实体框架生成的,你可能不同意,但这只是实体框架滚动的方式。
  • 这不是这个问题的重点。真正的问题是,为什么 SQL Server 会使用这两个子句进行这种计划更改并产生性能。
  • @Kane -- 正如@niktrs 指出的那样,三重嵌套的 sql 完全没有必要。如果您说这就是实体框架的运行方式……那是对您自己的问题的可悲答案-您的问题是实体框架,而不是SQL-Server。我建议重新标记您的问题,这样您就可以(希望)获得一些关于如何调整 EF 以生成更好的 sql 的更好答案——最重要的是,摆脱嵌套。
  • @kuru kuru pa - OP 中显示的执行计划表明 SQL Server 在忽略冗余嵌套方面做得很好。
【解决方案3】:

首先,我将简化@niktrs 指定的查询。尽管执行计划似乎忽略了子查询,但它使其更加人性化,因此更易于操作和理解。

然后,您有一个 INNER JOIN,在我看来它可能会消失。 INNER JOIN LogMessage 到 LogMessageCategory 是否“真正”需要?您可以使用以下方法进行快速检查..

SELECT LM.CategoryID AS FromLogMessage, LMC.CategoryID AS FromLogMessageCategory
FROM dbo.LogMessage AS LM
     FULL OUTER JOIN dbo.LogMessageCategory AS LMC ON LMC.CategoryID = LM.CategoryID
WHERE LM.CategoryID IS NULL OR LMC.CategoryID IS NULL

【讨论】:

    【解决方案4】:

    如果你这样做,它会如何运行?

    Select * 
    FROM
     (your whole scary framework query with the escape N) a
    LIMIT 1000 
    (or the mssql alternative if mssql does not support the correct syntax -- )
    

    因为如果成功了 .. 您有机会继续使用该框架并从非常糟糕的 sql 中获得不错的性能(例如...这意味着您创建了完整的 rs 然后只从中选择 1k .. .).

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-10-14
      • 1970-01-01
      • 1970-01-01
      • 2013-02-05
      • 2014-04-15
      • 2012-02-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多