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 StoredProcedureA
chamada StoredProcedureB
chamada StoredProcedureC
chamada StoredProcedureD
para 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.
fonte
Respostas:
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:
Via rápida:
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.
fonte
WITH RECOMPILE
não fez diferença para mim, apenas os parâmetros locais.Eu encontrei o problema, aqui está o script das versões lenta e rápida do procedimento armazenado:
dbo.ViewOpener__RenamedForCruachan__Slow.PRC
dbo.ViewOpener__RenamedForCruachan__Fast.PRC
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:
Então, há alguns
NULL
envolvidos.A explicação é ainda mais comprovada retornando ao Query Analizer e executando
.
.
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ãoANSI_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_NULLS
opção na caixa de diálogo do procedimento armazenado:fonte
ANSI_NULLS ON
faz uma diferença de desempenho tão enorme.JOIN
cláusulas dentro da visão têm um significado diferente quandoANSI_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.ANSI_NULLS OFF
foi descontinuado e considerado uma má práticaFaç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:
Espero que isto ajude. Apenas passando a ajuda de outras pessoas que me ajudaram.
fonte
DBCC REINDEX
foi preterido, portanto, você deve procurar alternativas.DBCC DBREINDEX
MS 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."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
fonte
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.
fonte
Eu estava com esse problema. Minha consulta era algo como:
Meu procedimento armazenado foi definido como:
Eu mudei o tipo de dados para datetime e pronto! Fui de 30 minutos a 1 minuto!
fonte
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.
fonte
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?
fonte
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
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.
fonte
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.
fonte
varchar
coluna com umnvarchar
valor (por exemploWHERE CustomerName = N'zrendall'
). O SQL Server teve que converter cada valor de coluna em umnvarchar
antes da comparação.Eu tenho outra ideia. E se você criar esta função baseada em tabela:
E, em seguida, selecionado usando a seguinte instrução (mesmo colocando isso no seu SP):
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.
fonte
- Aqui está a solução:
-- É isso aí
fonte