Procedimentos armazenados vs. SQL embutido


27

Eu sei que os procedimentos armazenados são mais eficientes pelo caminho de execução (do que o sql embutido nos aplicativos). No entanto, quando pressionado, não sou super conhecedor do porquê.

Eu gostaria de saber o raciocínio técnico para isso (de uma maneira que eu possa explicar para alguém mais tarde).

Alguém pode me ajudar a formular uma boa resposta?


11
Uma consulta adequadamente parametrizada é tão boa quanto um procedimento armazenado, do ponto de vista do desempenho. Ambos são compilados antes do primeiro uso, ambos reutilizam o plano de execução em cache nas execuções subseqüentes, os dois planos são armazenados no mesmo cache do plano e os dois recebem o mesmo nome. Não há mais benefícios de desempenho para um procedimento armazenado, atualmente no SQL Server.
marc_s

@marc_s isso é verdade se as consultas forem idênticas. No entanto, como indiquei na minha resposta, existem algumas características de consultas ad hoc que podem ser problemas de desempenho, mesmo para consultas que parecem idênticas.
Aaron Bertrand

Respostas:


42

Acredito que esse sentimento era verdadeiro em um ponto, mas não nas versões atuais do SQL Server. O problema era que, antigamente, as instruções SQL ad hoc não podiam ser otimizadas adequadamente porque o SQL Server só podia otimizar / compilar no nível do lote. Agora temos a otimização no nível de instrução, para que uma consulta adequadamente parametrizada proveniente de um aplicativo possa tirar proveito do mesmo plano de execução que a consulta incorporada em um procedimento armazenado.

Eu ainda prefiro procedimentos armazenados do lado do DBA pelos seguintes motivos (e vários deles podem ter um enorme impacto no desempenho):

  • Se eu tiver vários aplicativos que reutilizem as mesmas consultas, um procedimento armazenado encapsula essa lógica, em vez de desarrumar a mesma consulta ad hoc várias vezes em diferentes bases de código. Os aplicativos que reutilizam as mesmas consultas também podem estar sujeitos ao inchaço do cache do plano, a menos que sejam copiados literalmente. Mesmo diferenças de maiúsculas e minúsculas e espaços em branco podem levar ao armazenamento de várias versões do mesmo plano (desperdício).
  • Posso inspecionar e solucionar problemas do que uma consulta está fazendo sem ter acesso ao código-fonte do aplicativo ou executar rastreamentos caros para ver exatamente o que o aplicativo está fazendo.
  • Também posso controlar (e saber com antecedência) quais consultas o aplicativo pode executar, quais tabelas ele pode acessar e em que contexto etc. Se os desenvolvedores estiverem escrevendo consultas ad-hoc em seu aplicativo, eles terão que venha puxar a manga da minha camisa toda vez que eles precisarem acessar uma mesa que eu não conhecia ou não podia prever, ou se eu sou menos responsável / entusiasmado e / ou consciente da segurança, vou promover isso usuário ao dbo para que eles parem de me incomodar. Normalmente, isso é feito quando os desenvolvedores superam os DBAs ou os DBAs são teimosos. Esse último ponto é ruim e precisamos melhorar o fornecimento das consultas necessárias.
  • Em uma observação relacionada, um conjunto de procedimentos armazenados é uma maneira muito fácil de inventariar exatamente quais consultas podem estar em execução no meu sistema. Assim que um aplicativo pode ignorar procedimentos e enviar suas próprias consultas ad-hoc, para encontrá-los, eu tenho que executar um rastreamento que cubra um ciclo de negócios inteiro ou analisar todo o código do aplicativo (novamente, isso Talvez eu não tenha acesso a) para encontrar algo que se pareça com uma consulta. Ser capaz de ver a lista de procedimentos armazenados (e grep uma única fonte sys.sql_modules, para referências a objetos específicos) facilita muito a vida de todos.
  • Posso me esforçar muito mais para impedir a injeção de SQL; mesmo se eu pegar uma entrada e executá-la com SQL dinâmico, posso controlar muito do que é permitido acontecer. Não tenho controle sobre o que um desenvolvedor está fazendo ao construir instruções SQL embutidas.
  • Posso otimizar a consulta (ou consultas) sem ter acesso ao código-fonte do aplicativo, a capacidade de fazer alterações, o conhecimento da linguagem do aplicativo para fazê-lo de forma eficaz, a autoridade (sem esquecer o incômodo) de recompilar e reimplementar o aplicativo etc. Isso é particularmente problemático se o aplicativo for distribuído.
  • Posso forçar determinadas opções definidas no procedimento armazenado para evitar que consultas individuais sejam sujeitas a alguns dos Slow no aplicativo, rápidos no SSMS? problemas Isso significa que, para dois aplicativos diferentes que chamam uma consulta ad hoc, um poderia ter SET ANSI_WARNINGS ONe o outro poderia ter SET ANSI_WARNINGS OFF, e cada um deles teria sua própria cópia do plano. O plano que eles obtêm depende dos parâmetros em uso, estatísticas em vigor etc. na primeira vez em que a consulta é chamada em cada caso, o que pode levar a planos diferentes e, portanto, a um desempenho muito diferente.
  • Eu posso controlar coisas como tipos de dados e como os parâmetros são usados, ao contrário de certas ORMs - algumas versões anteriores de coisas como EF parametrizavam uma consulta com base no comprimento de um parâmetro, portanto, se eu tivesse um parâmetro N'Smith 'e outro N' Johnson, eu receberia duas versões diferentes do plano. Eles consertaram isso. Eles consertaram isso, mas o que mais ainda está quebrado?
  • Eu posso fazer coisas que ORMs e outras estruturas e bibliotecas "úteis" ainda não são capazes de oferecer suporte.

