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

І знову, намагаючись по-справжньому обдурити його перекомпіляцією.

Я скинув і відтворив збережену процедуру, щоб змусити її створити новий план.

Я спробував форсувати рекомпіляції та запобігти нюханню параметрів , використовуючи змінну decoy:

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:

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" невідомо може працювати.
JustDave

Відповіді:


404

У мене була така ж проблема, як і в оригінальному плакаті, але цитована відповідь не вирішила для мене проблеми. Запит все ще виконувався дуже повільно від збереженої процедури.

Тут я знайшов ще одну відповідь "Параметри нюху" , спасибі 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 Але це дуже дивно, і виникає багато питань, як ми повинні робити це для всіх процедур, а якщо ні, то коли це робити?
gotqn

31
Чи я єдиний, хто здивований такою поведінкою ?? Потрібна декларація локальних змінних, щоб запобігти обнюхуванню параметра ?? Чи не повинен SQL Server бути достатньо розумним, щоб не допустити цього в першу чергу? Це просто викликає непотрібне роздуття коду завдяки короткозорому дизайну IMHO від Microsoft.
l46kok

4
15 хв -> 8 сек! рятувальник життя
Тоні Брікс

3
@BennettDill WITH RECOMPILEне змінив для мене лише локальні параметри.
mrogers

