Viene chiesto di non utilizzare le transazioni e di utilizzare una soluzione alternativa per simularne una


43

Sto sviluppando T-SQL da diversi anni e sto approfondendo ulteriormente, continuando a imparare tutto ciò che posso su tutti gli aspetti della lingua. Di recente ho iniziato a lavorare in una nuova società e ho ricevuto quello che penso sia uno strano suggerimento in merito alle transazioni. Non usarli mai. Utilizzare invece una soluzione alternativa che simula una transazione. Questo deriva dal nostro DBA che lavora in un database con molte transazioni e, successivamente, molti blocchi. Il database in cui lavoro principalmente non soffre di questo problema e vedo che le transazioni sono state utilizzate in passato.

Capisco che il blocco è previsto con le transazioni in quanto è nella loro natura farlo e se puoi scappare senza usarne uno, fallo sicuramente. Ma ho molte occasioni in cui ogni istruzione DEVE essere eseguita con successo. Se uno fallisce, tutti non devono impegnarsi.

Ho sempre mantenuto il campo di applicazione delle mie transazioni il più stretto possibile, usato sempre insieme a SET XACT_ABORT ON e sempre all'interno di TRY / CATCH.

Esempio:

CREATE SCHEMA someschema;
GO


CREATE TABLE someschema.tableA
(id   INT NOT NULL IDENTITY(1, 1) PRIMARY KEY, 
 ColA VARCHAR(10) NOT NULL
);
GO

CREATE TABLE someschema.tableB
(id   INT NOT NULL IDENTITY(1, 1) PRIMARY KEY, 
 ColB VARCHAR(10) NOT NULL
); 
GO


CREATE PROCEDURE someschema.ProcedureName @ColA VARCHAR(10), 
                                          @ColB VARCHAR(10)
AS
SET NOCOUNT, XACT_ABORT ON;
BEGIN
BEGIN TRY
    BEGIN TRANSACTION;

    INSERT INTO someschema.tableA(ColA)
    VALUES(@ColA);

    INSERT INTO someschema.tableB(ColB)
    VALUES(@ColB);

--Implement error
    SELECT 1/0 

    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    IF @@trancount > 0
    BEGIN
        ROLLBACK TRANSACTION;
    END;
    THROW;
    RETURN;
END CATCH;
END;
GO

Ecco cosa mi hanno suggerito di fare.

GO



CREATE PROCEDURE someschema.ProcedureNameNoTransaction @ColA VARCHAR(10), 
                                                       @ColB VARCHAR(10)
AS
SET NOCOUNT ON;
BEGIN
BEGIN TRY
    DECLARE @tableAid INT;
    DECLARE @tableBid INT;

    INSERT INTO someschema.tableA(ColA)
    VALUES(@ColA);
    SET @tableAid = SCOPE_IDENTITY();

    INSERT INTO someschema.tableB(ColB)
    VALUES(@ColB);
    SET @tableBid = SCOPE_IDENTITY();

--Implement error
    SELECT 1/0 

END TRY
BEGIN CATCH
    DELETE FROM someschema.tableA
    WHERE id = @tableAid;

    DELETE FROM someschema.tableB
    WHERE id = @tableBid;

    THROW;

    RETURN;
END CATCH;
END;
GO

La mia domanda alla comunità è la seguente. Ha senso come soluzione alternativa valida per le transazioni?

La mia opinione da ciò che so sulle transazioni e su ciò che la soluzione propone è che no, questa non è una soluzione praticabile e introduce molti punti di errore.

Nella soluzione alternativa, vedo che si verificano quattro transazioni implicite. I due inserimenti nel tentativo e quindi altre due transazioni per le eliminazioni nel fermo. "Annulla" gli inserti, ma senza eseguire il rollback di nulla, quindi non viene eseguito il rollback di nulla.

Questo è un esempio molto semplice per dimostrare il concetto che stanno suggerendo. Alcune delle effettive procedure memorizzate che ho fatto in questo modo le rendono esaustivamente lunghe e difficili da gestire perché il "rollback" di più set di risultati rispetto a due valori di parametro in questo esempio diventa piuttosto complicato come si può immaginare. Dal momento che il "rollback" è stato fatto manualmente ora, l'opportunità di perdere qualcosa perché reale.