Dito isso, é provável que essa questão suscite mais argumentos religiosos do que debates técnicos. Se virmos isso acontecendo, provavelmente o desligaremos.


2
Another reason for stored procedures? For long, complicated queries, you have to push the query to the server every time, unless it's a sproc, then you are basically just pushing "exec sprocname" and a few parameters. This could make a difference on a slow (or busy) network.
David Crowell

0

Embora eu respeite o apresentador, discordo humildemente da resposta fornecida e não por "razões religiosas". Em outras palavras, acredito que não há nenhum recurso fornecido pela Microsoft que diminua a necessidade de orientações para o uso de procedimentos armazenados.

Qualquer orientação fornecida a um desenvolvedor que favorece o uso de consultas SQL de texto bruto deve ser preenchida com muitas ressalvas, de modo que eu acho que o conselho mais prudente é incentivar muito o uso de Procedimentos armazenados e desencorajar suas equipes de desenvolvedores de se envolverem na prática de incorporar instruções SQL no código ou enviar solicitações SQL baseadas em texto simples e brutos, fora dos SPROCs SQL (procedimentos armazenados).

Penso que a resposta simples para a pergunta de por que usar um SPROC é como o apresentador supôs: os SPROCs são analisados, otimizados e compilados. Dessa forma, seus planos de consulta / execução são armazenados em cache porque você salvou uma representação estática de uma consulta e, normalmente, a variará apenas por parâmetros, o que não é verdade no caso de instruções SQL copiadas / coladas que provavelmente se modificam de página para página e componente / camada, e geralmente são variados na medida em que tabelas diferentes, até mesmo nomes de banco de dados, podem ser especificadas de chamada para chamada. Permitindo esse tipo de ad hoc dinâmicoO envio de SQL diminui bastante a probabilidade de o DB Engine reutilizar o plano de consulta para suas instruções ad hoc, de acordo com algumas regras muito estritas. Aqui, estou fazendo a distinção entre consultas dinâmicas ad hoc (no espírito da pergunta levantada) versus o uso do eficiente sistema SPROC sp_executesql.

