SQL Server: consulta rápida, mas lenta do procedimento

257

Uma consulta é executada rapidamente:

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

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

custo da subárvore: 0.502

Mas colocar o mesmo SQL em um procedimento armazenado é lento e com um plano de execução totalmente diferente

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

EXECUTE ViewOpener @SessionGUID

Custo da subárvore: 19,2

Eu corri

sp_recompile ViewOpener

E ainda funciona da mesma maneira (mal) e também mudei o procedimento armazenado para

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

E, novamente, tentando realmente convencê-lo a recompilar.

Larguei e recriei o procedimento armazenado para gerar um novo plano.

Tentei forçar recompiles e impedir a detecção de parâmetros usando uma variável de chamariz:

CREATE PROCEDURE dbo.ViewOpener @SessionGUID uniqueidentifier AS

DECLARE @SessionGUIDbitch uniqueidentifier
SET @SessionGUIDbitch = @SessionGUID

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

Eu também tentei definir o procedimento armazenado WITH RECOMPILE:

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

Para que seu plano nunca seja armazenado em cache, tentei forçar uma recompilação em execute:

EXECUTE ViewOpener @SessionGUID WITH RECOMPILE

O que não ajudou.

Eu tentei converter o procedimento para SQL dinâmico:

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

O que não ajudou.

A entidade " Report_Opener" é uma exibição que não está indexada. A exibição faz referência apenas às tabelas subjacentes. Nenhuma tabela contém colunas computadas, indexadas ou não.

Por um inferno, tentei criar a vista com

SET ANSI_NULLS ON
SET QUOTED_IDENTIFER ON

Isso não consertou.

Como é que

  • a consulta é rápida
  • mover a consulta para uma visualização e selecionar rapidamente a visualização
  • selecionar na exibição de um procedimento armazenado é 40x mais lento?

Tentei mover a definição da visualização diretamente para o procedimento armazenado (violando três regras de negócios e quebrando um encapsulamento importante), e isso a torna apenas cerca de 6x mais lenta.

Por que a versão do procedimento armazenado é tão lenta? O que pode ser responsável pelo SQL Server executando SQL ad-hoc mais rápido que um tipo diferente de SQL ad-hoc?

Eu realmente prefiro não

  • incorporar o SQL no código
  • mude o código de todo

    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)

Mas o que pode levar o SQL Server a não ser executado tão rápido quanto o SQL Sever executando uma consulta, se não o farejador de parâmetros.


Minha próxima tentativa será a de ter StoredProcedureAchamada StoredProcedureBchamada StoredProcedureCchamada StoredProcedureDpara consultar a vista.

E, na sua falta, faça com que o procedimento armazenado chame um procedimento armazenado, chame um UDF, chame um UDF, chame um procedimento armazenado, chame um UDF para consultar a exibição.


Para resumir, o seguinte é executado rapidamente no controle de qualidade, mas lento quando colocado em um procedimento armazenado:

O original:

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

Planos de Execução

O bom plano:

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

O plano ruim

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

O malvado está ansioso para colocar 6 milhões de linhas; o outro não é.

Nota: Esta não é uma pergunta sobre o ajuste de uma consulta. Eu tenho uma consulta que executa muito rápido. Eu só quero que o SQL Server seja executado rapidamente a partir de um procedimento armazenado.

Ian Boyd
fonte
Percebo que toda vez que você pega um parâmetro e o reatribui para outro e, em seguida, o usa em uma consulta mais tarde, isso pode acontecer e, como a resposta sugere Otimizar para @ "someparamname" desconhecido, pode funcionar.
JustDave

Respostas:

405

Eu tive o mesmo problema que o pôster original, mas a resposta citada não resolveu o problema para mim. A consulta ainda foi muito lenta a partir de um procedimento armazenado.

Encontrei outra resposta aqui "Parâmetro Sniffing" , Obrigado Omnibuzz. Tudo se resume ao uso de "variáveis ​​locais" nas consultas de procedimento armazenado, mas leia o original para obter mais entendimento, é uma excelente redação. por exemplo

Maneira lenta:

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

Via rápida:

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

    SELECT * 
    FROM orders
    WHERE customerid = @LocCustID
END