Un altro problema che penso esista è per i timeout o le connessioni interrotte. Viene ancora eseguito il rollback? Questa è la mia comprensione del motivo per cui SET XACT_ABORT ON dovrebbe essere utilizzato in modo che in questi casi la transazione venga ripristinata.

Grazie per il tuo feedback in anticipo!


4
I commenti che non hanno lo scopo dichiarato sono stati eliminati o spostati nella risposta Wiki della community.
Paul White dice GoFundMonica

Risposte:


61

Non è possibile non utilizzare le transazioni in SQL Server (e probabilmente qualsiasi altro adeguato RDBMS). In assenza di limiti di transazione espliciti ( begin transaction... commit) ogni istruzione SQL avvia una nuova transazione, che viene implicitamente impegnata (o ripristinata) dopo il completamento (o il fallimento) dell'istruzione.

La simulazione di transazione suggerita dalla persona che si presenta come "DBA" non è in grado di garantire tre delle quattro proprietà richieste dell'elaborazione della transazione, poiché risolve solo errori "soft" e non è in grado di gestire errori "hard", come disconnessioni di rete, interruzioni di corrente, guasti del disco e così via.

  • Atomicità: fallire. Se si verifica un errore "grave" nel mezzo della pseudo-transazione, la modifica sarà non atomica.

  • Coerenza: fallire. Da quanto precede deriva che i tuoi dati saranno in uno stato incoerente a seguito di un errore "difficile".

  • Isolamento: fallito. È possibile che una pseudo-transazione concorrente modifichi alcuni dei dati modificati dalla pseudo-transazione prima che la tua venga completata.

  • Durabilità: successo. Le modifiche apportate saranno durature, il server del database lo garantirà; questa è l'unica cosa che l'approccio del tuo collega non può rovinare.

I blocchi sono un metodo ampiamente utilizzato ed empiricamente efficace per garantire l'ACIDITÀ delle transazioni in tutti i tipi o RDBMS (questo sito ne è un esempio). Trovo molto improbabile che un DBA casuale possa trovare una soluzione migliore al problema della concorrenza rispetto a centinaia, forse migliaia di informatici e ingegneri che hanno costruito alcuni sistemi di database interessanti negli ultimi, cosa, 50? 60 anni? (Mi rendo conto che questo è in qualche modo fallace come argomento di "appello all'autorità", ma mi atterrò comunque.)

In conclusione, ignora il consiglio del tuo "DBA" se puoi, combatti se hai lo spirito e torna qui con problemi di concorrenza specifici se si presentano.


14

Ci sono alcuni errori che sono così gravi che il blocco CATCH non viene mai inserito. Dalla documentazione

Errori con gravità pari o superiore a 20 che interrompono l'elaborazione dell'attività del Motore di database di SQL Server per la sessione. Se si verifica un errore con gravità 20 o superiore e la connessione al database non viene interrotta, TRY ... CATCH gestirà l'errore.

Attenzioni, come richieste di interruzione client o connessioni client interrotte.

Quando la sessione viene chiusa da un amministratore di sistema usando l'istruzione KILL.

...

Compilare errori, come errori di sintassi, che impediscono l'esecuzione di un batch.

Errori che si verificano ... a causa della risoluzione differita del nome.

Molti di questi sono facili da produrre tramite SQL dinamico. Le dichiarazioni di annullamento come quelle mostrate non proteggeranno i tuoi dati da tali errori.


2
Giusto - e se non altro, il client che muore durante l'esecuzione del codice costituirebbe un errore "così grave che il blocco CATCH non viene mai inserito". Non importa quanto ti fidi del software (non solo del tuo codice, ma OGNI parte di TUTTI gli stack software coinvolti), c'è sempre la possibilità di un guasto hardware (di nuovo, potenzialmente in qualsiasi punto della catena) che ti blocca in qualsiasi momento . Tenere presente questo è una buona difesa contro il pensiero gentile che porta a questo tipo di "soluzione".
Il