Mais especificamente, existem os seguintes componentes:

  • Planos de consulta serial e paralela que não mantêm o contexto do usuário e permitem a reutilização pelo mecanismo de banco de dados.
  • Contexto de execução que permite a reutilização de um plano de consulta por um novo usuário com diferentes parâmetros de dados.
  • Cache de procedimento, que é o que o mecanismo de banco de dados consulta para criar as eficiências que buscamos.

Quando uma instrução SQL é emitida a partir de uma página da web, denominada "instrução ad hoc", o mecanismo procura um plano de execução existente para lidar com a solicitação. Como este é um texto enviado por um usuário, ele será ingerido, analisado, compilado e executado, se for válido. Nesse momento, ele receberá um custo de consulta igual a zero. O custo da consulta é usado quando o mecanismo de banco de dados usa seu algoritmo para determinar quais planos de execução serão removidos do cache.

As consultas ad hoc recebem um valor de custo de consulta original igual a zero, por padrão. Após a execução subsequente do mesmo texto de consulta ad hoc exato, por outro processo do usuário (ou o mesmo), o custo da consulta atual é redefinido para o custo de compilação original. Como nosso custo de compilação de consulta ad hoc é zero, isso não é um bom presságio para a possibilidade de reutilização. Obviamente, zero é o número inteiro menos valorizado, mas por que seria despejado?

Quando surgirem pressões de memória, e ocorrerão se você tiver um site usado com frequência, o mecanismo do DB usará um algoritmo de limpeza para determinar como recuperar a memória que o cache do Procedimento está usando. Ele usa o custo atual da consulta para decidir quais planos despejar. Como você pode imaginar, os planos com custo zero são os primeiros a serem despejados do cache, porque zero significa essencialmente "nenhum usuário atual ou referência a esse plano".

  • Nota: Planos de execução ad hoc - o custo atual é aumentado por cada processo do usuário, pelo custo de compilação original do plano. No entanto, o custo máximo de nenhum plano pode ser maior que o custo de compilação original ... no caso de consultas ad hoc ... zero. Portanto, ele será "aumentado" por esse valor ... zero - o que significa essencialmente que continuará sendo o plano de custo mais baixo.

Portanto, é bem provável que esse plano seja despejado primeiro quando surgirem pressões de memória.

Portanto, se você tiver um servidor com muita memória "além de suas necessidades", poderá não ter esse problema com a mesma frequência que um servidor ocupado que tenha apenas memória "suficiente" para lidar com sua carga de trabalho. (Desculpe, a capacidade e a utilização da memória do servidor são um pouco subjetivas / relativas, embora o algoritmo não seja.)

Agora, se estou de fato incorreto sobre um ou mais pontos, certamente estou aberto a ser corrigido.

Por fim, o autor escreveu:

"Agora temos otimização no nível de instrução, para que uma consulta adequadamente parametrizada proveniente de um aplicativo possa tirar proveito do mesmo plano de execução que a consulta incorporada em um procedimento armazenado".

Acredito que o autor esteja se referindo à opção "otimizar para cargas de trabalho ad hoc".

Nesse caso, essa opção permite um processo de duas etapas que evita o envio imediato do plano de consulta completo ao cache do Procedimento. Ele envia apenas um stub de consulta menor lá. Se uma chamada de consulta exata for enviada de volta ao servidor enquanto o stub da consulta ainda estiver no cache do Procedimento, o plano de execução completo da consulta será salvo no cache do Procedimento naquele momento. Isso economiza memória, que durante incidentes de pressão de memória, pode permitir que o algoritmo de remoção despeje seu stub com menos frequência do que um plano de consulta maior que foi armazenado em cache. Novamente, isso depende da memória e utilização do servidor.

No entanto, você precisa ativar esta opção, pois ela está desativada por padrão.

Por fim, quero enfatizar que, muitas vezes, a razão pela qual os desenvolvedores incorporariam o SQL em páginas, componentes e outros locais é porque desejam ser flexíveis e enviar consultas SQL dinâmicas ao mecanismo de banco de dados. Portanto, em um Caso de Uso do mundo real, é improvável que o envio do mesmo texto chamada a chamada seja o mesmo que ocorre com o cache / eficiência que procuramos ao enviar consultas ad hoc ao SQL Server.

