SQL Server: запрос быстрый, но медленный от процедуры

запрос выполняется быстро:

DECLARE @SessionGUID uniqueidentifier
SET @SessionGUID = 'BCBA333C-B6A1-4155-9833-C495F22EA908'

SELECT *
FROM Report_Opener
WHERE SessionGUID = @SessionGUID
ORDER BY CurrencyTypeOrder, Rank

стоимость поддерева: 0.502

но размещение одного и того же SQL в хранимой процедуре выполняется медленно и с совершенно другим планом выполнения

CREATE PROCEDURE dbo.ViewOpener @SessionGUID uniqueidentifier AS
SELECT *
FROM Report_Opener
WHERE SessionGUID = @SessionGUID
ORDER BY CurrencyTypeOrder, Rank

EXECUTE ViewOpener @SessionGUID

стоимость поддерева: 19.2

я

sp_recompile ViewOpener

и он по-прежнему работает одинаково (плохо), и я также изменил сохраненный процедура для

CREATE PROCEDURE dbo.ViewOpener @SessionGUID uniqueidentifier AS
SELECT *, 'recompile please'
FROM Report_Opener
WHERE SessionGUID = @SessionGUID
ORDER BY CurrencyTypeOrder, Rank

и обратно, пытаясь действительно обмануть его в перекомпиляцию.

я отбросил и воссоздал хранимую процедуру, чтобы заставить ее генерировать новый план.

я пытался принудительно перекомпилировать,и предотвратить обнюхивание параметров, используя переменную-приманку:

CREATE PROCEDURE dbo.ViewOpener @SessionGUID uniqueidentifier AS

DECLARE @SessionGUIDbitch uniqueidentifier
SET @SessionGUIDbitch = @SessionGUID

SELECT *
FROM Report_Opener
WHERE SessionGUID = @SessionGUIDbitch
ORDER BY CurrencyTypeOrder, Rank

я также попытался определить хранимую процедуру WITH RECOMPILE:

CREATE PROCEDURE dbo.ViewOpener @SessionGUID uniqueidentifier 
WITH RECOMPILE
AS
SELECT *
FROM Report_Opener
WHERE SessionGUID = @SessionGUID
ORDER BY CurrencyTypeOrder, Rank

так что план никогда не кэшируется, и я попытался заставить перекомпилировать на выполнить:

EXECUTE ViewOpener @SessionGUID WITH RECOMPILE

что не помогло.

я попытался преобразовать процедуру в динамический SQL:

CREATE PROCEDURE dbo.ViewOpener @SessionGUID uniqueidentifier 
WITH RECOMPILE AS
DECLARE @SQLString NVARCHAR(500)

SET @SQLString = N'SELECT *
   FROM Report_OpenerTest
   WHERE SessionGUID = @SessionGUID
   ORDER BY CurrencyTypeOrder, Rank'

EXECUTE sp_executesql @SQLString,
N'@SessionGUID uniqueidentifier',
@SessionGUID

что не помогло.

предприятия "Report_Opener " - это представление, которое не индексируется. Представление ссылается только на базовые таблицы. Ни одна таблица не содержит вычисляемых столбцов, индексированных или иных.

для черта я попытался создать представление с

SET ANSI_NULLS ON
SET QUOTED_IDENTIFER ON

это не решило проблему.

как это

  • запрос выполняется быстро
  • перемещение запроса в представление и выбор из представления выполняется быстро
  • выбор из представления из хранимой процедуры 40x медленнее?

я попытался переместить определение представления непосредственно в хранимую процедуру (нарушая 3 бизнес-правила и нарушая важную инкапсуляцию), и это делает его только о 6X медленнее.

почему версия хранимой процедуры настолько медленная? Что может быть причиной того, что SQL Server работает ad-hoc SQL быстрее, чем другой вид ad-hoc SQL?

я не хочу

  • встроить SQL в код
  • изменить код вообще

    Microsoft SQL Server  2000 - 8.00.2050 (Intel X86)
    Mar  7 2008 21:29:56
    Copyright (c) 1988-2003 Microsoft Corporation
    Standard Edition on Windows NT 5.2 (Build 3790: Service Pack 2)
    

