Изменение в плане запроса и времени выполнения с помощью TOP и ESCAPE - PullRequest
6 голосов
/ 13 июня 2011

Один из запросов (приведенный ниже) занимает более 90 секунд. Возвращает ~ 500 строк из довольно большой таблицы LogMessage. Если 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 секунд)

Query Plan 1 (takes 90+ seconds)

План запроса 2 (занимает ~ 3 секунды)

Query Plan 2 (takes ~3 seconds)

Ответы [ 4 ]

2 голосов
/ 23 сентября 2011

Это выглядит как прямая проблема с прослушиванием параметров.

По вашему желанию TOP 1000, заказанный TimeGenerated, SQL Server может либо просмотреть индекс на TimeGenerated и выполнить поиск по базовой таблице, чтобы оценить предикат на ApplicationName, и остановиться, когда будет найдена строка 1000 или он может выполнить сканирование кластерного индекса, найти все строки, соответствующие предикату ApplicationName, а затем выполнить сортировку TOP N.

SQL Server ведет статистику по строковым столбцам. Первый план будет с большей вероятностью выбран, если он полагает, что многие строки в конечном итоге будут соответствовать предикату ApplicationName, однако этот план не очень подходит для повторного использования в качестве параметризованного запроса, поскольку он может быть катастрофически неэффективным в случае, если несколько строк совпадают. Если совпадение меньше 1000, ему, безусловно, потребуется выполнить столько операций поиска ключей, сколько строк в таблице.

В результате тестирования я не смог найти ситуации, в которой добавление или удаление избыточного ESCAPE изменило оценки мощности SQL Server. Конечно, изменение текста запроса с параметризацией означает, что оригинальный план не может быть использован, однако, и ему нужно скомпилировать другой план, который, вероятно, будет более подходящим для конкретного значения в настоящее время.

1 голос
/ 17 июня 2011

Почему все эти вложенные запросы ??Приведенный ниже код выполняет ту же работу

        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 '~' может быть опущен, так как я не могу найти причины для его использования.

0 голосов
/ 22 декабря 2011

Для начала я бы упростил запрос, указанный @niktrs. Несмотря на то, что план выполнения, похоже, игнорирует подзапросы, он делает его более дружественным к человеку и, следовательно, его легче манипулировать и понимать.

Тогда у вас есть ВНУТРЕННЕЕ СОЕДИНЕНИЕ, которое, как мне кажется, может уйти. Есть ли «реальная» потребность для ВНУТРЕННЕГО СОЕДИНЕНИЯ 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
0 голосов
/ 22 сентября 2011

Как это работает, если вы делаете это?

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 из него ...).

...