Espero que isso ajude outra pessoa, pois isso reduziu meu tempo de execução de mais de 5 minutos para cerca de 6-7 segundos.

Adam Marshall
fonte
23
+1 Mas isso é muito estranho, e surgem muitas perguntas, como devemos fazer isso para todos os procedimentos e, se não, quando fazer isso?
gotqn
31
Eu sou o único que fica perplexo com esse comportamento? Exigindo que variáveis ​​locais sejam declaradas para evitar o sniffing de parâmetros? O SQL Server não deve ser inteligente o suficiente para impedir que isso aconteça em primeiro lugar? Isso apenas causa inchaço desnecessário no código do IMHO, projeto míope da Microsoft.
L46kok
4
15 min -> 8 seg! salva-vidas #
Tony Brix
3
@BennettDill WITH RECOMPILEnão fez diferença para mim, apenas os parâmetros locais.
mrogers
8
Agora isso pode ser conseguido usando a dica de consulta - OPTION (OPTIMIZE FOR (@varA UNKNOWN, @varB UNKNOWN)
Dave
131

Eu encontrei o problema, aqui está o script das versões lenta e rápida do procedimento armazenado:

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

Se você não percebeu a diferença, eu não culpo você. A diferença não está no procedimento armazenado. A diferença que transforma uma consulta rápida de 0,5 custos em uma que executa um spool ansioso de 6 milhões de linhas:

Lento: SET ANSI_NULLS OFF

Rápido: SET ANSI_NULLS ON


Essa resposta também pode ser feita para fazer sentido, pois a exibição possui uma cláusula de junção que diz:

(table.column IS NOT NULL)

Então, há alguns NULLenvolvidos.


A explicação é ainda mais comprovada retornando ao Query Analizer e executando

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

E a consulta é lenta.


Portanto, o problema não é porque a consulta está sendo executada a partir de um procedimento armazenado. O problema é que a opção padrão de conexão do Enterprise Manager é ANSI_NULLS off, e não ANSI_NULLS on, qual é o padrão do controle de qualidade.

A Microsoft reconhece esse fato no KB296769 (Erro: Não é possível usar o SQL Enterprise Manager para criar procedimentos armazenados que contêm objetos de servidor vinculados). A solução alternativa é incluir a ANSI_NULLSopção na caixa de diálogo do procedimento armazenado:

Set ANSI_NULLS ON
Go
Create Proc spXXXX as
....
Ian Boyd
fonte
2
Ainda não entendo como o giro ANSI_NULLS ONfaz uma diferença de desempenho tão enorme.
23613 Justin Helgerson
2
@ Ek0nomik Porque as JOINcláusulas dentro da visão têm um significado diferente quando ANSI_NULLS OFF. De repente, as linhas coincidem, fazendo com que o otimizador execute a consulta de maneira completamente diferente. Imagine que, em vez de eliminar 99,9% de todas as linhas, elas retornem repentinamente.
22613 Ian Boyd
2
Nota: ANSI_NULLS OFFfoi descontinuado e considerado uma má prática
jean
2
link "Em uma versão futura do SQL Server, o ANSI_NULLS sempre estará LIGADO e qualquer aplicativo que defina explicitamente a opção OFF gerará um erro. Evite usar esse recurso em novos trabalhos de desenvolvimento e planeje modificar os aplicativos que atualmente usam esse recurso. "
SOTN
Não ajudou no meu caso.
st_stefanov
19

Faça isso para o seu banco de dados. Eu tenho o mesmo problema - ele funciona bem em um banco de dados, mas quando eu copio esse banco de dados para outro usando o SSIS Import (não a restauração usual), esse problema ocorre com a maioria dos meus procedimentos armazenados. Então, depois de pesquisar um pouco mais, encontrei o blog de Pinal Dave (que, aliás, encontrei a maior parte de seu post e me ajudou muito, então obrigado Pinal Dave) .

Eu executo a consulta abaixo no meu banco de dados e ele corrigiu meu problema:

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

Espero que isto ajude. Apenas passando a ajuda de outras pessoas que me ajudaram.

Carenski
fonte
2
Apenas um FYI para futuros leitores: DBCC REINDEXfoi preterido, portanto, você deve procurar alternativas.
gvee
1
Corrigido meu problema, obrigado (1m20s a 2s!). Re: DBCC DBREINDEXMS diz: "Esse recurso será removido em uma versão futura do Microsoft SQL Server. Não use esse recurso em novos trabalhos de desenvolvimento e modifique os aplicativos que atualmente usam esse recurso o mais rápido possível. Use ALTER INDEX."
AJV JSY
Não sei se esta é a melhor resposta, mas no meu caso sp_updatestats é tudo o que tinha, então +1
Todd Menier
..sim e, não se esqueça que a reconstrução de índices pode levar tempo e espaço; portanto, antes de executar isso no servidor de produção, verifique se você pode permitir uma possível desaceleração. Gostaria de sugerir a olhar para REORGANIZE ou Reconstruir Com (ONLINE = ON)
Milan
14

Eu estava enfrentando o mesmo problema e este post foi muito útil para mim, mas nenhuma das respostas postadas resolveu meu problema específico. Queria postar a solução que funcionou para mim, na esperança de que ela possa ajudar outra pessoa.

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

No final da sua consulta, adicione OPTION (OPTIMIZE FOR (@now UNKNOWN))

jessieloo
fonte
4

Desta vez, você encontrou seu problema. Se da próxima vez você tiver menos sorte e não conseguir descobrir, poderá usar o congelamento do plano e parar de se preocupar com o plano de execução errado.

AK
fonte
De acordo com a entrada do blog, o congelamento do plano é apenas para o MS SQL 2005 e superior, portanto não ajudaria o OP.
Coxy
O problema era que estava usando o plano de consulta errado. eu não gostaria de congelá-lo para o errado.
23710 Ian Boyd
4

Eu estava com esse problema. Minha consulta era algo como:

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

Meu procedimento armazenado foi definido como:

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

Eu mudei o tipo de dados para datetime e pronto! Fui de 30 minutos a 1 minuto!

create procedure my_procedure (@dtFrom datetime)
as
select a, b, c from sometable where date > @dtFrom
Lee Tickett
fonte
2
Muito obrigado Lee, isso salvou o meu dia! Aqui está como eu obter apenas a parte de data de um campo de data e hora: DATEADD (dd, 0, DATEDIFF (dd, 0, table.field))
Julien B.
1
Isso corrigiu meu problema. Eu tinha a coluna varchar (20) e meu parâmetro era nvarchar (50), depois que fiz o tipo de parâmetro igual ao tipo da coluna - sem mais atrasos.
st_stefanov
3

Você tentou recriar as estatísticas e / ou os índices na tabela Report_Opener. Todas as recomendações do SP não valerão nada se as estatísticas ainda mostrarem dados de quando o banco de dados foi iniciado pela primeira vez.

A consulta inicial em si funciona rapidamente porque o otimizador pode ver que o parâmetro nunca será nulo. No caso do SP, o otimizador não pode ter certeza de que o parâmetro nunca será nulo.

AnthonyWJones
fonte
Existe uma maneira de indicar em uma declaração de procedimento armazenado que o parâmetro i não pode ser nulo? E não é algo que seria corrigido pelo sp_executesql?
22630 Ian Boyd
Em uma palavra não, não em 2000. 2005 adicionou uma dica de consulta em que você poderia fornecer um valor de exemplo para um parâmetro, o otimizador otimizaria como se soubesse que esse parâmetro sempre foi usado. Dito isto, geralmente considero esse tipo de coisa um problema estatístico.
13139 AnthonyWJones
Se for um problema estatístico, eles funcionam bem no controle de qualidade quando eu o executo ad-hoc, sp_executesql, exec (). E por que todos eles executam mal quando um procedimento armazenado contém o sql ad-hoc, sp_executesql, exec ()?
22730 Ian Boyd
1

Embora eu seja geralmente contra (embora neste caso pareça que você tenha um motivo genuíno), você tentou fornecer dicas de consulta sobre a versão SP da consulta? Se o SQL Server estiver preparando um plano de execução diferente nessas duas instâncias, você pode usar uma dica para informar qual índice usar, para que o plano corresponda ao primeiro?

Para alguns exemplos, você pode ir aqui .

EDIT: Se você pode postar seu plano de consulta aqui, talvez possamos identificar alguma diferença entre os planos que estão dizendo.

SEGUNDO: Atualizado o link para ser específico do SQL-2000. Você terá que rolar algumas maneiras, mas há um segundo intitulado "Dicas de tabela" que é o que você está procurando.

TERCEIRO: A consulta "Bad" parece estar ignorando o [IX_Openers_SessionGUID] na tabela "Openers" - qualquer chance de adicionar uma dica INDEX para forçá-la a usar esse índice mudará as coisas?

SqlRyan
fonte
As dicas de consulta mais úteis nessa referência não estão disponíveis no SQL 2000, que é a versão em questão aqui.
21139 AnthonyWJones
Além disso, que dicas são necessárias? O SQL Server é capaz de descobrir isso sem problemas ao executá-lo ad-hoc.
3105 Ian Boyd
Claro, e essa sempre foi a minha experiência também. No entanto, neste caso, ele está dizendo que está apresentando um plano executivo totalmente diferente. Talvez exista um índice que seja utilizado ad-hoc, mas por algum motivo esteja sendo ignorado no processo. Ele pode forçar o SQL Server a usar o índice com a dica "INDEX".
SqlRyan
1

Provavelmente, isso é improvável, mas, como o seu comportamento observado é incomum, ele precisa ser verificado e ninguém mais o mencionou.

Você tem certeza absoluta de que todos os objetos pertencem ao dbo e não possui cópias não autorizadas de sua propriedade ou também de um usuário diferente?

Ocasionalmente, quando vejo comportamento estranho, é porque na verdade existem duas cópias de um objeto e qual delas você depende depende do que está especificado e de quem você está conectado. Por exemplo, é perfeitamente possível ter duas cópias de uma visualização ou procedimento com o mesmo nome, mas pertencentes a proprietários diferentes - uma situação que pode surgir em que você não está conectado ao banco de dados como um dbo e se esqueça de especificar dbo como proprietário do objeto quando você cria o objeto.

Observe que no texto você está executando algumas coisas sem especificar o proprietário, por exemplo

sp_recompile ViewOpener

se, por exemplo, houver duas cópias do viewOpener presentes pertencentes ao dbo e a [outro usuário], qual delas você recompilará de verdade, se não especificar, depende das circunstâncias. Idem com a visualização Report_Opener - se houver duas cópias (e elas podem diferir na especificação ou no plano de execução), o que é usado depende das circunstâncias - e como você não especifica o proprietário, é perfeitamente possível que sua consulta adhoc use uma e a procedimento compilado pode usar use o outro.

Como eu disse, provavelmente é improvável, mas é possível e deve ser verificado, pois seus problemas podem ser que você está simplesmente procurando o bug no lugar errado.

Cruachan
fonte
1

Isso pode parecer bobo e parece óbvio no nome SessionGUID, mas a coluna é um identificador exclusivo no Report_Opener? Caso contrário, você pode tentar convertê-lo para o tipo correto e tentar ou declarar sua variável para o tipo correto.

O plano criado como parte do sproc pode funcionar de maneira não intuitiva e fazer uma projeção interna em uma mesa grande.

David Rendall
fonte
Não é. Mas eu vi problemas de desempenho com uma cláusula where que estava comparando uma varcharcoluna com um nvarcharvalor (por exemplo WHERE CustomerName = N'zrendall'). O SQL Server teve que converter cada valor de coluna em um nvarcharantes da comparação.
22615 Ian
0

Eu tenho outra ideia. E se você criar esta função baseada em tabela:

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

E, em seguida, selecionado usando a seguinte instrução (mesmo colocando isso no seu SP):

SELECT *
FROM tbfSelectFromView(@SessionGUID)

Parece que o que está acontecendo (no qual todo mundo já comentou) é que o SQL Server apenas faz uma suposição em algum lugar errado, e talvez isso o force a corrigir a suposição. Detesto adicionar a etapa extra, mas não tenho certeza do que mais possa estar causando isso.

SqlRyan
fonte
0

- Aqui está a solução:

create procedure GetOrderForCustomers(@CustID varchar(20))

as

begin

select * from orders

where customerid = ISNULL(@CustID, '')

end

-- É isso aí

Koldoon
fonte