Database
 sql >> Base de Dados >  >> RDS >> Database

Outro argumento para procedimentos armazenados


Este é um daqueles debates religiosos/políticos que vem ocorrendo há anos:devo usar procedimentos armazenados ou devo colocar consultas ad hoc em meu aplicativo? Sempre fui um defensor dos procedimentos armazenados, por alguns motivos:
  1. Não consigo implementar proteções de injeção de SQL se a consulta for construída no código do aplicativo. Os desenvolvedores podem estar cientes das consultas parametrizadas, mas nada os força a usá-las corretamente.
  2. Não consigo ajustar uma consulta incorporada no código-fonte do aplicativo nem aplicar práticas recomendadas.
  3. Se eu encontrar uma oportunidade de ajuste de consulta, para implantá-la, preciso recompilar e reimplantar o código do aplicativo, em vez de apenas alterar o procedimento armazenado.
  4. Se a consulta for usada em vários locais no aplicativo ou em vários aplicativos e exigir uma alteração, terei que alterá-la em vários locais, enquanto com um procedimento armazenado só preciso alterá-la uma vez (problemas de implantação lado).

Também vejo que muitas pessoas estão abandonando os procedimentos armazenados em favor dos ORMs. Para aplicativos simples, isso provavelmente funcionará bem, mas à medida que seu aplicativo se torna mais complexo, é provável que você descubra que seu ORM de escolha é simplesmente incapaz de executar determinados padrões de consulta, *forçando* você a usar um procedimento armazenado. Se ele oferece suporte a procedimentos armazenados, isso é.

Embora eu ainda ache todos esses argumentos bastante convincentes, não é sobre eles que quero falar hoje; Quero falar sobre desempenho.

Muitos argumentos por aí vão simplesmente dizer, "procedimentos armazenados têm melhor desempenho!" Isso pode ter sido marginalmente verdadeiro em algum momento, mas como o SQL Server adicionou a capacidade de compilar no nível da instrução em vez do nível do objeto, e adquiriu funcionalidades poderosas como optimize for ad hoc workloads , este já não é um argumento muito forte. O ajuste de índice e os padrões de consulta sensatos têm um impacto muito maior no desempenho do que a escolha de usar um procedimento armazenado; nas versões modernas, duvido que você encontre muitos casos em que exatamente a mesma consulta exiba diferenças de desempenho perceptíveis, a menos que você também esteja introduzindo outras variáveis ​​(como executar um procedimento localmente versus um aplicativo em um data center diferente em um continente diferente).

Dito isso, há um aspecto de desempenho que geralmente é negligenciado ao lidar com consultas ad hoc:o cache do plano. Podemos usar optimize for ad hoc workloads para evitar que os planos de uso único preencham nosso cache (Kimberly Tripp (@KimberlyLTripp) do SQLskills.com tem ótimas informações sobre isso aqui), e isso afeta os planos de uso único, independentemente de as consultas serem executadas de dentro de um procedimento armazenado ou são executados ad hoc. Um impacto diferente que você pode não notar, independentemente dessa configuração, é quando idêntico os planos ocupam vários slots no cache devido a diferenças em SET opções ou deltas menores no texto da consulta real. Todo o fenômeno "lento no aplicativo, rápido no SSMS" ajudou muitas pessoas a resolver problemas envolvendo configurações como SET ARITHABORT . Hoje eu queria falar sobre as diferenças de texto de consulta e demonstrar algo que surpreende as pessoas toda vez que eu falo sobre isso.

Cache para gravar


Digamos que temos um sistema muito simples executando o AdventureWorks2012. E apenas para provar que isso não ajuda, ativamos optimize for ad hoc workloads :
EXEC sp_configure 'mostrar opções avançadas', 1;GORECONFIGURE WITH OVERRIDE;GOEXEC sp_configure 'otimizar para cargas de trabalho ad hoc', 1;GORECONFIGURE WITH OVERRIDE;

E então libere o cache do plano:
DBCC FREEPROCCACHE;

