TransactionScope este tentant, dar perfid. Determinarea tipului de tranzacție. Tranzacții cu proceduri stocate

Tranzacții

Tranzacţie este un set de operațiuni care trebuie fie să reușească, fie să eșueze, în ansamblu. Scopul unei tranzacții este de a se asigura că datele sunt întotdeauna într-o stare corectă și consecventă.

De exemplu, luați în considerare o tranzacție care transferă 1000 USD din contul A în contul B. În mod clar, există două tranzacții implicate:

    retrage 1000 USD din contul A;

    adăugați 1000 USD în contul B.

Să presupunem că aplicația a finalizat cu succes pasul 1, dar din cauza unei erori, pasul 2 a eșuat. Acest lucru duce la inconsecvența datelor deoarece valoare totală banii din sistem nu mai sunt exacti. 1000$ lipsesc.

Tranzacțiile evită aceste tipuri de probleme deoarece asigură că modificările sunt efectuate numai în sursa de date dacă toți pașii au succes. Prin urmare, în acest exemplu, dacă pasul 2 eșuează, atunci modificările făcute în pasul 1 nu vor fi trimise în baza de date. Acest lucru asigură că sistemul rămâne într-una dintre cele două stări corecte - inițială (când nu se transferă bani) sau finală (când banii sunt debitați dintr-un cont și creditați în altul).

Tranzacțiile sunt caracterizate de patru proprietăți, numite proprietăți ACID. Aici ACID introduce următoarele concepte:

Atomicitate (atomică)

Toți pașii tranzacției trebuie fie să reușească, fie să fie anulați. Dacă nu toți pașii unei tranzacții sunt finalizați, aceasta nu este tratată ca finalizată.

Consecvență

O tranzacție mută baza de date de la o stare stabilă la alta.

Izolare

Fiecare tranzacție este o entitate independentă. O tranzacție nu ar trebui să afecteze altele care rulează simultan cu ea.

Durabil

Modificările care apar în timpul unei tranzacții sunt stocate permanent pe un mediu de stocare (de obicei un hard disk) înainte ca tranzacția să fie declarată reușită. Înregistrarea este acceptată astfel încât baza de date să poată fi restaurată la o stare corectă chiar și în cazul unei defecțiuni hardware sau rețelei.

Vă rugăm să rețineți că aceste caracteristici ideale ale tranzacției nu sunt întotdeauna complet realizabile. Una dintre probleme se referă la asigurarea izolației. Un SGBD relațional trebuie să blocheze datele, astfel încât alți utilizatori să nu le poată accesa în timp ce o tranzacție este în curs. Cu cât sunt folosite mai multe încuietori și cu cât sunt mai mari, cu atât sunt mai mari șansele ca utilizatorul să nu poată efectua alte lucrări în timp ce tranzacția este în desfășurare. Cu alte cuvinte, uneori există un compromis între paralelismul utilizatorului și izolare.

Tranzacții și aplicații ASP.NET

În aplicațiile ASP.NET puteți folosi trei tip de bază tranzacții. Mai jos este o listă a acestora (de la cel mai puțin la cel mai scump):

    Tranzacții cu proceduri stocate. Aceste tranzacții au loc în întregime în baza de date. Tranzacțiile cu proceduri stocate oferă cea mai buna performanta, deoarece necesită un singur acces la baza de date. Dezavantajul este că trebuie să scrieți toată logica tranzacției folosind instrucțiuni SQL.

    Tranzacții inițiate de client (ADO.NET).. Aceste tranzacții sunt gestionate programatic de codul paginii dvs. web ASP.NET. În culise, folosesc aceleași comenzi ca și tranzacțiile cu proceduri stocate, dar codul folosește un număr de obiecte ADO.NET care ascund detaliile. Dezavantajul este necesitatea apelurilor multiple la baza de date pentru a începe și a efectua o tranzacție.

    Tranzacții COM+. Aceste tranzacții sunt procesate de runtime COM+ pe baza atributelor declarative pe care le adăugați la codul dumneavoastră. Tranzacțiile COM+ folosesc un protocol de comitere în două faze și implică întotdeauna cheltuieli suplimentare. Acestea necesită crearea unei clase separate de bean service.

    În general, obiectele COM+ alegere buna numai dacă tranzacția se întinde pe mai mulți manageri de resurse tranzacționale, deoarece COM+ include suport nativ pentru tranzacții distribuite. De exemplu, o tranzacție COM+ separată poate acoperi interacțiunea cu baza de date SQL Serverși baza de date Oracle.

Deși ADO.NET oferă sprijin bun tranzacții, nu trebuie să le folosiți întotdeauna. De fapt, ori de câte ori este utilizat orice fel de tranzacție, cheltuielile generale sunt introduse automat. În plus, tranzacțiile necesită un fel de blocare a rândurilor de tabel. Prin urmare, tranzacțiile utilizate în mod inutil pot dăuna scalabilității aplicației.