но что может объяснить, почему SQL Server не может работать так же быстро, как SQL Sever, выполняющий запрос, если не нюхает параметр.


моя следующая попытка будет иметь StoredProcedureA звоните StoredProcedureB вызов StoredProcedureC вызов StoredProcedureD для запроса представления.

и в противном случае вызовите хранимую процедуру, вызовите UDF, вызовите UDF, вызовите хранимую процедуру, вызовите UDF для запроса представления.


чтобы подвести итог, следующий запуск быстро из QA, но медленно, когда он помещен в хранимую процедуру:

в оригинал:

--Runs fine outside of a stored procedure
SELECT *
FROM Report_OpenerTest
WHERE SessionGUID = @SessionGUID
ORDER BY CurrencyTypeOrder, Rank

sp_executesql:

--Runs fine outside of a stored procedure
DECLARE @SQLString NVARCHAR(500)
SET @SQLString = N'SELECT *
FROM Report_OpenerTest
WHERE SessionGUID = @SessionGUID
ORDER BY CurrencyTypeOrder, Rank'

EXECUTE sp_executesql @SQLString,
        N'@SessionGUID uniqueidentifier',
        @SessionGUID

EXEC(@sql):

--Runs fine outside of a stored procedure
DECLARE @sql NVARCHAR(500)
SET @sql = N'SELECT *
FROM Report_OpenerTest
WHERE SessionGUID = '''+CAST(@SessionGUID AS varchar(50))+'''
ORDER BY CurrencyTypeOrder, Rank'

EXEC(@sql)

Выполнение Планов

