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

257

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

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

Ян Бойд
источник
Я замечаю, что каждый раз, когда вы берете параметр и переназначаете его другому, а затем позже используете его в запросе, это может произойти, и, как следует из ответа, оптимизация для @ "someparamname" unknown может работать.
JustDave

Ответы:

405

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

Я нашел другой ответ здесь "Параметр нюхает" , спасибо 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 секунд.

Адам Маршалл
источник
23
+1 Но это очень странно, и возникает много вопросов, например, следует ли нам делать это для всех процедур, а если нет, когда это делать?
получил
32
Я единственный, кто озадачен этим поведением? Требование объявления локальных переменных для предотвращения перехвата параметров ?? Разве SQL Server не должен быть достаточно умным, чтобы предотвратить это в первую очередь? Это просто вызывает ненужное раздувание кода из-за недальновидного дизайна Microsoft IMHO.
l46kok
4
15 минут -> 8 секунд! Спасатель
Тони Брикс
3
@BennettDill WITH RECOMPILEне имел значения для меня, только локальные параметры.
Mrogers
8
Теперь это может быть достигнуто с помощью подсказки запроса - OPTION (OPTIMIZE FOR (@varA UNKNOWN, @varB UNKNOWN)
Дейв
131

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

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


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

(table.column IS NOT NULL)

Таким образом, есть некоторые NULLс.


Это объяснение подтверждается возвращением в Query Analizer и выполнением

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 onQA по умолчанию.

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

Set ANSI_NULLS ON
Go
Create Proc spXXXX as
....
Ян Бойд
источник
2
Я до сих пор не понимаю, как поворот ANSI_NULLS ONделает такую ​​огромную разницу в производительности.
Джастин Хелгерсон
2
@ Ek0nomik Потому что JOINпредложения внутри представления имеют разное значение когда ANSI_NULLS OFF. Внезапно совпадение строк приводит к тому, что оптимизатор выполняет запрос совершенно по-другому. Представьте, что вместо удаления 99,9% всех строк они внезапно возвращаются.
Ян Бойд
2
Примечание: ANSI_NULLS OFFсчитается устаревшим и считается плохой практикой
Жан
2
ссылка "В будущей версии SQL Server ANSI_NULLS всегда будет включен, и любые приложения, которые явно установят опцию OFF, будут генерировать ошибку. Избегайте использования этой функции в новых разработках и планируйте модифицировать приложения, которые в настоящее время используют эту функцию. "
17
Не помогло в моем случае.
st_stefanov
19

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

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

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

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

Carenski
источник
2
Просто FYI для будущих читателей: DBCC REINDEXустарела, поэтому вы должны искать альтернативы.
gvee
1
Исправил мою проблему, спасибо (1m20s до 2s!). Re: DBCC DBREINDEXMS говорит: «Эта функция будет удалена в будущей версии Microsoft SQL Server. Не используйте эту функцию в новых разработках и изменяйте приложения, которые в настоящее время используют эту функцию, как можно скорее. Вместо этого используйте ALTER INDEX».
AjV Jsy
Не знаю, является ли это лучшим ответом, но в моем случае sp_updatestats - это все, что нужно, так что +1
Тодд Меньер,
..yes и не забывайте, что для перестроения индексов может потребоваться время и пространство, поэтому прежде чем выполнять это на своем производственном сервере, убедитесь, что вы можете позволить себе возможное замедление. Я бы посоветовал изучить REORGANIZE или REBUILD WITH (ONLINE = ON)
Милан,
14

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

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

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

jessieloo
источник
4

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

Аляска
источник
Согласно этой записи в блоге, план замораживания предназначен только для MS SQL 2005 и выше, поэтому он не поможет OP.
Кокси
Проблема была в том, что он использовал неправильный план запроса. Я не хотел бы заморозить это неправильно.
Ян Бойд
4

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

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
Ли Тикетт
источник
2
Большое спасибо Ли, это спасло мой день! Вот как я могу получить только часть даты в поле datetime: DATEADD (dd, 0, DATEDIFF (dd, 0, table.field))
Julien B.
1
Это исправило мою проблему. У меня был столбец varchar (20), и мой параметр был nvarchar (50), как только я сделал тип параметра таким же, как тип столбца - больше никаких задержек.
st_stefanov
3

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

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

AnthonyWJones
источник
Есть ли способ указать в объявлении хранимой процедуры, что параметр i не может быть нулевым? И это не то, что будет исправлено sp_executesql?
Ян Бойд
Одним словом, не в 2000 году. В 2005 году была добавлена ​​подсказка запроса, в которой вы могли бы указать примерное значение параметра, оптимизатор оптимизировал бы его так, как если бы он знал, что параметр всегда используется. Сказав, что я вообще нашел такие вещи, чтобы быть статистической проблемой.
AnthonyWJones
Если это проблема статистики, они работают нормально из QA, когда я запускаю ее ad-hoc, sp_executesql, exec (). И почему тогда все они работают плохо, когда хранимая процедура содержит специальные SQL, sp_executesql, exec ()?
Ян Бойд
1

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

Для некоторых примеров, вы можете пойти сюда .

РЕДАКТИРОВАТЬ: Если вы можете опубликовать свой план запроса здесь, возможно, мы сможем определить разницу между планами, которые говорят.

ВТОРОЕ: Обновлена ​​ссылка для соответствия SQL-2000. Вам придется прокручивать пути вниз, но есть вторая, озаглавленная «Таблица подсказок», это то, что вы ищете.

ТРЕТЬЕ: «Плохой» запрос, похоже, игнорирует [IX_Openers_SessionGUID] в таблице «Открыватели» - есть ли шанс добавить подсказку INDEX, чтобы заставить его использовать этот индекс, что-то изменит?

SqlRyan
источник
Наиболее полезные подсказки запроса в этой ссылке недоступны в SQL 2000, который является рассматриваемой здесь версией.
AnthonyWJones
Кроме того, какие подсказки нужны? SQL Server может понять это без проблем, когда я запускаю его ad-hoc.
Ян Бойд
Конечно, и это всегда было моим опытом. Тем не менее, в этом случае он говорит, что он придумывает совершенно другой исполнительный план. Может быть, есть индекс, который использует ad-hoc, но по какой-то причине игнорируется в процедуре. Он может заставить SQL Server использовать индекс с подсказкой «INDEX».
SqlRyan
1

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

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

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

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

sp_recompile ViewOpener

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

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

Cruachan
источник
1

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

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

Дэвид Рендалл
источник
Это не. Но я видел проблемы с производительностью в предложении where, сравнивавшем varcharстолбец со nvarcharзначением (например, WHERE CustomerName = N'zrendall'). SQL Server должен был преобразовать значение каждого столбца в a nvarcharперед сравнением.
Ян Бойд,
0

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

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

SqlRyan
источник
0

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

create procedure GetOrderForCustomers(@CustID varchar(20))

as

begin

select * from orders

where customerid = ISNULL(@CustID, '')

end

-- Это оно

Koldoon
источник