2
Inoltre, potresti essere una vittima dello stallo. I blocchi CATCH vengono eseguiti ma vengono lanciati se provano a scrivere nel database.
Giosuè,

10

i-one : la soluzione alternativa che ti viene proposta rende possibile (almeno) violare "A" dell'ACID . Ad esempio, se SP viene eseguito da un client remoto e si interrompono le connessioni, potrebbe verificarsi "commit" / "rollback" parziale, poiché il server può terminare la sessione tra due inserimenti / eliminazioni (e interrompere l'esecuzione di SP prima che raggiunga la fine) .

Ha senso come soluzione alternativa valida per le transazioni?

dan-guzman : No, ilCATCHblocco non viene mai eseguito in caso di timeout della query perché l'API client ha annullato il batch. Senza una transazione,SET XACT_ABORT ONnon è possibile eseguire il rollback di nient'altro che la dichiarazione corrente.

tibor-karaszi : hai 4 transazioni, il che significa più registrazione nel file di registro delle transazioni. Ricordare che ogni transazione richiede una scrittura sincrona dei record di registro fino a quel momento, ovvero si ottengono prestazioni peggiori anche da quell'aspetto quando si utilizzano molte transazioni.

rbarryyoung : se stanno ottenendo molti blocchi, devono correggere il design dei dati, razionalizzare l'ordine di accesso alla tabella o utilizzare un livello di isolamento più appropriato. Stanno assumendo che i loro problemi (e l'incapacità di capirlo) diventeranno il tuo problema. La prova di milioni di altri database è che non lo farà.

Inoltre, ciò che stanno cercando di implementare manualmente è effettivamente una concorrenza ottimistica per i poveri. Quello che dovrebbero fare invece è utilizzare alcune delle migliori concorrenza ottimistiche al mondo, già integrate in SQL Server. Questo va al punto di isolamento sopra. Con ogni probabilità devono passare da qualsiasi livello di isolamento della concorrenza pessimistico che stanno attualmente utilizzando a uno dei livelli di isolamento della concorrenza ottimistica, SNAPSHOToppure READ_COMMITTED_SNAPSHOT. Questi faranno effettivamente la stessa cosa del loro codice manuale, tranne che lo faranno correttamente.

ross-presser : se hai processi estremamente lunghi - come succede qualcosa oggi e la prossima settimana qualcosa deve seguire, e se la cosa della prossima settimana fallisce, allora quella di oggi deve fallire retroattivamente - potresti voler esaminare le saghe . A rigor di termini questo è al di fuori del database, in quanto richiede un bus di servizio.


5

Il codice della cattiva idea sarà solo più costoso da sistemare.

Se ci sono problemi di blocco utilizzando la transazione esplicita (rollback / commit), indirizza il tuo DBA a Internet per alcune grandi idee per affrontare i problemi.

Ecco un modo per alleviare il blocco: https://www.sqlservercentral.com/articles/using-indexes-to-reduce-blocking-in-concurrent-transactions

Gli indici riducono il numero di ricerche che devono verificarsi in una tabella / pagina per trovare una riga / serie di righe. Sono generalmente visti come un metodo per ridurre i tempi di esecuzione per le query SELECT * e giustamente. Non sono considerati adatti per le tabelle coinvolte in un gran numero di AGGIORNAMENTI. In effetti, in questi casi gli INDICI risultano sfavorevoli poiché aumentano il tempo impiegato per completare le query di AGGIORNAMENTO.

Ma non è sempre così. Analizzando leggermente in profondità l'esecuzione di un'istruzione UPDATE, scopriamo che anche questo comporta prima l'esecuzione di un'istruzione SELECT. Questo è uno scenario speciale e spesso visto in cui le query aggiornano serie di righe reciprocamente esclusive. Gli INDICI qui possono portare a un aumento significativo delle prestazioni del motore di database contrariamente alla credenza popolare.


4

La falsa strategia di transazione è pericolosa perché consente problemi di concorrenza che le transazioni impediscono specificamente. Considera che nel secondo esempio qualsiasi dato potrebbe essere cambiato tra le istruzioni.