A ajunge cel mai bun rezultat, atunci când implementați tranzacții, trebuie să urmați recomandările enumerate mai jos:

    Păstrați tranzacțiile cât mai scurte posibil.

    Evitați sărirea datelor cu o interogare SELECTîn mijlocul unei tranzacții. În mod ideal, ar trebui să returnați datele înainte de a începe tranzacția. Acest lucru va reduce cantitatea de date blocate de tranzacție.

    Dacă preluați înregistrări, atunci recuperați numai rândurile care sunt necesare pentru a reduce numărul de blocări.

    Acolo unde este posibil, implementați tranzacțiile în cadrul procedurilor stocate, în loc să utilizați tranzacțiile ADO.NET. Ca urmare, tranzacțiile vor începe și vor finaliza mai repede, deoarece serverul bazei de date nu va trebui să interacționeze cu clientul (aplicația web).

    Evitați tranzacțiile care combină multe pachete independente de muncă. Loc pachete individualeîn tranzacții separate.

    Dacă este posibil, evitați actualizările care afectează număr mareînregistrări.

ADO.NET acceptă, de asemenea, un model de tranzacție distribuită la nivel înalt. Cu toate acestea, tranzacția propagată nu este unele tip nou tranzacții, ci pur și simplu o modalitate de a crea o tranzacție inițiată de client care se poate extinde automat la o tranzacție COM+ dacă este necesar. Nu ar trebui să utilizați tranzacții propagate decât dacă aveți cu adevărat nevoie de ele, deoarece este dificil să preziceți impactul lor asupra performanței și scalabilității soluțiilor finale.

Amintiți-vă de regula generală: utilizați tranzacțiile numai atunci când operațiunea o cere. De exemplu, dacă pur și simplu selectați înregistrări dintr-o bază de date sau emiteți o interogare, atunci tranzacțiile nu sunt necesare. Pe de altă parte, dacă inserați o înregistrare de comandă care este legată de o secvență de înregistrări OrderItem dependente, atunci poate fi necesară o tranzacție. În general, tranzacțiile nu sunt niciodată necesare pentru echipe singure, cum ar fi instrucțiunile individuale UPDATE, DELETE sau INSERT, deoarece sunt tranzacționale prin definiție.

Tranzacții cu proceduri stocate

Dacă se poate, atunci cea mai buna solutie va plasa tranzacția în codul procedurii stocate. Acest lucru asigură că codul de pe partea serverului este întotdeauna sub control, eliminând posibilitatea ca un client să țină din greșeală o tranzacție deschisă prea mult timp, creând astfel probleme pentru actualizările datelor altor clienți.

Acest lucru garantează, de asemenea, cea mai bună performanță posibilă, deoarece toate acțiunile pot fi efectuate pe sursa de date, fără a fi nevoie de niciuna networking. În general, cu cât timpul de tranzacție este mai scurt, cu atât este mai mare gradul de paralelism al bazei de date și cu atât este mai mic numărul de interogări care vor fi serializate (așteptând cât există blocarea temporară a înregistrării).

Codul procedurii stocate variază în funcție de baza de date utilizată, dar majoritatea SGBD-urilor relaționale acceptă instrucțiunea SQL ÎNCEPE TRANZACȚIA. Odată ce o tranzacție este începută, toate extrasele ulterioare sunt considerate parte a tranzacției. Tranzacția este finalizată folosind extrasul de cont COMMIT sau ROLLBACK. Dacă acest lucru nu se face, tranzacția va fi anulată automat.

Mai jos este un exemplu de pseudocod care efectuează un transfer de fond între conturi. Aceasta este o versiune simplificată care permite contului să aibă un sold negativ:

CREATE Procedura TransferAmount (@Amount Money @ID_A int @ID_B int) AS ÎNCEPE TRANZACȚIE ACTUALIZARE Conturi SET Balance = Sold + @Amount WHERE AccountID = @ID_A IF (@@ERROR > 0) GOTO PROBLEM UPDATE Accounts SET Balance = Sold - @Amount WHERE AccountID = @ID_B IF (@@ERROR > 0) GOTO PROBLEM -- Nu au fost găsite probleme COMMIT RETURN -- Cod de gestionare a erorilor PROBLEMĂ: ROLLBACK;

RAISERROR(„Actualizarea a eșuat”, 16, 1)

Acest exemplu utilizează capabilitățile limitate de tratare a erorilor ale Transact-SQL (o variantă a SQL implementată în SQL Server). Când utilizați valoarea @@ERROR în Transact-SQL, ar trebui să fiți atenți și să o verificați imediat după fiecare operațiune. Acest lucru se datorează faptului că @@ERROR este resetat la 0 când instrucțiunea SQL se finalizează cu succes. Ca rezultat, dacă prima actualizare eșuează, dar a doua reușește, @@ERROR va returna 0. În acest moment, este prea târziu să o verifici.

Tranzacții ADO.NET inițiate de client Majoritatea furnizorilor de date ADO.NET includ suport pentru baze de date. Tranzacțiile sunt pornite prin intermediul obiectului Connection prin apelarea metodei BeginTransaction() . Această metodă returnează un obiect specific furnizorului Tranzacţie , folosit pentru a controla tranzacția. Toate clasele de tranzacții implementează interfața IDbTransaction

. Furnizorii includ clase precum SqlTransaction, OleDbTransaction, OracleTransaction etc. implementarea acestei interfețe. Clasa Tranzacție oferă două:

