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


это ничего не исправило.



как это что




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

  • перемещение запроса в представление и выбор из представления выполняется быстро

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


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



почему версия хранимой процедуры так медленно? Что может учетную запись для SQL Server под управлением специальной SQL быстрее, чем разного рода специальных служб 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, выполняющий запрос, если не параметр sniffing.





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



и не, что у хранимой процедуры вызов хранимой процедуры, вызов функции, вызов функции, вызов хранимой процедуры, вызов 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)




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



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



      |--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)


The плохо план



       |--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 быстро запускался из хранимой процедуры.


1273   12  

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

в конце вашего запроса добавьте опцию (оптимизировать для (@теперь неизвестно))

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

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

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

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

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

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

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

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

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

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

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

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

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

sp_recompile ViewOpener

если, например, там, где две копии viewOpener настоящее время принадлежит 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

- это

Comments

    Ничего не найдено.