Le cancellazioni di transazioni false non sono GARANTITE per l'esecuzione o il completamento. Se il server del database si spegne durante la transazione falsa, alcuni ma non tutti gli effetti rimarranno. Inoltre, non è garantito che abbiano successo nel dire il rollback di una transazione.

Questa strategia potrebbe funzionare con gli inserti, ma sicuramente non funzionerebbe con gli aggiornamenti o le eliminazioni (nessuna istruzione SQL della macchina del tempo).

Se la rigida concorrenza delle transazioni sta causando il blocco, ci sono molte soluzioni, anche quelle che riducono il livello di protezione ... questi sono il modo corretto per risolvere il problema.

Il DBA offre una soluzione che potrebbe funzionare correttamente se esistesse un solo utente del database, ma è assolutamente inadatto per qualsiasi tipo di utilizzo serio.


4

Questo non è un problema di programmazione, piuttosto è un problema interpersonale / di cattiva comunicazione. Molto probabilmente il tuo "DBA" è preoccupato per i blocchi, non per le transazioni.

Le altre risposte spiegano già perché devi usare le transazioni ... Voglio dire che è ciò che fanno RDBMS, senza transazioni usate correttamente non c'è integrità dei dati, quindi mi concentrerò su come risolvere il vero problema, ovvero: scoprire perché il tuo "DBA" ha sviluppato un'allergia alle transazioni e lo ha convinto a cambiare idea.

Penso che questo ragazzo stia confondendo "uno scenario particolare in cui il cattivo codice ha portato a prestazioni terribili" con "tutte le transazioni sono cattive". Non mi aspetto che un DBA competente commetta questo errore, quindi è davvero strano. Forse ha avuto una brutta esperienza con un codice terribile?

Prendi in considerazione uno scenario come questo:

BEGIN
UPDATE or DELETE some row, which takes locks it
...do something that takes a while
...perform other queries
COMMIT

Questo stile di utilizzo delle transazioni contiene un blocco (o più blocchi), il che significa che altre transazioni che colpiscono le stesse righe dovranno attendere. Se i blocchi vengono mantenuti a lungo, e soprattutto se molte altre transazioni vogliono bloccare le stesse righe, ciò può compromettere le prestazioni.

Quello che potresti fare è chiedergli perché ha questa idea curiosamente sbagliata di non utilizzare le transazioni, quali tipi di query erano problematiche, ecc. Quindi cerca di convincerlo che eviterai sicuramente scenari negativi simili, che monitorerai l'utilizzo del blocco e spettacolo, rassicurarlo, ecc.

Quello che ti sta dicendo è "non toccare il cacciavite!" quindi il codice che hai inserito nella tua domanda sta fondamentalmente usando un martello per guidare una vite. Un'opzione molto migliore è convincerlo che sai come usare un cacciavite ...

Mi vengono in mente diversi esempi ... beh, erano su MySQL ma anche quello dovrebbe funzionare.