Agora, geramos algumas variações simples para uma consulta que, de outra forma, é idêntica. Essas variações podem representar estilos de codificação para dois desenvolvedores diferentes – pequenas diferenças no espaço em branco, maiúsculas/minúsculas, etc.
SELECT TOP (1) SalesOrderID, OrderDate, SubTotalFROM Sales.SalesOrderHeaderWHERE SalesOrderID>=75120ORDER BY OrderDate DESC;GO -- alterar>=75120 para> 75119 (mesma lógica, pois é um INT)GO SELECT TOP (1) SalesOrderID, OrderDate, SubTotalFROM Sales.SalesOrderHeaderWHERE SalesOrderID> 75119ORDER BY OrderDate DESC;GO -- altere a consulta para todas as letras minúsculasGO selecione top (1) salesorderid, orderdate, subtotalfrom sales.salesorderheaderwhere salesorderid> 75119order by orderdate desc;GO -- remova os parênteses ao redor o argumento para topGO selecione top 1 salesorderid, orderdate, subtotalfrom sales.salesorderheaderwhere salesorderid> 75119order by orderdate desc;GO -- adicione um espaço após top 1GO selecione top 1 salesorderid, orderdate, subtotalfrom sales.salesorderheaderwhere salesorderid> 75119order by orderdate desc;GO -- remova os espaços entre as vírgulasGO selecione top 1 salesorderid,orderdate,subtotalfrom sales.salesorderheaderwhere salesorderid> 75119order by orderdate desc;GO 
Se executarmos esse lote uma vez e verificarmos o cache do plano, veremos que temos 6 cópias, essencialmente, do mesmo plano de execução. Isso ocorre porque o texto da consulta tem hash binário, o que significa que maiúsculas e minúsculas fazem diferença e podem fazer com que consultas idênticas pareçam exclusivas do SQL Server.
SELECT [texto], size_in_bytes, usecounts, cacheobjtypeFROM sys.dm_exec_cached_plans AS pCROSS APPLY sys.dm_exec_sql_text(p.plan_handle) AS tWHERE LOWER(t.[texto]) LIKE '%ales.sales'+'orderheader%'; 

Resultados:
texto size_in_bytes usecounts cacheobjtype
selecione os 1 principais salesorderid,o… 272 1 Stub de plano compilado
selecione os 1 principais salesorderid, … 272 1 Stub de plano compilado
selecione os 1 principais salesorderid, o… 272 1 Stub de plano compilado
selecione os principais (1) salesorderid,… 272 1 Stub de plano compilado
SELECT TOP (1) SalesOrderID,… 272 1 Stub de plano compilado
SELECT TOP (1) SalesOrderID,… 272 1 Stub de plano compilado

Resultados após a primeira execução de consultas "idênticas"

Portanto, isso não é totalmente um desperdício, pois a configuração ad hoc permitiu que o SQL Server armazenasse apenas pequenos stubs na primeira execução. Se executarmos o lote novamente (sem liberar o cache do procedimento), veremos um resultado um pouco mais alarmante:
texto size_in_bytes usecounts cacheobjtype
selecione os 1 principais salesorderid,o… 49.152 1 Plano compilado
selecione os 1 principais salesorderid, … 49.152 1 Plano compilado
selecione os 1 principais salesorderid, o… 49.152 1 Plano compilado
selecione os principais (1) salesorderid,… 49.152 1 Plano compilado
SELECT TOP (1) SalesOrderID,… 49.152 1 Plano compilado
SELECT TOP (1) SalesOrderID,… 49.152 1 Plano compilado

Resultados após a segunda execução de consultas "idênticas"

O mesmo acontece com as consultas parametrizadas, independentemente de a parametrização ser simples ou forçada. E a mesma coisa acontece quando a configuração ad hoc não está habilitada, exceto que isso acontece mais cedo.

O resultado líquido é que isso pode produzir muito inchaço no cache do plano, mesmo para consultas que parecem idênticas – até duas consultas em que um desenvolvedor recua com uma guia e o outro recua com 4 espaços. Não preciso dizer que tentar impor esse tipo de consistência em uma equipe pode ser tedioso ou impossível. Então, em minha mente, isso dá um forte aceno à modularização, cedendo ao DRY e centralizando esse tipo de consulta em um único procedimento armazenado.

Uma advertência


Obviamente, se você colocar essa consulta em um procedimento armazenado, terá apenas uma cópia dela, evitando totalmente a possibilidade de ter várias versões da consulta com um texto de consulta ligeiramente diferente. Agora, você também pode argumentar que usuários diferentes podem criar o mesmo procedimento armazenado com nomes diferentes e em cada procedimento armazenado há uma pequena variação do texto da consulta. Embora seja possível, acho que isso representa um problema totalmente diferente. :-)