Database
 sql >> Database >  >> RDS >> Database

Un altro argomento per le stored procedure

Questo è uno di quei dibattiti religiosi/politici che imperversano da anni:dovrei usare stored procedure o dovrei inserire query ad hoc nella mia applicazione? Sono sempre stato un sostenitore delle stored procedure, per alcuni motivi:

  • Non posso implementare le protezioni SQL injection se la query è costruita nel codice dell'applicazione. Gli sviluppatori potrebbero essere a conoscenza delle query parametrizzate ma nulla li obbliga a usarle correttamente.
  • Non posso ottimizzare una query incorporata nel codice sorgente dell'applicazione, né applicare best practice.
  • Se trovo un'opportunità per l'ottimizzazione delle query, per distribuirla, devo ricompilare e ridistribuire il codice dell'applicazione, invece di modificare semplicemente la procedura memorizzata.
  • Se la query viene utilizzata in più punti dell'applicazione o in più applicazioni e richiede una modifica, devo cambiarla in più punti, mentre con una procedura memorizzata devo modificarla solo una volta (problemi di distribuzione a parte).
  • Vedo anche che molte persone stanno abbandonando le stored procedure a favore degli ORM. Per le applicazioni semplici probabilmente andrà bene, ma man mano che la tua applicazione diventa più complessa, è probabile che il tuo ORM preferito sia semplicemente incapace di eseguire determinati modelli di query, *forzandoti* a utilizzare una procedura memorizzata. Se supporta le procedure memorizzate, cioè.

    Anche se trovo ancora tutti questi argomenti piuttosto convincenti, non sono ciò di cui voglio parlare oggi; Voglio parlare di prestazioni.

    Molti argomenti là fuori diranno semplicemente "le stored procedure funzionano meglio!" Ciò potrebbe essere stato marginalmente vero a un certo punto, ma poiché SQL Server ha aggiunto la possibilità di compilare a livello di istruzioni anziché a livello di oggetto e ha acquisito potenti funzionalità come optimize for ad hoc workloads , questo non è più un argomento molto forte. L'ottimizzazione dell'indice e modelli di query ragionevoli hanno un impatto molto maggiore sulle prestazioni rispetto alla scelta di utilizzare una stored procedure; nelle versioni moderne, dubito che troverai molti casi in cui la stessa identica query mostra notevoli differenze di prestazioni, a meno che tu non stia introducendo anche altre variabili (come l'esecuzione di una procedura localmente rispetto a un'applicazione in un data center diverso in un continente diverso).

    Detto questo, c'è un aspetto delle prestazioni che viene spesso trascurato quando si tratta di query ad hoc:la cache del piano. Possiamo utilizzare optimize for ad hoc workloads per evitare che i piani monouso riempiano la nostra cache (Kimberly Tripp (@KimberlyLTripp) di SQLskills.com ha alcune ottime informazioni al riguardo qui) e ciò influisce sui piani monouso indipendentemente dal fatto che le query vengano eseguite da una stored procedure oppure vengono eseguiti ad hoc. Un impatto diverso che potresti non notare, indipendentemente da questa impostazione, è quando identico i piani occupano più slot nella cache a causa delle differenze in SET opzioni o delta minori nel testo della query effettivo. L'intero fenomeno "lento nell'applicazione, veloce negli SSMS" ha aiutato molte persone a risolvere problemi relativi a impostazioni come SET ARITHABORT . Oggi volevo parlare delle differenze tra i testi delle query e dimostrare qualcosa che sorprende le persone ogni volta che ne parlo.

    Cache da masterizzare

    Diciamo che abbiamo un sistema molto semplice che esegue AdventureWorks2012. E solo per dimostrare che non aiuta, abbiamo abilitato optimize for ad hoc workloads :

    EXEC sp_configure 'show advanced options', 1;
    GO
    RECONFIGURE WITH OVERRIDE;
    GO
    EXEC sp_configure 'optimize for ad hoc workloads', 1;
    GO
    RECONFIGURE WITH OVERRIDE;

    E poi libera la cache del piano:

    DBCC FREEPROCCACHE;

    Ora generiamo alcune semplici variazioni a una query altrimenti identica. Queste variazioni possono potenzialmente rappresentare stili di codifica per due diversi sviluppatori:lievi differenze negli spazi bianchi, maiuscole/minuscole, ecc.

    SELECT TOP (1) SalesOrderID, OrderDate, SubTotal
    FROM Sales.SalesOrderHeader
    WHERE SalesOrderID >= 75120
    ORDER BY OrderDate DESC;
    GO
     
    -- change >= 75120 to > 75119 (same logic since it's an INT)
    GO
     
    SELECT TOP (1) SalesOrderID, OrderDate, SubTotal
    FROM Sales.SalesOrderHeader
    WHERE SalesOrderID > 75119
    ORDER BY OrderDate DESC;
    GO
     
    -- change the query to all lower case
    GO
     
    select top (1) salesorderid, orderdate, subtotal
    from sales.salesorderheader
    where salesorderid > 75119
    order by orderdate desc;
    GO
     
    -- remove the parentheses around the argument for top
    GO
     
    select top 1 salesorderid, orderdate, subtotal
    from sales.salesorderheader
    where salesorderid > 75119
    order by orderdate desc;
    GO 
     
    -- add a space after top 1
    GO
     
    select top 1  salesorderid, orderdate, subtotal
    from sales.salesorderheader
    where salesorderid > 75119
    order by orderdate desc;
    GO
     
    -- remove the spaces between the commas
    GO
     
    select top 1  salesorderid,orderdate,subtotal
    from sales.salesorderheader
    where salesorderid > 75119
    order by orderdate desc;
    GO

    Se eseguiamo quel batch una volta e poi controlliamo la cache del piano, vediamo che abbiamo 6 copie, essenzialmente, dello stesso identico piano di esecuzione. Questo perché il testo della query è un hash binario, il che significa che maiuscole e minuscole e spazi bianchi fanno la differenza e possono rendere le query altrimenti identiche univoche per SQL Server.

    SELECT [text], size_in_bytes, usecounts, cacheobjtype
    FROM sys.dm_exec_cached_plans AS p
    CROSS APPLY sys.dm_exec_sql_text(p.plan_handle) AS t
    WHERE LOWER(t.[text]) LIKE '%ales.sales'+'orderheader%';

    Risultati:

    testo dimensione_in_byte conteggi di utilizzo tipocacheobj
    seleziona il primo ID ordine di vendita,o... 272 1 Stubo del piano compilato
    seleziona il primo ID ordine di vendita, … 272 1 Stubo del piano compilato
    seleziona il primo ID ordine di vendita, o... 272 1 Stubo del piano compilato
    seleziona top (1) salesorderid,... 272 1 Stubo del piano compilato
    SELEZIONA TOP (1) SalesOrderID,... 272 1 Stubo del piano compilato
    SELEZIONA TOP (1) SalesOrderID,... 272 1 Stubo del piano compilato

    Risultati dopo la prima esecuzione di query "identiche"

    Quindi, questo non è del tutto dispendioso, poiché l'impostazione ad hoc ha consentito a SQL Server di archiviare solo piccoli stub alla prima esecuzione. Se eseguiamo nuovamente il batch (senza liberare la cache delle procedure), vediamo un risultato leggermente più allarmante:

    testo dimensione_in_byte conteggi di utilizzo tipocacheobj
    seleziona il primo ID ordine di vendita,o... 49.152 1 Piano compilato
    seleziona il primo ID ordine di vendita, … 49.152 1 Piano compilato
    seleziona il primo ID ordine di vendita, o... 49.152 1 Piano compilato
    seleziona top (1) salesorderid,... 49.152 1 Piano compilato
    SELEZIONA TOP (1) SalesOrderID,... 49.152 1 Piano compilato
    SELEZIONA TOP (1) SalesOrderID,... 49.152 1 Piano compilato

    Risultati dopo la seconda esecuzione di query "identiche"

    La stessa cosa accade per le query parametrizzate, indipendentemente dal fatto che la parametrizzazione sia semplice o forzata. E la stessa cosa accade quando l'impostazione ad hoc non è abilitata, tranne per il fatto che accade prima.

    Il risultato netto è che questo può produrre un sacco di rigonfiamento della cache del piano, anche per query che sembrano identiche, fino a due query in cui uno sviluppatore rientra con una scheda e l'altro con 4 spazi. Non devo dirti che cercare di imporre questo tipo di coerenza in una squadra può essere da noioso a impossibile. Quindi nella mia mente questo fa un forte cenno alla modularizzazione, cedendo a DRY e centralizzando questo tipo di query in un'unica stored procedure.

    Un avvertimento

    Ovviamente, se si inserisce questa query in una procedura memorizzata, ne avrai solo una copia, quindi eviti completamente la possibilità di avere più versioni della query con testo della query leggermente diverso. Si potrebbe anche sostenere che utenti diversi potrebbero creare la stessa stored procedure con nomi diversi e che in ciascuna stored procedure è presente una leggera variazione del testo della query. Sebbene possibile, penso che rappresenti un problema completamente diverso. :-)