Edukia
Hau Django exekutatzen duten aplikazioetarako da PSQL edo antzeko beste SQL (SQLite, MySQL, etab.) oinarritutako datu-baseekin (DB); transakzioen inguruko arauak baliteke beste DB batzuei ez aplikatzea. Kanpaiaren azpian, Django-ren transakzioak kudeatzeko kodeak honako hau egiten du (hau Django-ren dokumentutik kopiatzen da -korritu behera- nire buletekin argitzeko):
DBrako konexioa lortzen edo sortzen du eta transakzio gisa ezartzen du.
DB eragiketa guztiek blokeoak behar dituzte — blokeo batzuk solteak dira eta beste konexio batzuek baliabide berean eragiketak egiteko aukera emango dute, baina beste blokeo batzuk zorrotzagoak dira; esklusiboak dira eta irakurketa eta idazketa eragiketak blokeatuko dituzte. Erregistroak sortu, eguneratu eta ezabatzeak, baita taulak eta zutabeak aldatzeak, blokeo zorrotzagoak eskuratuko ditu, beste konexio batzuk eragiketak exekutatzeko blokeoa egingo dutenak. Blokeo hauek transakzioaren AMAIERA arte mantentzen dira.
Hau automatikoki egiten da, adibidez, erregistroak taula batean txertatzean; eredu-eragiketa batzuk automatikoki bloke atomikoetan bilduko dira.
Hau eskuz egin daiteke atomic()-ek bilduta dagoen funtzio bati deituz edo atomic() erabiliz adierazpen batekin (testuinguru-kudeatzaile gisa).
Gorde puntuek kanpoko transakzioaren konexio bera erabiliko dute - Django kodea gordetzeko puntuetarako begiratzen baduzu, beste transakzio bat sortzeko dei errekurtsibo bat besterik ez da.
Gorde-puntuak gordetze-puntuetan habia daitezke - pila batek gordetze-puntuen jarraipena egiten du.
Txertatu ondoren, gordetzeko puntua kentzen da aldaketak konprometituz eta gainerako transakziorako eskuragarri jarriz (baina EZ beste konexio batzuk) edo aldaketak atzera botaz.
DBko blokeoak oraindik mantentzen dira DB ez den logika exekutatzen den bitartean, adibidez, transakzio baten gorputzean HTTP eskaera bat bidaltzen badugu, blokeoak mantendu egingo dira eskaera hori bidali eta erantzuna itzultzen den arte. Django-k ez ditu blokeoak askatzen DB eragiketak exekutatzen uzten ditugunean. Sarrailak mantentzen ditu kanpoko transakzioa amaitu ARTE.
Habiaratutako transakzioetan eskuratutako blokeoak ere mantentzen dira kanpoko transakzioa amaitu arte edo habiaratutako transakzioa atzera egin arte.
Transakzioa gelditzen da, atzera egiten da eta blokeoak askatzen dira.
Errorea habiaratutako transakzio batean gertatzen bada, kanpoko transakzioak ez du eraginik izango. Atzera itzulia gertatzen da, baina kanpoko transakzioak etenik gabe jarrai dezake (betiere sortutako errorea atzeman eta kudeatzen bada).
Transakzioek ez dituzte akatsak antzematen, baina horien araberakoak dira aldaketak atzera egin behar diren jakiteko. Hori dela eta, ez biltzeko DB eragiketak try/salbu, baizik eta itzulbiratu transakzio batean eta, ondoren, itzulbiratu transakzioa try/salbu. Hori eginez gero, DB eragiketak huts egiten du eta automatikoki atzera egiten du kanpoko transakzioan eragin gabe.
Azkenik, transakzio osoa konprometituta dago, aldaketak DB erabiltzaile/konexio guztien eskura jarriz. Edo atzera botatzen da, DB transakzioa aurkitu zenean zegoen egoera berean utziz.
Hurrengo blogean, sarrailak goseteak eragindako etenaldiak saihesteko transakzio batean jarri behar duzunari buruz idatziko dut.
Transakzioak ACID DB-ak (PSQL, SQL, azken MongoDB , etab.) ATOMIC (A ACID-en) izatea ahalbidetzen duten mekanismoak dira. Horrek esan nahi du DBrako idazketa guztiak eragiketa bakar gisa egiten direla, nahiz eta konplexua izan. Idazketa arrakastatsua bada, DBko aldaketa guztiek iraun egiten dute eta konexio guztietarako erabilgarri egongo dira aldi berean (idazketa partzialik ez). Idazteak huts egiten badu, aldaketa guztiak atzera egiten dira; berriro ere, ez dago aldaketa partzialik.
Transakzioek arau hauek bermatzen dituzte:
Arau hauei esker, DBko erabiltzaileak eragiketa konplexuak bildu eta eragiketa bakarrean exekutatu ditzake. Adibidez, banku-kontu batetik bestera transferentzia egitea; transakziorik ez bagenu, orduan, bi idazketa eragiketa horien erdian, transferentzia baliogabe bihurtzen duen kentze bat edo baita kontu itxi bat ere egon liteke. Transakzioek trafikoa kontrolatzeko modua ematen diote erabiltzaileari. Gatazkako beste eragiketa guztiak blokeatu ditzakegu transakzioa abian den bitartean.
Eragiketak tauletan eta errenkadetan blokeo batzuen bidez blokeatzen dira. PSQL eta beste SQL aldaeretan, transakzioak BEGIN-ekin sortzen dira; gero blokeoak eskuratzen dira hautatu/txertatu/ezabatu/alter bezalako eragiketa bat exekutatzen denean eta transakzioak COMMIT edo ROLLBACK-ekin amaitzen direnean. Blokeoak askatzen dira COMMIT edo ROLLBACK exekutatzen direnean. Zorionez, Django-k hiru adierazpen hauek erabili behar izan gabe transakzioak sortzeko aukera ematen digu (baina oraindik blokeoez kezkatu behar dugu; horri buruz gehiago hurrengo mezuan).
-- Start a new transaction BEGIN; SELECT … INSERT INTO … UPDATE … DELETE FROM … -- Save the changes COMMIT;
from django.db import transaction from app.core.models import Accounts, Fee, NotificationJob def do_migration(): overdrawn_accounts = Accounts.objects.filter(type='overdrawn') for acount in overdrawn_accounts: create_notification(acount) @transaction.atomic def create_notification(acount: Accounts): # $5 fee for overdraft - 500 because we never store money as float!!! recall = Fee.objects.create(acount=acount, description='Fee for overdraft', amount=500) NotificationJob.objects.create(recall=recall, notification_type='all') acount.status = 'awaiting_payment' acount.save() def do_migration2(): overdawn_account = Accounts.objects.filter(type='overdrawn') for account in overdawn_account: with transaction.atomic(): recall = Fee.objects.create(acount=account, description='Fee for overdraft', amount=500) NotificationJob.objects.create(recall=recall, notification_type='all') account.status = 'awaiting_payment' account.save()
Django-k automatikoki biltzen ditu DB eragiketa guztiak transakzio batean autokonpromiso moduan exekutatzen direnean (behean gehiago). Transakzio esplizituak atomic() dekoratzailea edo testuinguru-kudeatzailea erabiliz sor daitezke (atomic()-rekin) — atomic() erabiltzen denean, DB eragiketa indibidualak EZ dira transakzio batean biltzen edo berehala DBan konprometitzen dira (exekutatzen dira, baina DBan egindako aldaketak ez dira beste erabiltzaileentzat ikusgai). Horren ordez, funtzio osoa transakzio bloke batean bilduta dago eta amaieran konprometitzen da (akatsik sortzen ez bada) - COMMIT exekutatzen da.
Akatsak badira, DB-ko aldaketak atzera egiten dira — ROLLBACK exekutatuko da. Horregatik, transakzioek blokeoei eusten diete amaiera arte; ez dugu nahi mahai baten blokeorik askatu, beste konexio batek taula aldatzea edo irakurtzea, eta gero aldatu edo irakurri berri dena atzera egitera behartuta egon.
ANGRY_CUSTOMER_THRESHOLD = 3 @transaction.atomic def create_notification(acount: Accounts): recall = Fee.objects.create(acount=acount, description='Fee for overdraft', amount=500) NotificationJob.objects.create(recall=recall, notification_type='all') try: # when this completes successfully, the changes will be available to the outer transaction with transaction.atomic(): owner = acount.owner fees = Fee.objects.filter(owner=owner).count() if fees >= ANGRY_CUSTOMER_THRESHOLD: for fee in fees: fee.amount = 0 fee.save() owner.status = 'angry' owner.save() # as long as we catch the error, the outer transaction will not be rolled back except Exception as e: logger.error(f'Error while removings fees for account {acount.id}: {e}') acount.status = 'awaiting_payment' acount.save()
Transakzioak ere habia ditzakegu atomic()-ek bilduta dagoen beste funtzio bati deituz edo transakzio baten barruan testuinguru-kudeatzailea erabiliz. Horri esker, salbatzeko puntuak sor ditzakegu, non eragiketa arriskutsuak saia ditzakegun gainerako transakzioari eragin gabe: barne-transakzio bat detektatzen denean, gordetze-puntu bat sortzen da, barne-transakzioa exekutatzen da eta barne-transakzioak huts egiten badu, datuak gordetzeko puntura itzultzen dira eta kanpoko transakzioak jarraitzen du. Kanpoko transakzioak huts egiten badu, barruko transakzio guztiak atzera egiten dira kanpoko transakzioarekin batera. Transakzioen habiaratzea ekidin daiteke durable=True ezarriz atomic() - honek transakzioak RuntimeError bat sortuko du barne-transakzioren bat detektatzen bada.
Habiaratutako transakzioei buruz gogoratu behar duzuna:
DATABASES = { 'default': { # True by default 'AUTOCOMMIT': False, # ...rest of configs } }
Lehenespenez, Django autocommit moduan exekutatzen da, hau da, DB eragiketa bakoitza transakzio batean bilduta dagoela eta berehala exekutatzen da (konpromisoa, beraz, autokonpromisoa) erabiltzaileak eragiketa esplizituki transakzio batean biltzen badu izan ezik. Honek azpiko DBren transakzio-logika nahasten du - DB batzuek, PSQL bezalakoak, eragiketa guztiak automatikoki biltzen dituzte transakzioetan, beste batzuek ez. Djangok hau egiten badugu, ez dugu azpian dagoen DB logikarekin kezkatu beharrik.
Autokonpromiso modua desaktibatu dezakegu, baina horrek DBn fidatuko gintuzke eragiketak kudeatzeko eta ATOMICak direla ziurtatzeko, eta hori arriskutsua eta deserosoa da garatzaileentzat. Ez litzateke dibertigarria izango, API bat sortzean, transakzioetan zein idazketa-eragiketa dauden asmatu beharko bagenu eta, beraz, guztiz idatziko dira eta ez partzialki.
Hala ere, baliteke autokonpromiso modua desaktibatu nahi izatea DBko konexio kopurua eta exekutatzen ari diren eragiketen sekuentzia ezagutzen baditugu. Eragiketa hauek gatazkak nola desagerrarazi nahi baditugu, autokonpromiso modua desaktibatu eta eraginkortasun batzuk lor ditzakegu:
Alde txarrak datuak usteltzeko arrisku handiagoak dira. Ez du merezi, baina agian bada pentsatu ezin dudan erabilera kasuren bat.