Para informações adicionais, consulte:

https://technet.microsoft.com/en-us/library/ms181055(v=sql.105).aspx
http://sqlmag.com/database-performance-tuning/don-t-fear-dynamic-sql

Best,
Henry


4
Eu li vários parágrafos do seu post, com cuidado, duas ou três vezes, e ainda não tenho idéia de quais pensamentos você está tentando transmitir. Em alguns casos, você parece no final das frases estar dizendo exatamente o oposto do que a frase começou tentando dizer. Você realmente precisa revisar e editar este envio com cuidado.
Pieter Geerkens

Obrigado pelo feedback Pieter. Se for esse o caso, é possível que eu reduza minhas frases para tornar o argumento mais claro. Você pode, por favor, dar um exemplo de onde pareço afirmar o oposto do pensamento original? Muito apreciado.
Henry

Não, não quis dizer Otimizar para cargas de trabalho ad hoc, quis dizer otimização em nível de instrução. No SQL Server 2000, por exemplo, um procedimento armazenado seria compilado como um todo, portanto, não havia como o aplicativo reutilizar um plano para sua própria consulta ad hoc que correspondesse a algo no procedimento. Eu direi que concordo com Pieter - muitas das coisas que você diz são difíceis de seguir. Coisas como "Acredito que não há nenhum recurso fornecido pela Microsoft que diminua a necessidade de orientações para o uso de procedimentos armazenados". são desnecessariamente complexos e exigem muita análise para serem entendidos. NA MINHA HUMILDE OPINIÃO.
Aaron Bertrand

11
parece que sua aversão ao sql "ad hoc" se baseia na idéia de que o sql está de alguma forma mudando entre execuções ... isso é totalmente falso quando a parametrização está envolvida.
b_levitt

0

TLDR: Não há diferença significativa de desempenho entre os dois, desde que o seu sql embutido seja parametrizado.

Essa é a razão pela qual eu abandonei lentamente os procedimentos armazenados:

  • Executamos um ambiente de aplicativos 'beta' - um ambiente paralelo à produção que compartilha o banco de dados de produção. Como o código db está no nível do aplicativo e as alterações na estrutura do db são raras, podemos permitir que as pessoas confirmem novas funcionalidades além do controle de qualidade e façam implantações fora da janela de implantação de produção, mas ainda forneçam funcionalidade de produção e correções não críticas. Isso não seria possível se metade do código do aplicativo estivesse no banco de dados.

  • Nós praticamos devops no nível do banco de dados (polvo + dacpacs). No entanto, embora a camada de negócios e a subida possam basicamente ser eliminadas, substituídas e a recuperação pelo contrário, isso não é verdade para as mudanças incrementais e potencialmente destrutivas que devem ir aos bancos de dados. Consequentemente, preferimos manter nossas implantações de banco de dados mais leves e menos frequentes.

  • Para evitar cópias quase exatas do mesmo código para parâmetros opcionais, geralmente usamos um padrão 'where @var é nulo ou @ var = table.field'. Com um processo armazenado, é provável que você obtenha o mesmo plano de execução, apesar de objetivos bastante diferentes, e, portanto, enfrente problemas de desempenho ou elimine planos em cache com dicas de 'recompilação'. Entretanto, com um simples código que acrescenta um comentário de "assinatura" ao final do sql, podemos forçar planos diferentes com base em quais variáveis ​​eram nulas (não devem ser interpretadas como um plano diferente para todas as combinações de variáveis ​​- somente null vs não nulo).

  • Eu posso fazer mudanças drásticas nos resultados com apenas pequenas alterações rapidamente no sql. Por exemplo, posso ter uma declaração que encerre com dois CTEs, "Raw" e "ReportReady". Não há nada que diga que ambas as CTEs devem ser usadas. Minha instrução sql pode ser:

    ...

    selecione * de {(formato)} "