metode cheie

Commit()

Această metodă indică finalizarea tranzacției și plasarea modificărilor finalizate în sursa de date.

Rollback()

De obicei, metoda Commit() este utilizată la sfârșitul unei operațiuni. Cu toate acestea, dacă apare vreo excepție în proces, trebuie apelat Rollback(). Să ne uităm la un exemplu de inserare a două înregistrări în tabelul Angajații:

Protected void Page_Load(emițător obiect, EventArgs e) ( șir connectionString = WebConfigurationManager.ConnectionStrings["Northwind"].ConnectionString; SqlConnection con = new SqlConnection(connectionString); SqlCommand cmd1 = new SqlCommand("INSERT (INSERT) VALOR nou ("Petrov", "Vasya")", con); SqlCommand cmd2 = new SqlCommand ("INSERT INTO Employees (LastName, First Name) VALUES ("Ivanov", "Vadim")", con); SqlTransaction tranzacție = null; încercați ( // Deschideți conexiunea și creați o tranzacție con.Open(); transaction = con.BeginTransaction(); // Includeți două comenzi în tranzacție cmd1.Transaction = tranzacție; cmd2.Transaction = tranzacție; // Executați ambele comenzi cmd1 .ExecuteNonQuery( ); cmd2.ExecuteNonQuery(); // Închide tranzacția.Commit(); con.Închidere ();

Vă rugăm să rețineți că simpla creare și efectuare a unei tranzacții nu este suficientă. De asemenea, trebuie să includeți în mod explicit fiecare obiect Command ca parte a unei tranzacții, setând valoarea proprietății lor Command.Transaction la obiectul Tranzacție. Dacă încercați să executați o comandă care nu face parte din tranzacția curentă în timp ce tranzacția este în curs, veți primi o eroare. Cu toate acestea, în viitor, acest model de obiect poate permite suport pentru mai multe tranzacții simultan în cadrul aceleiași conexiuni.

În loc să utilizați obiecte de comandă separate, puteți, de asemenea, să executați același obiect de două ori, modificând pur și simplu proprietatea CommandText pe parcurs (dacă este dinamic Declarație SQL) sau valorile parametrilor săi (dacă aceasta este o comandă parametrizată). De exemplu, dacă comanda se inserează noua intrare, această abordare poate fi folosită pentru a introduce două înregistrări într-o tranzacție.

Pentru a testa proprietatea rollback (anulare) a unei tranzacții, introduceți următoarea linie chiar înainte de a apela metoda Commit() din exemplul anterior:

Aruncă o nouă ApplicationException();

Această linie va arunca o excepție care va face ca tranzacția să fie anulată, asigurându-se că nicio înregistrare nu intră în baza de date.

Deși tranzacțiile ADO.NET gravitează în jurul obiectelor Command și Tranzacție, comenzile subiacente nu sunt diferite de cele utilizate în tranzacțiile cu proceduri stocate. De exemplu, când metoda BeginTransaction() este apelată cu furnizorul date SQL Server, trimite comanda BEGIN TRANSACTION la baza de date.

Tranzacția trebuie finalizată cât mai curând posibil (începută cât mai târziu posibil și finalizată cât mai devreme). În plus, o tranzacție activă blochează diverse resurse implicate, așa că ar trebui să selectați doar acele rânduri de tabel care sunt de fapt necesare.

Niveluri de izolare

Nivelul de izolare determină cât de sensibilă este o tranzacție la modificările efectuate în alte tranzacții în curs. De exemplu, în mod implicit, atunci când tranzacțiile sunt executate independent unele de altele, înregistrările introduse de o tranzacție sunt invizibile pentru cealaltă tranzacție până când prima tranzacție este efectuată.

Conceptul de niveluri de izolare este strâns legat de conceptul de blocare deoarece nivelul de izolare specificat pentru o anumită tranzacție determină tipurile de blocare necesare. Încuietori partajate- Acestea sunt acele blocări care sunt setate atunci când o tranzacție dorește să citească date din baza de date. În timp ce o blocare partajată este ținută pe un tabel, un rând sau un interval, nicio altă tranzacție nu poate modifica datele. Cu toate acestea, mai mult de un utilizator poate folosi o blocare partajată pentru a citi datele în același timp.

Încuietori excepționale sunt blocări care împiedică două sau mai multe tranzacții să modifice simultan datele. O blocare exclusivă este obținută atunci când o tranzacție este pe cale să actualizeze datele și nicio altă blocare nu este ținută în acel moment. Niciun alt utilizator nu poate modifica datele în timp ce acestea sunt blocate exclusiv.

SQL Server acceptă de fapt mai multe tipuri de blocări care lucrează împreună pentru a preveni blocajele și altele situatii neplacute. Pentru Informații suplimentare Pentru informații despre blocare, consultați Ajutorul online SQL Server Books livrat cu SQL Server.

În depozitat Proceduri SQL Server puteți seta niveluri de izolare folosind comanda SETĂ NIVELUL DE IZOLARE A TRANZACȚIILOR. În ADO.NET puteți trece o valoare de enumerare Nivel de izolare metoda supraîncărcată Connection.BeginTransaction(). Tabelul de mai jos prezintă valorile posibile ale acestuia:

Valori de enumerare IsolationLevel
Sens Descriere
Citiți Neangajat

Fără încuietori partajate și fără încuietori exclusive. Acest tip de nivel de izolare este potrivit atunci când doriți să lucrați cu toate datele care îndeplinesc anumite condiții, indiferent dacă au fost comise. Sunt posibile citiri nevalide, dar performanța se îmbunătățește

ReadCommitted

Blocările partajate sunt păstrate în timp ce datele sunt citite de tranzacție. Acest lucru elimină posibilitatea citirilor invalide, dar datele pot fi modificate înainte de finalizarea tranzacției. Acest lucru poate duce la citiri nereproductibile sau scrieri fantomă. Acesta este nivelul de izolare implicit în SQL Server

Instantaneu

Stochează o copie a datelor accesate prin tranzacție. Ca urmare, o tranzacție nu poate vedea modificările efectuate de alte tranzacții. Această abordare limitează blocările deoarece, chiar dacă alte tranzacții dețin blocări asupra datelor, o tranzacție cu acel nivel de izolare va putea citi copii ale datelor. Acest nivel de izolare este acceptat numai în SQL Server 2005 și versiunile ulterioare și trebuie activat prin opțiunea la nivel de bază de date

RepeatableRead

În acest caz, blocările partajate sunt plasate pe toate înregistrările utilizate în interogare. Acest lucru împiedică alți clienți să modifice datele și, de asemenea, previne citirile nereproductibile. Cu toate acestea, înregistrările fantomă sunt posibile

Serializabil

O blocare a intervalului plasată pe datele în uz împiedică alte persoane să actualizeze sau să insereze înregistrări care ar putea intra în interval. Acesta este singurul nivel de izolare care elimină posibilitatea de rânduri fantomă. Cu toate acestea, oferă exclusiv impact negativ pentru oportunitate munca paralela utilizatori și este rar utilizat în scenarii multi-utilizator

Acest tabel prezintă terminologia legată de bazele de date care necesită unele explicații:

Lectură murdară

O citire murdară este o citire care se ocupă de o valoare dintr-o altă tranzacție în așteptare care poate fi anulată ulterior.

Lectură irepetabilă

Dacă citirea nereproducibilă este activată, este posibil să se execute din nou interogarea în aceeași tranzacție și să se obțină date diferite. Acest lucru se datorează faptului că simpla citire nu îi împiedică pe alții să modifice datele în timpul tranzacției. Pentru a preveni citirile nereproductibile, serverul bazei de date trebuie să blocheze rândurile pe care le citește tranzacția dvs.

Rând fantomă

Un rând fantomă este un rând care nu apare la citirea inițială, dar apare atunci când aceleași date sunt citite din nou în aceeași tranzacție. Acest lucru se poate întâmpla dacă un alt utilizator introduce o înregistrare înainte de finalizarea tranzacției. Pentru a preveni apariția rândurilor fantomă atunci când tranzacția dvs. execută o interogare, serverul bazei de date trebuie să aplice blocarea intervalului pe baza clauzei WHERE a acelei interogări.

Vor fi aceste fenomene niște ciudatenii inofensive sau surse potențiale erori - depinde de cerințele specifice. De cele mai multe ori, citirile nereproductibile și rândurile fantomă nu reprezintă probleme serioase, iar eforturile de prevenire a acestora sunt prea costisitoare pentru a avea sens. Cu toate acestea, dacă trebuie să actualizați mai multe rânduri simultan și acele rânduri conțin unele date asociate, este posibil să aveți nevoie de o blocare mai strictă pentru a preveni ca modificările suprapuse să cauzeze probleme de consistență a datelor.

Nivelurile de izolare din tabelul de mai sus sunt ordonate de la cel mai mic la cel mai mare. Nivelul implicit, ReadCommitted, este un compromis bun pentru majoritatea tranzacțiilor. Următorul tabel descrie comportamentul de blocare pentru diferite niveluri de izolare:

Salvați puncte

Ori de câte ori o tranzacție este anulată, rezultatele tuturor comenzilor executate de la începutul tranzacției sunt invalidate. Dar ce se întâmplă dacă doriți să anulați doar o parte din tranzacția curentă? SQL Server are un instrument pentru acest lucru numit puncte de salvare.

Punctele de salvare sunt marcatoare care funcționează ca marcajele. Marcați un anumit punct în fluxul unei tranzacții și apoi puteți reveni la acel punct. Punctul de salvare este setat folosind metoda Tranzacție. Salva(). Rețineți că metoda Save() este disponibilă numai în clasa SqlTransaction deoarece nu face parte din interfața standard IDbTransaction.

Următoarele demonstrează conceptul de utilizare a unui punct de salvare:

// Începe o tranzacție SqlTransaction tran = con.BeginTransaction(); // (Activați și executați unele comenzi într-o tranzacție.) // Marcați un punct de salvare tran.Save("CompletedInsert" ; // (Includeți și executați alte comenzi în tranzacție.) // Dacă este necesar, reveniți la punctul de salvare tran.Rollback("CompletedInsert"); // Commiteți sau derulați întreaga tranzacție tran.Commit();

Observați utilizarea metodei Rollback() cu punctul de salvare ca parametru. Dacă doriți să anulați întreaga tranzacție, omiteți această opțiune. Când o tranzacție este returnată la un punct de salvare, toate punctele de salvare definite după aceea sunt pierdute. Dacă sunt necesare, acestea vor trebui instalate din nou.

ADO.NET 2.0 a fost lansat cu mult timp în urmă, iar odată cu el a apărut ansamblul System.Transactions, care conține clasa TransactionScope - un ghid pentru lume de luminăși utilizarea fără efort a tranzacțiilor. În articolul de astăzi mă voi uita la câteva dintre nuanțe care apar atunci când folosesc această abstracție leaky, dar atât de frumoasă.

Deci, începând cu ADO.NET 2.0, pentru a-și include codul într-o tranzacție, dezvoltatorul trebuie doar să îl plaseze într-un bloc TransactionScope:

Folosind (var transactionScope = new TransactionScope(TransactionScopeOption.Suppress, new TransactionOptions() ( IsolationLevel = IsolationLevel.Serializable )) ( //cod în interiorul tranzacției transactionScope.Complete(); )

Am folosit cel mai mult în constructor parametri importanti- să le privim (în ordine inversă).

Nivel de izolare

Întrebarea dacă să folosiți sau nu RequiresNew și Suppress este, desigur, determinată de cerințele algoritmului, dar am câteva preconcepții despre acest lucru. Faptul este că TransactionScope în modurile RequiresNew și Suppress, în prezența operațiunilor care modifică starea bazei de date, face imposibilă utilizarea vechiului truc atunci când codul de testare de integrare este inclus într-o tranzacție, care este derulat înapoi la sfârșitul testului, restabilind astfel starea bazei de date:

Public void void IntegrationTest() (folosind (nou TransactionScope()) ( // cod de testare // nu apelați Complete ) )

Dacă TransactionScopes sunt create în codul testat în modul Requires, atunci acestea vor fi conectate la testul TransactionScope, ceea ce înseamnă că putem anula toate modificările. Dacă codul conține un TransactionScope în modul RequiresNew sau Suppress, atunci nu vom putea derula înapoi rezultatul muncii lor din testul TransactionScope. Este de remarcat faptul că prezența logicii legată de momentul în care tranzacția este efectuată (ca în exemplul anterior) face, de asemenea, imposibilă utilizarea acestei tehnici.

În cele din urmă, observ că TransactionScope este local firului de execuție (deoarece implementarea sa se bazează pe variabila ThreadStatic). Dacă trebuie să utilizați o tranzacție din mai multe fire, utilizați clasa

Există două tipuri principale de tranzacții: tranzacții de conectare și tranzacții externe. Tranzacția de conectare (de exemplu, SqlTransaction) este legată direct de conexiunea DB (de exemplu, SqlConnection), ceea ce înseamnă că trebuie să continuați să treceți conexiunea - OK în unele cazuri, dar nu permite utilizarea „creare/utilizare/eliberare” și nu permite permite lucrul cu mai multe db. Exemplu (formatat pentru spațiu):

Folosind (IDbTransaction tran = conn.BeginTransaction()) ( încercați ( //codul dvs. tran.Commit(); ) catch ( tran.Rollback(); arunca; ) )

Nu prea dezordonat, dar limitat la conexiunea noastră „conn”. Daca vrem sa contactam diverse metode, trebuie să trecem „conn”.

Alternativa este o tranzacție externă; nou în .NET 2.0, obiectul TransactionScope (system.Transactions.dll) permite utilizarea în întreaga gamă de operațiuni (furnizorii potriviți vor fi înrolați automat într-o tranzacție cu cei din jur). Acest lucru facilitează încorporarea în codul existent (non-tranzacțional) și vorbirea cu mai mulți furnizori (deși DTC va fi implicat dacă vorbiți cu mai mulți).

De exemplu:

Folosind(TransactionScope tran = new TransactionScope()) ( CallAMethodThatDoesSomeWork(); CallAMethodThatDoesSomeMoreWork(); tran.Complete(); )

Rețineți că aceste două metode își pot gestiona propriile conexiuni (deschide/utilizare/închide/dispune), dar vor deveni în tăcere parte a tranzacției exterioare, fără a fi nevoie să treacă nimic.

Dacă codul dvs. are erori, Dispose() va fi apelat fără Complete(), deci va fi anulat. Atașarea așteptată etc. este acceptată, deși nu puteți anula o tranzacție internă, ci puteți efectua o tranzacție externă: dacă cineva este nemulțumit, tranzacția este anulată.

Un alt avantaj al TransactionScope este că nu este legat de baze de date; orice furnizor de tranzacții îl poate folosi. WCF, de exemplu. Sau chiar există unele modele de obiecte care sunt compatibile cu TransactionScope (cum ar fi clasele .NET cu capabilități de rollback - poate mai simple decât memoria, deși eu nu am folosit niciodată această abordare).

În general, un obiect foarte, foarte util.

Câteva avertismente:

  • În SQL Server 2000, TransactionScope va trece imediat la DTC; acest lucru este remediat în SQL Server 2005 și mai sus, poate folosi LTM (mult mai puțin utilitar) până când vorbești cu 2 surse etc. când este promovat la DTC.
  • Există o eroare care înseamnă că poate fi necesar să vă ajustați șirul de conexiune

Protected void Button1_Click(sender object, EventArgs e) (folosind (SqlConnection connection1 = new SqlConnection("Data Source=.\\SQLEXPRESS;AttachDbFilename=|DataDirectory|\\Database.mdf;Integrated Security=True;User Instance=True")) ) ( connection1.Open(); //Începe o tranzacție locală. SqlTransaction sqlTran = connection1.BeginTransaction(); //Enlist o comandă în tranzacția curentă. SqlCommand command = connection1.CreateCommand(); command.Transaction = sqlTran; încercați ( //Executați două comenzi separate. command.CommandText = "inserați în (drname,drspecialization,drday) valori ("a","b","c")"; command.ExecuteNonQuery(); command.CommandText = "inserați în (drname,drspecialization,drday) ("x","y","z")"; „Ambele înregistrări au fost scrise în baza de date.”; rotiți înapoi tranzacția.

sqlTran.Rollback();

) catch (Exception exRollback) ( //Aruncă o excepție InvalidOperationException dacă conexiunea //este închisă sau tranzacția a fost deja anulată //înapoi pe server. Label5.Text = exRollback.Message; ) ) ) ) O tranzacție este executarea unei secvențe de comenzi (construcții SQL) într-o bază de date, care fie este comisă atunci când fiecare comandă este executată cu succes, fie anulată când cel puțin o comandă nu are succes. Cele mai multe SGBD-uri moderne acceptă un mecanism de tranzacție, iar marea majoritate a aplicațiilor client care lucrează cu acestea folosesc tranzacții pentru a-și executa comenzile. Apare întrebarea - de ce sunt necesare tranzacții? Să ne imaginăm că trebuie să introduceți în baza de date BDTur_firm2

postări conexe




în două tabele - „Turiști” și „Informații despre turiști”. Dacă o înregistrare introdusă în tabelul Turişti este incorectă, de exemplu din cauza unui cod turistic specificat incorect, baza de date nu va permite efectuarea modificărilor, dar o înregistrare inutilă va apărea în tabelul Informaţii turistice. Să ne uităm la această situație folosind un exemplu.

Să lansăm Management Studio și să introducem o solicitare într-un formular nou pentru a adăuga două înregistrări:

VALORI (8, „CA 1234567”, „Novosibirsk”, „Rusia”, 1234567, 996548);
Două înregistrări au fost adăugate cu succes la baza de date:

(1 rând(e) afectat) //sau (rânduri procesate: 1)

INSERT INTO Turisti ([Cod Turistic], Nume, Prenume, Patronimic)
VALORI (8, „Tikhomirov”, „Andrey”, „Borisovici”);
INSERT INTO [Informații turistice]([Cod turistic], [Seria pașaport], oraș, țară, telefon, cod poștal)
VALORI (9, „CA 1234567”, „Novosibirsk”, „Rusia”, 1234567, 996548);

Apare un mesaj care spune că este imposibil să inserați prima înregistrare cu o valoare a câmpului cheie existentă. O a doua intrare, totuși, este adăugată la tabel:

Mesaj 2627, nivel 14, stare 1, linie 1

Declarația a fost terminat.
(rânduri procesate: 1)

Să extragem conținutul ambelor tabele folosind următoarea interogare dublă:

SELECT * FROM Turisti
SELECTAȚI * DIN [Informații turistice]

În tabelul „Informații despre turiști” ultima intrare a fost adăugat fără nicio legătură cu intrarea din tabelul „Turiști” (Fig. 99). Pentru a evita greseli similare, trebuie să aplicați tranzacția.

Orez. 99. Conținutul tabelelor „Turiști” și „Informații despre turiști” - eșec de comunicare

Să ștergem toate înregistrările introduse din ambele tabele și să formalizăm constructele SQL originale ca o tranzacție:

ÎNCEPE TRAN

INSERT INTO Turisti ([Cod Turistic], Nume, Prenume, Patronimic)
VALORI (8, „Tikhomirov”, „Andrey”, „Borisovici”);


Oraș, țară, telefon, cod poștal)
VALORI (8, „CA 1234567”, „Novosibirsk”, „Rusia”, 1234567, 996548); SELECTAȚI @OshibkiTabliciInfoTourists=@@EROARE IF @OshibkiTabliciTourists=0 ȘI @OshibkiTabliciInfoTourists=0 COMMIT TRAN ELSE
ROLLBACK TRAN

Începutul unei tranzacții este anunțat folosind comanda BEGIN TRAN. În continuare, sunt creați doi parametri - @OshibkiTabliciTourists, OshibkiTabliciInfoTourists pentru a colecta erori. După prima solicitare, returnăm valoarea pe care funcția încorporată @@ERROR o atribuie primului parametru:

SELECTAȚI @OshibkiTabliciTourists=@@EROARE

Facem același lucru după a doua solicitare pentru un alt parametru:

SELECTAȚI @OshibkiTabliciInfoTourists=@@EROARE

Verificăm valorile ambilor parametri, care ar trebui să fie egale cu zero dacă nu există erori:

DACĂ @OshibkiTabliciTourists=0 ȘI @OshibkiTabliciInfoTourists=0

În acest caz, confirmăm tranzacția (în în acest caz, efectuarea de modificări) folosind comanda COMMIT TRAN. În caz contrar, dacă valoarea a cel puțin unuia dintre parametrii @OshibkiTabliciTourists și @OshibkiTabliciInfoTourists este diferită de zero, anulați tranzacția folosind comanda ROLLBACK TRAN.

După finalizarea tranzacției, apare un mesaj care indică faptul că au fost adăugate două rânduri:

(rânduri procesate: 1)
(rânduri procesate: 1)

Să schimbăm din nou codul turistic în a doua cerere:

ÎNCEPE TRAN
DECLARE @OshibkiTabliciTourists int, @OshibkiTabliciInfoTourists int
INSERT INTO Turisti ([Cod Turistic], Nume, Prenume, Patronimic)
VALORI (8, „Tikhomirov”, „Andrey”, „Borisovici”);
SELECTAȚI @OshibkiTabliciTourists=@@EROARE
INSERT INTO [Informații turistice]([Cod turistic], [Seria pașapoarte],
Oraș, țară, telefon, cod poștal)
VALORI (9, „CA 1234567”, „Novosibirsk”, „Rusia”, 1234567, 996548);
SELECTAȚI @OshibkiTabliciInfoTourists=@@EROARE
DACĂ @OshibkiTabliciTourists=0 ȘI @OshibkiTabliciInfoTourists=0
COMMIT TRAN
ALTE
ROLLBACK TRAN

Începem tranzacția - apare exact același mesaj ca și în cazul utilizării interogărilor obișnuite:

Mesaj 2627, nivel 14, stare 1, linie 3
Încălcarea constrângerii PRIMARY KEY „PK_Tourists”. Nu se poate introduce cheia duplicată în obiectul „dbo.Tourists”.
Declarația a fost reziliată. (rânduri procesate: 1)

Cu toate acestea, acum nu au fost aduse modificări celui de-al doilea tabel (Fig. 100).

Orez. 100. Conținutul tabelelor „Turiști” și „Informații turistice” după o tranzacție nereușită

Mesajul (1 rând(e) afectat) indică faptul că o înregistrare a fost „adăugata”, în acest caz înseamnă doar că a doua construcție SQL a fost corectă și înregistrarea ar putea fi adăugată dacă tranzacția a avut succes.

Astfel, mecanismul tranzacției menține integritatea datelor celor două tabele, prevenind încălcarea acesteia prin adăugarea de date incorecte.

Tranzacții înZGOMOT . NET

Să trecem acum să luăm în considerare tranzacțiile în ADO .NET. Să creăm o nouă consolă Aplicație ușoară Tranzacţie. Să stabilim sarcina: transferați aceleași date în două tabele - „Turiști” și „Informații despre turiști”.

Lista completă această aplicație arata asa:

folosind System;
folosind System.Data.SqlClient;
spațiu de nume EasyTransaction (
clasa Clasa 1 (
static void Main(string args)
{
//Creează o conexiune
SqlConnection conn = new SqlConnection();
conn.ConnectionString = @"Sursa de date=.\SQLEXPRESS;AttachDbFilename=" +
@"D:\VMI\For ADO\BDTur_firmSQL2.mdf" +
„Securitate integrată=True;Connect Timeout=30;User Instance=True”;
conn.Open();
SqlCommand myCommand = conn.CreateCommand();
//Creează o tranzacție
myCommand.Transaction = conn.BeginTransaction(System.Data.IsolationLevel.Serializable);
incearca (
myCommand.CommandText =
„INSERT INTO Turists ([Cod turistic], Prenume, Prenume, Patronimic)” +
„VALORI (9, „Tikhomirov”, „Andrey”, „Borisovici”);
myCommand.CommandText = "INSERT INTO [Informații turistice]" +
" ([Cod turistic], [Seria pașaport], oraș, țară, telefon, cod poștal)" +
„VALORI (9, „CA 1234567”, „Novosibirsk”, „Rusia”, 1234567, 996548)”;
myCommand.ExecuteNonQuery();
//Confirmați tranzacția
Console.WriteLine("Transferul de date a fost finalizat cu succes");
}
prinde (excepție ex) (
//Respinge tranzacția
Console.WriteLine("A apărut o eroare la transferul datelor: "+ ex.Message);
}
in sfarsit(
conn.Close();
}
) final Main
) Clasa finală
) sfârșitul spațiului de nume

Înainte de a începe aplicația, ștergem din nou toate înregistrările adăugate din tabele. Dacă cererea este executată cu succes, apare un mesaj corespunzător și înregistrările sunt adăugate în tabele (Fig. 101).

Orez. 101. Aplicația EasyTransaction. Tranzacție finalizată

Rularea acestei aplicații din nou face ca tranzacția să fie respinsă - înregistrările cu aceleași valori nu pot fi inserate chei primare(Fig. 102).

Orez. 102. Aplicația EasyTransaction. Tranzacția a fost refuzată

Execuția uneia sau mai multor proceduri stocate poate fi formalizată ca o tranzacție - de fapt, structura generală este următoarea:

//Creează o conexiune
...vezi exemplu de aplicație EasyTransaction
//Creează o tranzacție
myCommand.Transaction = conn.BeginTransaction();
incearca (
//Executați comenzi, apelați una sau mai multe proceduri stocate
//Confirmați tranzacția
myCommand.Transaction.Commit();
}
prinde (excepție ex) (
//Respinge tranzacția
myCommand.Transaction.Rollback();
}
in sfarsit(
//Închide conexiunea
conn.Close();
}

Atunci când mai mulți utilizatori ai aceleiași baze de date efectuează tranzacții, pot apărea următoarele probleme:

1. Citituri murdare - citire „murdară”. Primul utilizator începe o tranzacție care modifică datele. În acest moment, un alt utilizator (sau o tranzacție creată de el) preia date parțial modificate care nu sunt corecte.

2. Lectură nerepetabilă - citire nerepetabilă. Primul utilizator începe o tranzacție care modifică datele. În acest moment, un alt utilizator începe și încheie o altă tranzacție. Primul utilizator care citește din nou datele (de exemplu, dacă tranzacția sa include mai multe Instrucțiuni SELECT) primește un alt set de înregistrări.

3. Phantom reads - citirea fantomelor. Primul utilizator începe o tranzacție care selectează datele din tabel. În acest moment, un alt utilizator începe și finalizează o tranzacție care introduce sau șterge înregistrări. Primul utilizator va primi un alt set de date care conține fantome - rânduri șterse sau modificate.

Pentru a rezolva aceste probleme, au fost dezvoltate patru niveluri de izolare a tranzacțiilor:

1. Citiți neangajat. O tranzacție poate citi date pe care alte tranzacții operează. Utilizarea acestui nivel de izolare poate duce la toate problemele enumerate.

2. Citiți angajat. O tranzacție nu poate citi datele pe care operează alte tranzacții. Utilizarea acestui nivel de izolare elimină problema citirilor murdare.

3. Citire repetabilă. O tranzacție nu poate citi datele pe care operează alte tranzacții. De asemenea, alte tranzacții nu pot citi datele pe care operează această tranzacție. Utilizarea acestui nivel de izolare elimină toate problemele, cu excepția citirii fantome.

4. Serializabil. Tranzacția este complet izolată de alte tranzacții. Utilizarea acestui nivel de izolare elimină complet toate problemele.

Nivelul implicit este Read committed. ÎN Ajutor Microsoft SQL Server 2005 oferă un tabel care ilustrează diferite niveluri izolație (Fig. 103).

Orez. 103. Niveluri de izolare Microsoft SQL Server 2005

Utilizarea celui mai înalt nivel de izolare (Serializable) înseamnă cea mai mare securitate și în același timp cea mai scăzută performanță - toate tranzacțiile sunt executate în serie, cea ulterioară este obligată să aștepte finalizarea celei anterioare. În schimb, folosirea celui mai scăzut nivel (Read uncommitted) înseamnă performanță maximă și absență completă securitate. Cu toate acestea, este imposibil să se ofere recomandări universale cu privire la utilizarea acestor niveluri - în fiecare situație specifică decizia va depinde de structura bazei de date și de natura interogărilor care se execută.

Pentru a seta nivelul de izolare, utilizați următoarea comandă:

SETĂ NIVELUL DE IZOLARE A TRANZACȚIILOR
CITEȘTE NEANGAJAT
sau CITEȘTE ANGAJAT
sau CITIRE REPETABILĂ
sau SERIALIZABIL

De exemplu, într-o tranzacție care adaugă două înregistrări, nivelul de izolare este specificat după cum urmează:

ÎNCEPE TRAN SET NIVEL DE IZOLARE A TRANZACȚIILOR SERIALIZABIL
DECLARE @OshibkiTabliciTourists int, @OshibkiTabliciInfoTourists int
...
ROLLBACK TRAN

În ADO .NET, nivelul de izolare poate fi setat la crearea unei tranzacții:

myCommand.Transaction = conn.BeginTransaction(System.Data.IsolationLevel.Serializable); În plus, sunt acceptate încă două niveluri (Fig. 104):

1. Haos. O tranzacție nu poate suprascrie alte tranzacții neangajate cu izolare ridicată, dar poate suprascrie modificările efectuate fără a utiliza tranzacții. Datele procesate de tranzacția curentă nu sunt blocate;

2. Nespecificat. Un nivel de izolare distinct care poate fi aplicat, dar nu poate fi definit. O tranzacție cu acest nivel poate fi utilizată pentru a seta propriul nivel de izolare.

Orez. 104. Determinarea nivelului tranzacției

Tranzacțiile asigură integritatea bazei de date atunci când se dezvoltă aplicații pe mai multe niveluri, utilizarea acestora este o regulă obligatorie.

  • Serghei Savenkov

    un fel de recenzie „scurtă”... de parcă s-ar grăbi undeva