8
Зараз це можна досягти, використовуючи підказку запиту - OPTION (ОПТИМІЗУЙТЕ ДЛЯ (@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задіяні.


Пояснення далі підтверджується поверненням до Аналізатора запитів та запуском

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 (BUG: Неможливо використовувати 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застаріла і вважається поганою практикою
jean

2
посилання "У майбутній версії SQL Server ANSI_NULLS завжди буде ВКЛ., і будь-які додатки, які явно встановлюють опцію ВИМКНЕНО, генерують помилку. Уникайте використання цієї функції в нових роботах з розробки та плануйте змінювати додатки, які наразі використовують цю функцію. "
sotn

Не допомогло в моєму випадку.
st_stefanov

19

Зробіть це для вашої бази даних. У мене така ж проблема - вона працює чудово в одній базі даних, але коли я копіюю цю базу даних в іншу за допомогою SSIS Import (не звичайне відновлення), ця проблема трапляється у більшості моїх збережених процедур. Отож, ще трохи погуглившись, я знайшов блог Пінала Дейва (який був, я зіткнувся з більшістю його посту і дуже допомог мені, тому дякую Піналу Дейву) .

Я виконую наступний запит у своїй базі даних, і він виправив мою проблему:

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

Сподіваюсь, це допомагає. Просто передаючи допомогу від інших, які мені допомогли.


2
Просто БЮР для майбутніх читачів: DBCC REINDEXзастарілий, тому слід шукати альтернативи.
gvee

1
Моя проблема виправлена, спасибі (1m20s до 2s!). Re:, DBCC DBREINDEXMS каже: "Ця функція буде видалена в майбутній версії Microsoft SQL Server. Не використовуйте цю функцію в нових роботах з розробки та змінюйте програми, які наразі використовують цю функцію якнайшвидше. Натомість використовуйте ALTER INDEX."
AjV Jsy

Не знаю, чи це найкраща відповідь, але в моєму випадку sp_updatestats - це все, що потрібно, тому +1
Todd Menier

..да, і не забувайте, що відновлення індексів може зайняти час і простір, тому перед тим, як виконати це на виробничому сервері, переконайтеся, що ви можете дозволити собі можливе уповільнення. Я б запропонував заглянути в REORGANIZE або REBUILD With (ONLINE = ON)
Мілан

14

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

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

В кінці запиту додайте ОПЦІЯ (ОПТИМІЗУЙТЕ ДЛЯ (@ НЕ ЗНАЧЕНО))


4

Цього разу ви знайшли свою проблему. Якщо наступного разу вам не пощастить і не вдається розібратися, ви можете скористатися заморожуванням плану і перестати турбуватися про неправильний план виконання.


Відповідно до цього запису в блозі, заморожування плану розроблено лише для MS SQL 2005 і вище, тому це не допоможе ОП.
Coxy

Проблема полягала в тому, що він використовував неправильний план запитів. я б не хотів заморозити його до неправильного.
Ян Бойд

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 і voila! Ходили від 30 хвилин до 1 хвилини!

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

2
Дякую багато Лі, це врятувало мені день! Ось як я отримую лише частину дати в полі дати: DATEADD (dd, 0, DATEDIFF (dd, 0, table.field))
Жульєн Б.

1
ЦЕ вирішила мою проблему. У мене був varchar стовпця (20), а мій параметр - nvarchar (50), як тільки я зробив тип параметра таким же, як тип стовпця - більше не буде затримок.
st_stefanov

3

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

Сам початковий запит працює швидко, оскільки оптимізатор може побачити, що параметр ніколи не буде нульовим. У випадку з SP оптимізатор не може бути впевнений, що параметр ніколи не буде нульовим.


Чи є спосіб вказати в декларації збереженої процедури, що параметр i не може бути нульовим? І хіба це не те, що було б виправлено sp_executesql?
Ян Бойд

Словом nope, не в 2000 році. 2005 додав підказку запиту, де ви можете навести приклад значення для параметра, оптимізатор оптимізував би так, як ніби знав, що параметр завжди використовується. Сказавши, що я взагалі вважаю, що ця річ є проблемою статистики.
AnthonyWJones

Якщо це проблема зі статистикою, вони працюють добре з QA, коли я запускаю її ad-hoc, sp_executesql, exec (). І чому вони тоді погано працюють, коли збережена процедура містить ad-hoc sql, sp_executesql, exec ()?
Ян Бойд

1

Хоча я зазвичай проти цього (хоча в цьому випадку здається, що у вас є справжня причина), ви намагалися надати будь-які підказки щодо версії запиту SP? Якщо SQL Server готує інший план виконання у цих двох випадках, чи можете ви скористатись підказкою, щоб сказати, який індекс використовувати, щоб план відповідав першому?

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

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

ДРУГО: оновлено посилання, щоб воно було специфічним для SQL-2000. Вам доведеться прокручувати способи вниз, але є другий під назвою "Підказки до таблиці", це те, що ви шукаєте.

ТРЕТИЙ: Схоже, запит "Поганий" ігнорує [IX_Openers_SessionGUID] таблиці "Відкривачі" - будь-який шанс додавання підказки INDEX змусити його використовувати цей індекс змінить речі?


Найбільш корисні підказки щодо запитів у цій посиланні недоступні для SQL 2000, про яку йдеться тут.
AnthonyWJones

Крім того, які підказки потрібні? SQL Server може вирішити це без проблем, коли я запускаю його спеціально.
Ян Бойд

Звичайно, і це теж був мій досвід. Однак у цьому випадку він говорить, що придумує зовсім інший план виконання. Можливо, є індекс, який використовується тимчасово, але чомусь ігнорується в процедурі. Він може змусити SQL Server використовувати індекс із підказкою "INDEX".
SqlRyan

1

Це, мабуть, малоймовірно, але, враховуючи, що ваша спостережувана поведінка є незвичною, її потрібно перевірити, і ніхто більше її не згадував.

Ви абсолютно? впевнені, що всі об'єкти належать dbo, і у вас немає примірницьких копій, які належать вам або іншому користувачеві?

Час від часу, коли я бачив дивну поведінку, це тому, що насправді було дві копії об'єкта, і яку ви отримаєте, залежить від того, що вказано та на кого ви ввійшли. Наприклад, цілком можливо мати дві копії представлення даних або процедури з тим самим іменем, але володіти різними власниками - ситуація, яка може виникнути, коли ви не ввійшли до бази даних як dbo і забули вказати dbo як власника об'єкта, коли ви створюєте об'єкт.

Зауважте, що в тексті ви виконуєте деякі речі, не вказуючи власника, наприклад

sp_recompile ViewOpener

якщо, наприклад, там, де дві копії представлення viewOpener, що належать dbo та [іншому користувачеві], то яку ви насправді перекомпілюєте, якщо ви не вказуєте, залежить від обставин. Дітто з поданням Report_Opener - якщо там є дві копії (і вони можуть відрізнятися специфікацією або планом виконання), то те, що використовується, залежить від обставин - і оскільки ви не вказали власника, цілком можливо, що ваш запит adhoc може використовувати один і для складеної процедури може використовуватися інша.

Як я кажу, це, мабуть, малоймовірно, але це можливо і слід перевірити, оскільки ваші проблеми можуть полягати в тому, що ви просто шукаєте помилку в неправильному місці.


1

Це може здатися нерозумним і здається очевидним із назви SessionGUID, але чи стовпець є унікальним ідентифікатором на Report_Opener? Якщо ні, то, можливо, ви захочете спробувати ввести його в потрібний тип і надати йому знімок або оголосити свою змінну правильному типу.

План, створений як частина проростка, може працювати невмисно і робити внутрішній склад на великому столі.


Це не. Але я бачив проблеми з продуктивністю з пунктом де, який порівнював 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 просто робить припущення, де це неправильно, і, можливо, це змусить його виправити припущення. Не люблю додати додатковий крок, але я не впевнений, що ще може спричинити це.


0

- Ось рішення:

create procedure GetOrderForCustomers(@CustID varchar(20))

as

begin

select * from orders

where customerid = ISNULL(@CustID, '')

end

-- Це воно

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.