Isso me permite usar exatamente o mesmo método de lógica de negócios para uma chamada de API simplificada e um relatório que precisa ser mais detalhado, garantindo que eu não duplique a lógica complicada.

  • quando você tem uma regra "procs only", acaba com uma tonelada de redundância na grande maioria do seu sql, que acaba sendo CRUD - você liga todos os parâmetros, lista todos esses parâmetros na assinatura do proc (e agora você está em um arquivo diferente em um projeto diferente), mapeia esses parâmetros simples para suas colunas. Isso cria uma experiência de desenvolvimento bastante independente.

Existem razões válidas para usar procs:

  • Segurança - Você tem outra camada aqui na qual o aplicativo deve passar. Se a conta de serviço do aplicativo não puder tocar nas tabelas, mas apenas tiver permissão de 'executar' nos procs, você terá alguma proteção extra. Isso não o torna um dado, pois tem um custo, mas é uma possibilidade.

  • Reutilização - Embora eu diria que a reutilização deve ocorrer em grande parte na camada de negócios para garantir que você não esteja ignorando regras de negócios não relacionadas ao banco de dados, ainda temos o tipo local de baixo nível de processos e processos de utilitários "usados ​​em todos os lugares".

Existem alguns argumentos que realmente não suportam procs ou são facilmente mitigados pela IMO:

  • Reutilização - mencionei isso acima como um "plus", mas também queria mencionar aqui que a reutilização deve ocorrer em grande parte na camada de negócios. Um processo para inserir um registro não deve ser considerado "reutilizável" quando a camada de negócios também pode estar verificando outros serviços não-db.

  • Inchaço do plano de cache - a única maneira de isso ser um problema é se você estiver concatenando valores em vez de parametrizar. O fato de você raramente obter mais de um plano por processo geralmente o prejudica quando você tem um 'ou' em uma consulta

  • Tamanho da instrução - um kb extra de instruções sql sobre o nome do processo geralmente será insignificante em relação aos dados que retornam. Se tudo bem para Entidades, tudo bem para mim.

  • Vendo a consulta exata - Facilitar a localização de consultas no código é tão simples quanto adicionar o local da chamada como um comentário ao código. Tornar o código copiável do código c # para o ssms é tão fácil quanto a interpolação criativa e o uso de comentários:

        //Usage /*{SSMSOnly_}*/Pure Sql To run in SSMS/*{_SSMSOnly}*/
        const string SSMSOnly_ = "*//*<SSMSOnly>/*";
        const string _SSMSOnly = "*/</SSMSOnly>";
        //Usage /*{NetOnly_}{InterpolationVariable}{_NetOnly}*/
        const string NetOnly_ = "*/";
        const string _NetOnly = "/*";
  • Injeção de SQL - Parametrize suas consultas. Feito. Na verdade, isso pode ser desfeito se o proc estiver usando sql dinâmico.

  • Ignorando a implantação - Também praticamos devops no nível do banco de dados, portanto essa não é uma opção para nós.

  • "Lento no aplicativo, rápido no SSMS" - este é um problema de cache do plano que afeta os dois lados. As opções definidas apenas fazem com que um novo plano seja compilado, o que parece resolver o problema das variáveis ​​THE ONE SET OFF. Isso apenas responde por que você vê resultados diferentes - as opções definidas NÃO corrigem o problema de detectar os parâmetros.

  • Os planos de execução de sql embutido não são armazenados em cache - simplesmente falso. Uma instrução parametrizada, assim como o nome do processo, é rapidamente dividida em hash e, em seguida, um plano é pesquisado por esse hash. É 100% o mesmo.

  • Para ser claro, eu estou falando sobre o código SQL inline bruto não gerado a partir de um ORM - usamos apenas o Dapper, que é um micro ORM, na melhor das hipóteses.

https://weblogs.asp.net/fbouma/38178

https://stackoverflow.com/a/15277/852208

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.