на хороший план:

      |--Sort(ORDER BY:([Expr1020] ASC, [Currencies].[Rank] ASC))
           |--Compute Scalar(DEFINE:([Expr1020]=If ([Currencies].[CurrencyType]='ctCanadianCash') then 1 else If ([Currencies].[CurrencyType]='ctMiscellaneous') then 2 else If ([Currencies].[CurrencyType]='ctTokens') then 3 else If ([Currencies].[CurrencyType]
                |--Nested Loops(Left Outer Join, OUTER REFERENCES:([Openers].[OpenerGUID]))
                     |--Filter(WHERE:((([Currencies].[IsActive]<>0 AND [Currencies].[OnOpener]<>0) AND ((((((([Currencies].[CurrencyType]='ctUSCoin' OR [Currencies].[CurrencyType]='ctMiscellaneousUS') OR [Currencies].[CurrencyType]='ctUSCash') OR [Currencies].
                     |    |--Nested Loops(Left Outer Join, OUTER REFERENCES:([Currencies].[CurrencyGUID], [Openers].[OpenerGUID]) WITH PREFETCH)
                     |         |--Nested Loops(Left Outer Join)
                     |         |    |--Bookmark Lookup(BOOKMARK:([Bmk1016]), OBJECT:([GrobManagementSystemLive].[dbo].[Windows]))
                     |         |    |    |--Nested Loops(Inner Join, OUTER REFERENCES:([Openers].[WindowGUID]))
                     |         |    |         |--Bookmark Lookup(BOOKMARK:([Bmk1014]), OBJECT:([GrobManagementSystemLive].[dbo].[Openers]))
                     |         |    |         |    |--Index Seek(OBJECT:([GrobManagementSystemLive].[dbo].[Openers].[IX_Openers_SessionGUID]), SEEK:([Openers].[SessionGUID]=[@SessionGUID]) ORDERED FORWARD)
                     |         |    |         |--Index Seek(OBJECT:([GrobManagementSystemLive].[dbo].[Windows].[IX_Windows]), SEEK:([Windows].[WindowGUID]=[Openers].[WindowGUID]) ORDERED FORWARD)
                     |         |    |--Clustered Index Scan(OBJECT:([GrobManagementSystemLive].[dbo].[Currencies].[IX_Currencies_CurrencyType]))
                     |         |--Clustered Index Seek(OBJECT:([GrobManagementSystemLive].[dbo].[OpenerDetails].[IX_OpenerDetails_OpenerGUIDCurrencyGUID]), SEEK:([OpenerDetails].[OpenerGUID]=[Openers].[OpenerGUID] AND [OpenerDetails].[CurrencyGUID]=[Currenc
                     |--Hash Match(Cache, HASH:([Openers].[OpenerGUID]), RESIDUAL:([Openers].[OpenerGUID]=[Openers].[OpenerGUID]))
                          |--Stream Aggregate(DEFINE:([Expr1006]=SUM(If (((([Currencies].[CurrencyType]='ctMiscellaneous' OR [Currencies].[CurrencyType]='ctTokens') OR [Currencies].[CurrencyType]='ctChips') OR [Currencies].[CurrencyType]='ctCanadianCoin') OR [
                               |--Nested Loops(Inner Join, OUTER REFERENCES:([OpenerDetails].[CurrencyGUID]) WITH PREFETCH)
                                    |--Nested Loops(Inner Join)
                                    |    |--Index Seek(OBJECT:([GrobManagementSystemLive].[dbo].[Openers].[IX_Openers_OneOpenerPerSession]), SEEK:([Openers].[OpenerGUID]=[Openers].[OpenerGUID]) ORDERED FORWARD)
                                    |    |--Clustered Index Seek(OBJECT:([GrobManagementSystemLive].[dbo].[OpenerDetails].[IX_OpenerDetails_OpenerGUIDCurrencyGUID]), SEEK:([OpenerDetails].[OpenerGUID]=[Openers].[OpenerGUID]) ORDERED FORWARD)
                                    |--Index Seek(OBJECT:([GrobManagementSystemLive].[dbo].[Currencies].[PK_Currencies_CurrencyGUID]), SEEK:([Currencies].[CurrencyGUID]=[OpenerDetails].[CurrencyGUID]) ORDERED FORWARD)

на плохо план

       |--Sort(ORDER BY:([Expr1020] ASC, [Currencies].[Rank] ASC))
            |--Compute Scalar(DEFINE:([Expr1020]=If ([Currencies].[CurrencyType]='ctCanadianCash') then 1 else If ([Currencies].[CurrencyType]='ctMiscellaneous') then 2 else If ([Currencies].[CurrencyType]='ctTokens') then 3 else If ([Currencies].[Currency
                 |--Nested Loops(Left Outer Join, OUTER REFERENCES:([Openers].[OpenerGUID]))
                      |--Filter(WHERE:((([Currencies].[IsActive]<>0 AND [Currencies].[OnOpener]<>0) AND ((((((([Currencies].[CurrencyType]='ctUSCoin' OR [Currencies].[CurrencyType]='ctMiscellaneousUS') OR [Currencies].[CurrencyType]='ctUSCash') OR [Currenc
                      |    |--Nested Loops(Left Outer Join, OUTER REFERENCES:([Currencies].[CurrencyGUID], [Openers].[OpenerGUID]) WITH PREFETCH)
                      |         |--Filter(WHERE:([Openers].[SessionGUID]=[@SessionGUID]))
                      |         |    |--Concatenation
                      |         |         |--Nested Loops(Left Outer Join)
                      |         |         |    |--Table Spool
                      |         |         |    |    |--Hash Match(Inner Join, HASH:([Windows].[WindowGUID])=([Openers].[WindowGUID]), RESIDUAL:([Windows].[WindowGUID]=[Openers].[WindowGUID]))
                      |         |         |    |         |--Clustered Index Scan(OBJECT:([GrobManagementSystemLive].[dbo].[Windows].[IX_Windows_CageGUID]))
                      |         |         |    |         |--Table Scan(OBJECT:([GrobManagementSystemLive].[dbo].[Openers]))
                      |         |         |    |--Table Spool
                      |         |         |         |--Clustered Index Scan(OBJECT:([GrobManagementSystemLive].[dbo].[Currencies].[IX_Currencies_CurrencyType]))
                      |         |         |--Compute Scalar(DEFINE:([Openers].[OpenerGUID]=NULL, [Openers].[SessionGUID]=NULL, [Windows].[UseChipDenominations]=NULL))
                      |         |              |--Nested Loops(Left Anti Semi Join)
                      |         |                   |--Clustered Index Scan(OBJECT:([GrobManagementSystemLive].[dbo].[Currencies].[IX_Currencies_CurrencyType]))
                      |         |                   |--Row Count Spool
                      |         |                        |--Table Spool
                      |         |--Clustered Index Seek(OBJECT:([GrobManagementSystemLive].[dbo].[OpenerDetails].[IX_OpenerDetails_OpenerGUIDCurrencyGUID]), SEEK:([OpenerDetails].[OpenerGUID]=[Openers].[OpenerGUID] AND [OpenerDetails].[CurrencyGUID]=[Cu
                      |--Hash Match(Cache, HASH:([Openers].[OpenerGUID]), RESIDUAL:([Openers].[OpenerGUID]=[Openers].[OpenerGUID]))
                           |--Stream Aggregate(DEFINE:([Expr1006]=SUM([partialagg1034]), [Expr1007]=SUM([partialagg1035]), [Expr1008]=SUM([partialagg1036]), [Expr1009]=SUM([partialagg1037]), [Expr1010]=SUM([partialagg1038]), [Expr1011]=SUM([partialagg1039]
                                |--Nested Loops(Inner Join)
                                     |--Stream Aggregate(DEFINE:([partialagg1034]=SUM(If (((([Currencies].[CurrencyType]='ctMiscellaneous' OR [Currencies].[CurrencyType]='ctTokens') OR [Currencies].[CurrencyType]='ctChips') OR [Currencies].[CurrencyType]='
                                     |    |--Nested Loops(Inner Join, OUTER REFERENCES:([OpenerDetails].[CurrencyGUID]) WITH PREFETCH)
                                     |         |--Clustered Index Seek(OBJECT:([GrobManagementSystemLive].[dbo].[OpenerDetails].[IX_OpenerDetails_OpenerGUIDCurrencyGUID]), SEEK:([OpenerDetails].[OpenerGUID]=[Openers].[OpenerGUID]) ORDERED FORWARD)
                                     |         |--Index Seek(OBJECT:([GrobManagementSystemLive].[dbo].[Currencies].[PK_Currencies_CurrencyGUID]), SEEK:([Currencies].[CurrencyGUID]=[OpenerDetails].[CurrencyGUID]) ORDERED FORWARD)
                                     |--Index Seek(OBJECT:([GrobManagementSystemLive].[dbo].[Openers].[IX_Openers_OneOpenerPerSession]), SEEK:([Openers].[OpenerGUID]=[Openers].[OpenerGUID]) ORDERED FORWARD)

плохой-один жаждет наматывать 6 миллионов строк; другой-нет.

Примечание: это не вопрос о настройке запроса. У меня есть запрос, который работает молниеносно. Я просто хочу, чтобы SQL Server быстро запускался из хранимой процедуры.

12 ответов


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

Я нашел другой ответ здесь "нюхать"параметр, Спасибо Omnibuzz. Сводится к использованию "локальных переменных" в запросах хранимых процедур, но прочитайте оригинал для большего понимания, это отличная запись. например,

медленная путь:

CREATE PROCEDURE GetOrderForCustomers(@CustID varchar(20))
AS
BEGIN
    SELECT * 
    FROM orders
    WHERE customerid = @CustID
END

быстрый способ:

CREATE PROCEDURE GetOrderForCustomersWithoutPS(@CustID varchar(20))
AS
BEGIN
    DECLARE @LocCustID varchar(20)
    SET @LocCustID = @CustID

    SELECT * 
    FROM orders
    WHERE customerid = @LocCustID
END

надеюсь, это поможет кому-то еще, делая это, уменьшило мое время выполнения с 5+ минут до 6-7 секунд.


я нашел проблему, вот скрипт медленной и быстрой версий хранимой процедуры:

dbo.ViewOpener__RenamedForCruachan__Slow.PRC

SET QUOTED_IDENTIFIER OFF 
GO
SET ANSI_NULLS OFF 
GO

CREATE PROCEDURE dbo.ViewOpener_RenamedForCruachan_Slow
    @SessionGUID uniqueidentifier
AS

SELECT *
FROM Report_Opener_RenamedForCruachan
WHERE SessionGUID = @SessionGUID
ORDER BY CurrencyTypeOrder, Rank
GO

SET QUOTED_IDENTIFIER OFF 
GO
SET ANSI_NULLS ON 
GO

dbo.ViewOpener__RenamedForCruachan__Fast.PRC

SET QUOTED_IDENTIFIER OFF 
GO
SET ANSI_NULLS ON 
GO

CREATE PROCEDURE dbo.ViewOpener_RenamedForCruachan_Fast
    @SessionGUID uniqueidentifier 
AS

SELECT *
FROM Report_Opener_RenamedForCruachan
WHERE SessionGUID = @SessionGUID
ORDER BY CurrencyTypeOrder, Rank
GO

SET QUOTED_IDENTIFIER OFF 
GO
SET ANSI_NULLS ON 
GO

если вы не заметили разницы, я не виню тебя. Разница заключается вовсе не в хранимой процедуре. Разница, которая превращает быстрый запрос стоимости 0.5 в тот, который делает нетерпеливую катушку из 6 миллионов строк:

медленно: SET ANSI_NULLS OFF

быстрый: SET ANSI_NULLS ON


этот ответ также может иметь смысл, поскольку в представлении есть предложение join, которое говорит:

(table.column IS NOT NULL)

так что есть NULLs участвует.


объяснение дополнительно подтверждается возвращением к анализатору запросов и запуском

SET ANSI_NULLS OFF

.

DECLARE @SessionGUID uniqueidentifier
SET @SessionGUID = 'BCBA333C-B6A1-4155-9833-C495F22EA908'

.

SELECT *
FROM Report_Opener_RenamedForCruachan
WHERE SessionGUID = @SessionGUID
ORDER BY CurrencyTypeOrder, Rank

и запрос медленный.


проблема не потому что запрос выполняется из хранимой процедуры. Проблема в том, что параметр подключения Enterprise Manager по умолчанию ANSI_NULLS off, а не ANSI_NULLS on, что является значением по умолчанию QA.

Microsoft признает этот факт в KB296769 (ошибка: невозможно использовать SQL Enterprise Manager для создания хранимых процедур, содержащих связанные объекты сервера). Обходной путь включает в диалог хранимой процедуры:

Set ANSI_NULLS ON
Go
Create Proc spXXXX as
....

сделайте это для своей базы данных. У меня такая же проблема - она отлично работает в одной базе данных, но когда я копирую эту базу данных в другую с помощью импорта SSIS (не обычное восстановление), эта проблема возникает с большинством моих хранимых процедур. Поэтому, погуглив еще немного, я нашел блог Pinal Dave (кстати, я столкнулся с большинством его сообщений и очень помог мне, поэтому спасибо Pinal Dave).

Я выполняю приведенный ниже запрос в моей базе данных, и он исправил мой выпуск:

EXEC sp_MSforeachtable @command1="print '?' DBCC DBREINDEX ('?', ' ', 80)"
GO
EXEC sp_updatestats
GO 

надеюсь, что это помогает. Просто передаю помощь от других, которые помогли мне.


Я столкнулся с той же проблемой, и этот пост был очень полезен для меня, но ни один из опубликованных ответов не решил мою конкретную проблему. Я хотел опубликовать решение, которое сработало для меня в надежде, что оно может помочь кому-то другому.

https://stackoverflow.com/a/24016676/814299

в конце вашего запроса добавьте опцию (OPTIMIZE FOR (@now UNKNOWN))


на этот раз вы нашли вашу проблему. Если в следующий раз Вам повезет меньше и вы не сможете это понять, вы можете использовать план замораживания и перестаньте беспокоиться о неправильном плане выполнения.


я испытывал эту проблему. Мой вопрос выглядел примерно так:

select a, b, c from sometable where date > '20140101'

моя хранимая процедура была определена следующим образом:

create procedure my_procedure (@dtFrom date)
as
select a, b, c from sometable where date > @dtFrom

Я изменил тип данных на datetime и вуаля! Ходила от 30 минут до 1 минуты!

create procedure my_procedure (@dtFrom datetime)
as
select a, b, c from sometable where date > @dtFrom

вы пытались перестроить статистику и / или индексы в таблице Report_Opener. Все recomplies SP ничего не будут стоить, если статистика все еще показывает данные с момента, когда база данных была впервые inauguarated.

сам начальный запрос работает быстро, потому что оптимизатор может видеть, что параметр никогда не будет нулевым. В случае SP оптимизатор не может быть уверен, что параметр никогда не будет нулевым.


хотя я обычно против этого (хотя в этом случае кажется, что у вас есть подлинная причина), вы пытались предоставить какие-либо подсказки запроса в SP-версии запроса? Если SQL Server готовит другой план выполнения в этих двух экземплярах, можно ли использовать подсказку, чтобы указать, какой индекс использовать, чтобы план соответствовал первому?

некоторые примеры вы можете пойти сюда.

EDIT: если вы можете опубликовать свой план запроса здесь, возможно, мы сможем определить какая-то разница между планами.

второй: обновлена ссылка, чтобы быть SQL-2000 конкретных. Вам придется прокручивать вниз, но есть второй под названием "табличные подсказки", это то, что вы ищете.

В-третьих:" плохой "запрос, похоже, игнорирует [IX_Openers_SessionGUID] в таблице "открыватели" - есть ли шанс добавить подсказку индекса, чтобы заставить его использовать этот индекс, изменит вещи?


Это, вероятно, маловероятно, но, учитывая, что ваше наблюдаемое поведение необычно, его нужно проверить, и никто больше не упоминал об этом.

ты абсолютно уверен, что все объекты принадлежат dbo, и у вас нет копий изгоев, принадлежащих вам или другому пользователю?

иногда, когда я видел странное поведение, это потому, что на самом деле было две копии объекта, и какой из них вы получаете, зависит от того, что указывается и кто вы вошли в систему как. Например, вполне возможно иметь две копии представления или процедуры с одинаковым именем, но принадлежащие разным владельцам - ситуация, которая может возникнуть, когда вы не вошли в базу данных как dbo и забыли указать dbo как владельца объекта при создании объекта.

обратите внимание, что в тексте вы запускаете некоторые вещи без указания владельца, например

sp_recompile ViewOpener

если, например, там, где две копии viewopener present принадлежит dbo и [другому пользователю], а затем какой из них вы фактически перекомпилируете, если не укажете, зависит от обстоятельств. То же самое с представлением Report_Opener - если есть две копии (и они могут отличаться в спецификации или плане выполнения), то то, что используется, зависит от обстоятельств - и поскольку вы не указываете владельца, вполне возможно, что ваш запрос adhoc может использовать один, а скомпилированная процедура может использовать другой.

Как я уже сказал, это вероятно, маловероятно, но это возможно и должно быть проверено, потому что ваши проблемы могут быть в том, что вы просто ищете ошибку в неправильном месте.


Это может показаться глупым и кажется очевидным из имени SessionGUID, но является ли столбец uniqueidentifier в Report_Opener? Если нет, вы можете попробовать привести его к правильному типу и дать ему снимок или объявить переменную к правильному типу.

план, созданный как часть sproc, может работать неинтуитивно и выполнять внутреннее приведение на большом столе.


у меня есть другая идея. Что делать, если вы создадите эту табличную функцию:

CREATE FUNCTION tbfSelectFromView
(   
    -- Add the parameters for the function here
    @SessionGUID UNIQUEIDENTIFIER
)
RETURNS TABLE 
AS
RETURN 
(
    SELECT *
    FROM Report_Opener
    WHERE SessionGUID = @SessionGUID
    ORDER BY CurrencyTypeOrder, Rank
)
GO

и затем выбран из него, используя следующий оператор (даже помещая это в свой SP):

SELECT *
FROM tbfSelectFromView(@SessionGUID)

похоже, что происходит (что все уже прокомментировали), что SQL Server просто делает предположение где-то, что неправильно, и, возможно, это заставит его исправить предположение. Я ненавижу добавлять дополнительный шаг, но я не уверен, что еще может быть причиной он.


-- вот решение:

create procedure GetOrderForCustomers(@CustID varchar(20))

as

begin

select * from orders

where customerid = ISNULL(@CustID, '')

end

- это