C'era un forum dove l'aggiornamento dell'indice full-text ha richiesto del tempo. Quando un utente ha inviato un post, la transazione aggiorna la tabella degli argomenti per aumentare il conteggio dei post e la data dell'ultimo post (bloccando così la riga dell'argomento), quindi inserisce il post e la transazione manterrà il blocco fino al completamento dell'aggiornamento dell'indice full-text e il COMMIT è stato fatto.

Dato che questo funzionava su un rustbucket con troppa poca RAM, l'aggiornamento di detto indice full-text spesso portava a diversi secondi di intenso IO casuale sulla singola unità a rotazione lenta nella scatola.

Il problema era che le persone che facevano clic sull'argomento causavano una query per aumentare il conteggio delle visualizzazioni sull'argomento, il che richiedeva anche un blocco nella riga dell'argomento. Pertanto, nessuno poteva visualizzare l'argomento mentre il suo indice full-text stava aggiornando. Voglio dire, la riga potrebbe essere letta, ma aggiornarla si bloccherebbe.

Ancor peggio, la pubblicazione aggiornerebbe il conteggio dei post sulla tabella dei forum principali e avrebbe anche bloccato il blocco mentre l'aggiornamento dell'indice full-text ... che bloccava l'intero forum per alcuni secondi e causava l'accumulo di tonnellate di richieste nella coda del web server .

La soluzione era quella di prendere i blocchi nell'ordine corretto: INIZIA, inserisci il post e aggiorna l'indice full-text senza prendere alcun blocco, quindi aggiorna rapidamente le righe dell'argomento / forum con il conteggio dei post e la data dell'ultimo post e COMMIT. Ciò ha risolto completamente il problema. Stava solo spostando alcune domande, davvero semplice.

In questo caso, le transazioni non erano il problema ... Stava acquisendo un blocco non necessario prima di un'operazione prolungata. Altri esempi di cose da evitare mentre si tiene un blocco in una transazione: attesa dell'input dell'utente, accesso a molti dati non memorizzati nella cache da unità a rotazione lenta, IO di rete, ecc.

Certo, a volte, non hai scelta e devi fare una lunga elaborazione mentre tieni serrature ingombranti. Ci sono dei trucchi al riguardo (operare su una copia dei dati, ecc.) Ma molto spesso il collo di bottiglia delle prestazioni deriva da un blocco che non è stato acquisito intenzionalmente e il semplice riordino delle query risolve il problema. Ancora meglio, è essere consapevoli dei blocchi eseguiti durante la scrittura delle query ...

Non ripeterò le altre risposte, ma in realtà ... uso le transazioni. Il tuo problema è convincere il tuo "DBA", non aggirare la caratteristica più importante di un database ...


3

TLDR: utilizzare il livello di isolamento adeguato .

Come hai notato correttamente, l'approccio senza transazioni e con il recupero "manuale" può essere molto complesso. L'elevata complessità normalmente richiede molto più tempo per implementarlo e molto più tempo per correggere gli errori (perché la complessità porta a più errori nell'implementazione). Significa che tale approccio può costare molto di più al cliente.

La preoccupazione principale del tuo collega "dba" è la prestazione. Uno dei modi per migliorarlo è utilizzare il livello di isolamento adeguato. Supponiamo di avere una procedura che fornisca all'utente alcuni tipi di dati generali. Tale procedura non deve necessariamente utilizzare il livello di isolamento SERIALIZZABILE. In molti casi, LEGGERE UNCOMMITTED può essere abbastanza sufficiente. Significa che tale procedura non verrà bloccata dalla transazione che crea o modifica alcuni dati.

Ti suggerirei di rivedere tutte le funzioni / procedure esistenti nel tuo database, valutare il ragionevole livello di isolamento per ognuno, spiegare i vantaggi in termini di prestazioni per il tuo cliente. Quindi regolare queste funzioni / procedure di conseguenza.


2

Puoi anche decidere di utilizzare le tabelle OLTP in memoria. Ovviamente usano ancora le transazioni, ma non ci sono blocchi.
Invece di bloccare tutte le operazioni avranno esito positivo, ma durante la fase di commit il motore verificherà i conflitti di transazione e uno dei commit potrebbe non riuscire. Microsoft utilizza il termine "blocco ottimistico".
Se il problema del ridimensionamento è causato dal conflitto tra due operazioni di scrittura, ad esempio due transazioni simultanee che tentano di aggiornare la stessa riga, OLTP in memoria consente il successo di una transazione e fallisce l'altra transazione. La transazione non riuscita deve essere reinviata in modo esplicito o implicito, riprovando la transazione.
Altro su: In memoria OLTP


-5

Esiste un modo per aggirare le transazioni in misura limitata, ovvero cambiando il modello di dati in modo che sia più orientato agli oggetti. Quindi, anziché archiviare, ad esempio, dati demografici su una persona in più tabelle e metterli in relazione tra loro e richiedere transazioni, potresti avere un unico documento JSON che memorizza tutto ciò che conosci su quella persona in un singolo campo. Ovviamente capire fino a che punto si estende il dominio è un'altra sfida di progettazione, fatta meglio dagli sviluppatori e non dai DBA

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.