Запрос выполняется быстро:
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, выполняющий запрос, если не анализировать параметры.
Моя следующая попытка будет иметь 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 работал быстро из хранимой процедуры.
Ответы:
У меня была та же проблема, что и у оригинального постера, но цитируемый ответ не решил проблему для меня. Запрос все еще выполнялся очень медленно из хранимой процедуры.
Я нашел другой ответ здесь "Параметр нюхает" , спасибо Omnibuzz. Все сводится к использованию «локальных переменных» в ваших запросах к хранимым процедурам, но для большего понимания прочитайте оригинал, это отличная статья. например
Медленный путь:
Быстрый путь:
Надеюсь, это поможет кому-то еще, благодаря этому мое время выполнения сократилось с 5 минут до 6-7 секунд.
источник
WITH RECOMPILE
не имел значения для меня, только локальные параметры.Я нашел проблему, вот сценарий медленной и быстрой версии хранимой процедуры:
dbo.ViewOpener__RenamedForCruachan__Slow.PRC
dbo.ViewOpener__RenamedForCruachan__Fast.PRC
Если вы не заметили разницу, я не виню вас. Разница не в хранимой процедуре вообще. Разница, которая превращает быстрый запрос на 0,5 стоимости в запрос с 6 миллионами строк:
Медленный:
SET ANSI_NULLS OFF
Быстро:
SET ANSI_NULLS ON
Этот ответ также может иметь смысл, так как у представления есть предложение соединения, которое говорит:
Таким образом, есть некоторые
NULL
с.Это объяснение подтверждается возвращением в Query Analizer и выполнением
,
,
И запрос медленный.
Таким образом, проблема не в том, что запрос выполняется из хранимой процедуры. Проблема в том, что по умолчанию для соединения с Enterprise Manager выбрана опция
ANSI_NULLS off
, а неANSI_NULLS on
QA по умолчанию.Microsoft подтверждает этот факт в KB296769 ( ошибка : невозможно использовать SQL Enterprise Manager для создания хранимых процедур, содержащих связанные объекты сервера). Обходной путь - включить
ANSI_NULLS
параметр в диалоговом окне хранимой процедуры:источник
ANSI_NULLS ON
делает такую огромную разницу в производительности.JOIN
предложения внутри представления имеют разное значение когдаANSI_NULLS OFF
. Внезапно совпадение строк приводит к тому, что оптимизатор выполняет запрос совершенно по-другому. Представьте, что вместо удаления 99,9% всех строк они внезапно возвращаются.ANSI_NULLS OFF
считается устаревшим и считается плохой практикойСделайте это для своей базы данных. У меня та же проблема - она отлично работает в одной базе данных, но когда я копирую эту базу данных в другую с помощью импорта служб SSIS (а не обычного восстановления), эта проблема возникает с большинством моих хранимых процедур. Поэтому, погуглив еще немного, я нашел блог Пинала Дейва (между прочим, я столкнулся с большей частью его поста и очень мне помог, так что спасибо Пиналу Дейву) .
Я выполняю следующий запрос к моей базе данных, и это исправило мою проблему:
Надеюсь это поможет. Просто передавая помощь от других, которые помогли мне.
источник
DBCC REINDEX
устарела, поэтому вы должны искать альтернативы.DBCC DBREINDEX
MS говорит: «Эта функция будет удалена в будущей версии Microsoft SQL Server. Не используйте эту функцию в новых разработках и изменяйте приложения, которые в настоящее время используют эту функцию, как можно скорее. Вместо этого используйте ALTER INDEX».Я столкнулся с той же проблемой, и этот пост был очень полезным для меня, но ни один из опубликованных ответов не решил мою конкретную проблему. Я хотел опубликовать решение, которое работало для меня, в надежде, что оно может помочь кому-то еще.
https://stackoverflow.com/a/24016676/814299
источник
На этот раз вы нашли свою проблему. Если в следующий раз вам повезет меньше, и вы не сможете с этим разобраться, вы можете использовать план замораживания и перестать беспокоиться о неправильном плане выполнения.
источник
Я испытывал эту проблему. Мой запрос выглядел примерно так:
Моя хранимая процедура была определена как:
Я изменил тип данных на datetime и вуаля! Ездил от 30 минут до 1 минуты!
источник
Вы пытались перестроить статистику и / или индексы в таблице Report_Opener. Все повторения SP не будут стоить ничего, если статистика все еще показывает данные, когда база данных была впервые открыта.
Сам первоначальный запрос работает быстро, потому что оптимизатор может видеть, что параметр никогда не будет нулевым. В случае SP оптимизатор не может быть уверен, что параметр никогда не будет нулевым.
источник
Хотя я обычно против (хотя в этом случае кажется, что у вас есть подлинная причина), вы пытались предоставить какие-либо подсказки для запроса в SP-версии запроса? Если SQL Server подготавливает другой план выполнения в этих двух экземплярах, можете ли вы использовать подсказку, чтобы сообщить ему, какой индекс использовать, чтобы план соответствовал первому?
Для некоторых примеров, вы можете пойти сюда .
РЕДАКТИРОВАТЬ: Если вы можете опубликовать свой план запроса здесь, возможно, мы сможем определить разницу между планами, которые говорят.
ВТОРОЕ: Обновлена ссылка для соответствия SQL-2000. Вам придется прокручивать пути вниз, но есть вторая, озаглавленная «Таблица подсказок», это то, что вы ищете.
ТРЕТЬЕ: «Плохой» запрос, похоже, игнорирует [IX_Openers_SessionGUID] в таблице «Открыватели» - есть ли шанс добавить подсказку INDEX, чтобы заставить его использовать этот индекс, что-то изменит?
источник
Это, вероятно, маловероятно, но, учитывая, что наблюдаемое вами поведение необычно, его необходимо проверить, и никто другой не упомянул об этом.
Вы абсолютно уверены, что все объекты принадлежат dbo, и у вас нет мошеннических копий, принадлежащих вам или другому пользователю?
Просто иногда, когда я видел странное поведение, это потому, что на самом деле было две копии объекта, и то, что вы получите, зависит от того, что указано, и от кого вы вошли в систему. Например, вполне возможно иметь две копии представления или процедуры с одинаковым именем, но принадлежащими разным владельцам - ситуация, которая может возникнуть, когда вы не вошли в базу данных как dbo и забыли указать dbo как владельца объекта, когда Вы создаете объект.
Обратите внимание, что в тексте вы выполняете некоторые вещи без указания владельца, например
если, например, там, где присутствуют две копии viewOpener, принадлежащие dbo и [некоторому другому пользователю], то какая из них вы на самом деле перекомпилируете, если не укажете, зависит от обстоятельств. То же самое с представлением Report_Opener - если там, где две копии (и они могут отличаться в спецификации или плане выполнения), то, что используется, зависит от обстоятельств - и, поскольку вы не указываете владельца, вполне возможно, что ваш запрос adhoc может использовать одну и Скомпилированная процедура может использовать другой.
Как я уже сказал, это, вероятно, маловероятно, но возможно и должно быть проверено, поскольку ваши проблемы могут заключаться в том, что вы просто ищете ошибку в неправильном месте.
источник
Это может звучать глупо и кажется очевидным из названия SessionGUID, но является ли столбец уникальным идентификатором в Report_Opener? Если нет, вы можете попробовать привести его к правильному типу и сделать снимок или объявить переменную с правильным типом.
План, созданный как часть sproc, может работать не интуитивно и выполнять внутреннее приведение на большом столе.
источник
varchar
столбец соnvarchar
значением (например,WHERE CustomerName = N'zrendall'
). SQL Server должен был преобразовать значение каждого столбца в anvarchar
перед сравнением.У меня есть другая идея. Что если вы создадите эту табличную функцию:
И затем выбрал из него следующее утверждение (даже поместив это в свой SP):
Похоже, что происходит (что все уже прокомментировали), что SQL Server просто делает предположение, где-то не так, и, возможно, это заставит его исправить предположение. Я ненавижу добавлять дополнительный шаг, но я не уверен, что еще может быть причиной этого.
источник
- Вот решение:
-- Это оно
источник