其中一个查询(如下所示)执行时间超过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
表LogMessage:
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秒)
答案 0 :(得分:2)
这看起来像是一个直接的参数嗅探问题。
如您所愿TOP 1000
TimeGenerated
命令SQL Server可以扫描TimeGenerated
上的索引并对基表进行查找以评估ApplicationName
上的谓词并在找到第1000行时停止,或者可以执行聚簇索引扫描,找到与ApplicationName
谓词匹配的所有行,然后执行TOP N
种类型的行。
SQL Server维护有关字符串列的统计信息。如果第一个计划认为许多行最终会匹配ApplicationName
谓词,那么第一个计划就更有可能被选择,但是这个计划并不适合作为参数化查询重新使用,因为它可能在灾难性方面效率低下。几行匹配的事件。如果小于1,000匹配,则肯定需要执行与表中的行一样多的键查找。
从测试结束开始,我无法找到添加或删除冗余ESCAPE
更改的SQL Server基数估计值的任何情况。当然,更改参数化查询的文本意味着原始计划无法使用,但需要编译一个不同的计划,这可能更适合当前考虑的特定值。
答案 1 :(得分:1)
为什么所有这些嵌套查询? 下面的代码执行相同的工作
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'〜'可以省略,因为我没有理由使用它。
答案 2 :(得分:0)
首先,我会简化@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
答案 3 :(得分:-1)
如果你这样做,它是如何运作的?
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 .. 。)。