Databastransaktioner¶
Django ger dig några sätt att kontrollera hur databastransaktioner hanteras.
Hantering av databastransaktioner¶
Djangos standardbeteende för transaktioner¶
Djangos standardbeteende är att köra i autocommit-läge. Varje fråga bekräftas omedelbart till databasen, såvida inte en transaktion är aktiv. Se nedan för detaljer.
Django använder transaktioner eller sparpunkter automatiskt för att garantera integriteten i ORM-operationer som kräver flera frågor, särskilt delete() och update() frågor.
Djangos klass TestCase
kapslar också in varje test i en transaktion av prestandaskäl.
Koppling av transaktioner till HTTP-förfrågningar¶
Ett vanligt sätt att hantera transaktioner på webben är att varje förfrågan paketeras i en transaktion. Ange ATOMIC_REQUESTS
till True
i konfigurationen för varje databas för vilken du vill aktivera detta beteende.
Det fungerar på följande sätt. Innan en view-funktion anropas startar Django en transaktion. Om svaret produceras utan problem, förbinder Django transaktionen. Om vyn producerar ett undantag rullar Django tillbaka transaktionen.
Du kan utföra undertransaktioner med hjälp av sparpunkter i din vykod, vanligtvis med kontexthanteraren atomic()
. I slutet av vyn kommer dock antingen alla eller inga av ändringarna att bekräftas.
Varning
Även om enkelheten i den här transaktionsmodellen är tilltalande, gör den den också ineffektiv när trafiken ökar. Att öppna en transaktion för varje vy innebär en viss overhead. Effekten på prestandan beror på frågemönstren i din applikation och på hur väl din databas hanterar låsning.
Transaktioner per förfrågan och strömmande svar
När en vy returnerar en StreamingHttpResponse
, kommer läsning av innehållet i svaret ofta att exekvera kod för att generera innehållet. Eftersom vyn redan har returnerats körs sådan kod utanför transaktionen.
Generellt sett är det inte tillrådligt att skriva till databasen medan ett streamingsvar genereras, eftersom det inte finns något vettigt sätt att hantera fel efter att svaret har börjat skickas.
I praktiken innebär denna funktion att varje vyfunktion omsluts av dekoratorn atomic()
som beskrivs nedan.
Observera att endast exekveringen av din vy är innesluten i transaktionerna. Middleware körs utanför transaktionen, och det gör även rendering av mallsvar.
När ATOMIC_REQUESTS
är aktiverat är det fortfarande möjligt att förhindra att vyer körs i en transaktion.
- non_atomic_requests(using=None)[source]¶
Denna dekorator upphäver effekten av
ATOMIC_REQUESTS
för en given vy:from django.db import transaction @transaction.non_atomic_requests def my_view(request): do_stuff() @transaction.non_atomic_requests(using="other") def my_other_view(request): do_stuff_on_the_other_database()
Det fungerar bara om det tillämpas på själva vyn.
Explicit kontroll av transaktioner¶
Django tillhandahåller ett enda API för att styra databastransaktioner.
- atomic(using=None, savepoint=True, durable=False)[source]¶
Atomicity är den definierande egenskapen för databastransaktioner. med
atomic
kan vi skapa ett kodblock inom vilket atomiciteten i databasen garanteras. Om kodblocket slutförs framgångsrikt överförs ändringarna till databasen. Om det finns ett undantag rullas ändringarna tillbaka.atomiska
block kan vara nästlade. I detta fall, när ett inre block slutförs framgångsrikt, kan dess effekter fortfarande rullas tillbaka om ett undantag uppstår i det yttre blocket vid en senare tidpunkt.Ibland är det bra att se till att ett
atomiskt
block alltid är det ytterstaatomiska
blocket, så att alla databasändringar bekräftas när blocket avslutas utan fel. Detta kallas för durability och kan uppnås genom att ställa indurable=True
. Om detatomiska
blocket är kapslat i ett annat block ger det upphov till ettRuntimeError
.atomic
är användbar både som en decorator:from django.db import transaction @transaction.atomic def viewfunc(request): # This code executes inside a transaction. do_stuff()
och som en kontexthanterare:
from django.db import transaction def viewfunc(request): # This code executes in autocommit mode (Django's default). do_stuff() with transaction.atomic(): # This code executes inside a transaction. do_more_stuff()
Genom att paketera
atomic
i ett try/except-block möjliggörs en naturlig hantering av integritetsfel:from django.db import IntegrityError, transaction @transaction.atomic def viewfunc(request): create_parent() try: with transaction.atomic(): generate_relationships() except IntegrityError: handle_exception() add_children()
I det här exemplet kan du, även om
generate_relationships()
orsakar ett databasfel genom att bryta en integritetsbegränsning, köra frågor iadd_children()
, och ändringarna fråncreate_parent()
finns fortfarande kvar och är bundna till samma transaktion. Observera att alla operationer som försökts igenerate_relationships()
redan kommer att ha rullats tillbaka på ett säkert sätt närhandle_exception()
anropas, så undantagshanteraren kan också operera på databasen om det behövs.Undvik att fånga undantag inuti
atomic
!När ett
atomiskt
block avslutas tittar Django på om det avslutades normalt eller med ett undantag för att avgöra om det ska bekräftas eller rullas tillbaka. Om du fångar och hanterar undantag inuti ettatomiskt
block kan du dölja för Django att ett problem har uppstått. Detta kan resultera i oväntat beteende.Detta är främst ett problem för
DatabaseError
och dess underklasser somIntegrityError
. Efter ett sådant fel bryts transaktionen och Django kommer att utföra en rollback i slutet av detatomiska
blocket. Om du försöker köra databasfrågor innan rollback sker, kommer Django att höja enTransactionManagementError
. Du kan också stöta på detta beteende när en ORM-relaterad signalhanterare ger upphov till ett undantag.Det korrekta sättet att fånga upp databasfel är runt ett
atomiskt
block som visas ovan. Om det behövs, lägg till ett extraatomic
block för detta ändamål. Det här mönstret har en annan fördel: det avgränsar uttryckligen vilka operationer som ska rullas tillbaka om ett undantag inträffar.Om du fångar upp undantag som orsakas av råa SQL-frågor är Djangos beteende ospecificerat och databasberoende.
Du kan behöva återställa appstatus manuellt när du rullar tillbaka en transaktion.
Värdena för en modells fält återställs inte när en transaktion rullas tillbaka. Detta kan leda till ett inkonsekvent modelltillstånd om du inte manuellt återställer de ursprungliga fältvärdena.
Till exempel, om
MyModel
har ettactive
-fält, säkerställer detta utdrag attif obj.active
-kontrollen i slutet använder rätt värde om uppdateringen avactive
tillTrue
misslyckas i transaktionen:from django.db import DatabaseError, transaction obj = MyModel(active=False) obj.active = True try: with transaction.atomic(): obj.save() except DatabaseError: obj.active = False if obj.active: ...
Detta gäller även alla andra mekanismer som kan hålla appens tillstånd, t.ex. cache eller globala variabler. Om koden t.ex. proaktivt uppdaterar data i cacheminnet efter att ett objekt har sparats, rekommenderas att du använder transaction.on_commit() istället, för att skjuta upp cacheändringar tills transaktionen faktiskt genomförs.
För att garantera atomicitet inaktiverar
atomic
vissa API:er. Om du försöker göra en commit, roll back eller ändra autocommit-läget för databasanslutningen inom ettatomic
-block kommer ett undantag att uppstå.atomic
tar ettusing
argument som bör vara namnet på en databas. Om detta argument inte tillhandahålls använder Django databasen"default"
.Under huven, Djangos kod för transaktionshantering:
öppnar en transaktion när den går in i det yttersta
atomiska
blocket;skapar en sparpunkt när du går in i ett inre
atomiskt
block;frigör eller rullar tillbaka till sparpunkten när ett inre block avslutas;
genomför eller rullar tillbaka transaktionen när det yttersta blocket avslutas.
Du kan inaktivera skapandet av sparpunkter för inre block genom att ställa in argumentet
savepoint
tillFalse
. Om ett undantag inträffar kommer Django att utföra en rollback genom att avsluta det första överordnade blocket med en savepoint om det finns en, och det yttersta blocket annars. Atomicitet garanteras fortfarande av den yttre transaktionen. Detta alternativ bör endast användas om omkostnaderna för sparpunkter är märkbara. Det har nackdelen att det bryter mot felhanteringen som beskrivs ovan.Du kan använda
atomic
när autocommit är avstängt. Det kommer bara att använda sparpunkter, även för det yttersta blocket.
Överväganden om prestanda
Öppna transaktioner innebär en prestandakostnad för din databasserver. För att minimera denna overhead bör du hålla dina transaktioner så korta som möjligt. Detta är särskilt viktigt om du använder atomic()
i långvariga processer, utanför Djangos request / response-cykel.
Autocommit¶
Varför Django använder autocommit¶
I SQL-standarderna startar varje SQL-fråga en transaktion, såvida inte en transaktion redan är aktiv. Sådana transaktioner måste då uttryckligen bekräftas eller rullas tillbaka.
Detta är inte alltid bekvämt för applikationsutvecklare. För att lindra detta problem tillhandahåller de flesta databaser ett autocommit-läge. När autocommit är aktiverat och ingen transaktion är aktiv, blir varje SQL-fråga inkapslad i en egen transaktion. Med andra ord startar inte bara varje sådan fråga en transaktion, utan transaktionen blir också automatiskt bekräftad eller rullad tillbaka, beroende på om frågan lyckades.
PEP 249, Python Database API Specification v2.0, kräver att autocommit initialt ska vara avstängt. Django åsidosätter denna standard och slår på autocommit.
För att undvika detta kan du avaktivera transaktionshanteringen, men det rekommenderas inte.
Avaktivera transaktionshantering¶
Du kan helt inaktivera Djangos transaktionshantering för en viss databas genom att ställa in AUTOCOMMIT
till False
i dess konfiguration. Om du gör detta kommer Django inte att aktivera autocommit och kommer inte att utföra några commits. Du kommer att få det vanliga beteendet hos det underliggande databasbiblioteket.
Detta kräver att du uttryckligen bekräftar varje transaktion, även de som startas av Django eller av tredjepartsbibliotek. Därför används detta bäst i situationer där du vill köra din egen transaktionskontrollerande middleware eller göra något riktigt konstigt.
Utföra åtgärder efter commit¶
Ibland behöver du utföra en åtgärd som är relaterad till den aktuella databastransaktionen, men bara om transaktionen har överförts. Exempel på detta kan vara en bakgrundsuppgift, ett e-postmeddelande eller en inaktivering av cacheminnet.
on_commit()
gör det möjligt att registrera callbacks som kommer att utföras efter att den öppna transaktionen har bekräftats:
Skicka en funktion, eller en valfri anropsbar funktion, till on_commit()
:
from django.db import transaction
def send_welcome_email(): ...
transaction.on_commit(send_welcome_email)
Callbacks får inte några argument, men du kan binda dem med functools.partial()
:
from functools import partial
for user in users:
transaction.on_commit(partial(send_invite_email, user=user))
Callbacks anropas efter att den öppna transaktionen har bekräftats. Om transaktionen i stället rullas tillbaka (vanligtvis när ett ohanterat undantag uppstår i ett atomic()
-block), kommer återkallelsen att kasseras och aldrig anropas.
Om du anropar on_commit()
medan det inte finns någon öppen transaktion, kommer återuppringningen att utföras omedelbart.
Det är ibland användbart att registrera callbacks som kan misslyckas. Genom att ange robust=True
kan nästa callback utföras även om den aktuella callbacken ger upphov till ett undantag. Alla fel som härrör från Pythons klass Exception
fångas upp och loggas till loggern django.db.backends.base
.
Du kan använda TestCase.captureOnCommitCallbacks()
för att testa callbacks som registrerats med on_commit()
.
Spara poäng¶
Sparpunkter (d.v.s. nästlade atomic()
-block) hanteras korrekt. Det vill säga, en on_commit()
callable som registreras efter en savepoint (i ett nästlat atomic()
block) kommer att anropas efter att den yttre transaktionen har committats, men inte om en rollback till den savepointen eller någon tidigare savepoint inträffade under transaktionen:
with transaction.atomic(): # Outer atomic, start a new transaction
transaction.on_commit(foo)
with transaction.atomic(): # Inner atomic block, create a savepoint
transaction.on_commit(bar)
# foo() and then bar() will be called when leaving the outermost block
Å andra sidan, när en sparpunkt rullas tillbaka (på grund av att ett undantag har uppstått), kommer den inre anropsbarheten inte att anropas:
with transaction.atomic(): # Outer atomic, start a new transaction
transaction.on_commit(foo)
try:
with transaction.atomic(): # Inner atomic block, create a savepoint
transaction.on_commit(bar)
raise SomeError() # Raising an exception - abort the savepoint
except SomeError:
pass
# foo() will be called, but not bar()
Verkställighetsordning¶
On-commit-funktioner för en viss transaktion utförs i den ordning de registrerades.
Hantering av undantag¶
Om en on-commit-funktion som registrerats med robust=False
inom en viss transaktion ger upphov till ett undantag som inte fångats upp, kommer inga senare registrerade funktioner i samma transaktion att köras. Detta är samma beteende som om du själv hade kört funktionerna sekventiellt utan on_commit()
.
Tidpunkt för verkställande¶
Dina callbacks exekveras efter en lyckad commit, så ett fel i en callback kommer inte att leda till att transaktionen rullas tillbaka. De exekveras villkorligt när transaktionen lyckas, men de är inte del av transaktionen. För de avsedda användningsfallen (e-postmeddelanden, bakgrundsuppgifter etc.) bör detta vara bra. Om det inte är det (om din uppföljningsåtgärd är så kritisk att dess misslyckande bör innebära att själva transaktionen misslyckas), då vill du inte använda on_commit()
-kroken. Istället kanske du vill ha two-phase commit som psycopg Two-Phase Commit protocol support och optional Two-Phase Commit Extensions in the Python DB-API specification.
Callbacks körs inte förrän autocommit återställs på anslutningen efter commit (eftersom alla frågor som görs i en callback annars skulle öppna en implicit transaktion och förhindra att anslutningen går tillbaka till autocommit-läge).
När funktionen är i autocommit-läge och utanför ett atomic()
-block kommer den att köras omedelbart, inte vid commit.
On-commit-funktioner fungerar endast med autocommit mode och atomic()
(eller ATOMIC_REQUESTS
) transaktions-API. Om du anropar on_commit()
när autocommit är inaktiverat och du inte befinner dig inom ett atomic block kommer det att resultera i ett fel.
Användning i tester¶
Djangos TestCase
-klass omsluter varje test i en transaktion och rullar tillbaka transaktionen efter varje test för att tillhandahålla testisolering. Detta innebär att ingen transaktion någonsin faktiskt genomförs, vilket innebär att dina on_commit()
callbacks aldrig kommer att köras.
Du kan övervinna denna begränsning genom att använda TestCase.captureOnCommitCallbacks()
. Detta fångar dina on_commit()
callbacks i en lista, så att du kan göra påståenden om dem, eller emulera transaktionen genom att anropa dem.
Ett annat sätt att övervinna begränsningen är att använda TransactionTestCase
istället för TestCase
. Detta kommer att innebära att dina transaktioner är bekräftade och att callbacks kommer att köras. Men TransactionTestCase
rensar databasen mellan testerna, vilket är betydligt långsammare än TestCase
:s isolering.
Varför ingen rollback-krok?¶
En rollback-krok är svårare att implementera på ett robust sätt än en commit-krok, eftersom en mängd olika saker kan orsaka en implicit rollback.
Om t.ex. din databasanslutning bryts på grund av att din process dödades utan möjlighet att stängas av på ett elegant sätt, kommer din rollback-krok aldrig att köras.
Men det finns en lösning: istället för att göra något under det atomiska blocket (transaktionen) och sedan ångra det om transaktionen misslyckas, använd on_commit()
för att skjuta upp det till efter att transaktionen har lyckats. Det är mycket lättare att ångra något som man aldrig gjorde från början!
API:er på låg nivå¶
Varning
Föredra alltid atomic()
om det alls är möjligt. Det tar hänsyn till varje databas egenheter och förhindrar ogiltiga operationer.
API:erna på låg nivå är bara användbara om du implementerar din egen transaktionshantering.
Autocommit¶
Django tillhandahåller ett API i modulen django.db.transaction
för att hantera autocommit-läget för varje databasanslutning.
Dessa funktioner tar ett using
argument som bör vara namnet på en databas. Om det inte tillhandahålls använder Django databasen "default"
.
Autocommit är initialt aktiverat. Om du stänger av den är det ditt ansvar att återställa den.
När du stänger av autocommit får du standardbeteendet för din databasadapter, och Django hjälper dig inte. Även om det beteendet specificeras i PEP 249, är implementeringar av adaptrar inte alltid konsekventa med varandra. Granska dokumentationen för den adapter du använder noggrant.
Du måste se till att ingen transaktion är aktiv, vanligtvis genom att utfärda en commit()
eller en rollback()
, innan du slår på autocommit igen.
Django kommer att vägra att stänga av autocommit när ett atomic()
-block är aktivt, eftersom det skulle bryta atomiciteten.
Transaktioner¶
En transaktion är en atomuppsättning av databasfrågor. Även om ditt program kraschar garanterar databasen att antingen alla ändringar kommer att tillämpas eller ingen av dem.
Django tillhandahåller inte ett API för att starta en transaktion. Det förväntade sättet att starta en transaktion är att inaktivera autocommit med set_autocommit()
.
När du är i en transaktion kan du välja att antingen tillämpa de ändringar du har utfört fram till denna punkt med commit()
, eller att avbryta dem med rollback()
. Dessa funktioner är definierade i django.db.transaction
.
Dessa funktioner tar ett using
argument som bör vara namnet på en databas. Om det inte tillhandahålls använder Django databasen "default"
.
Django kommer att vägra att göra en commit eller rollback när ett atomic()
block är aktivt, eftersom det skulle bryta atomiciteten.
Spara poäng¶
En savepoint är en markör inom en transaktion som gör att du kan rulla tillbaka en del av en transaktion, snarare än hela transaktionen. Savepoints är tillgängliga med backends SQLite, PostgreSQL, Oracle och MySQL (när du använder InnoDB-lagringsmotorn). Andra backends tillhandahåller savepoint-funktionerna, men de är tomma operationer - de gör faktiskt ingenting.
Savepoints är inte särskilt användbara om du använder autocommit, standardbeteendet i Django. Men när du öppnar en transaktion med atomic()
bygger du upp en serie databasoperationer i väntan på en commit eller rollback. Om du utfärdar en rollback, rullas hela transaktionen tillbaka. Savepoints ger möjlighet att utföra en finkornig rollback, snarare än den fullständiga rollback som skulle utföras av transaction.rollback()
.
När dekoratorn atomic()
är nästlad skapar den en sparpunkt för att möjliggöra partiell commit eller rollback. Du uppmuntras starkt att använda atomic()
i stället för de funktioner som beskrivs nedan, men de är fortfarande en del av det offentliga API:et och det finns ingen plan för att avskaffa dem.
Var och en av dessa funktioner tar ett using
-argument som bör vara namnet på en databas för vilken beteendet gäller. Om inget using
-argument anges används "default"
-databasen.
Sparpunkter styrs av tre funktioner i django.db.transaction
:
- savepoint(using=None)[source]¶
Skapar en ny sparpunkt. Detta markerar en punkt i transaktionen som är känd för att vara i ett ”bra” tillstånd. Returnerar sparpunktens ID (
sid
).
- savepoint_commit(sid, using=None)[source]¶
Frigör lagringspunkten
sid
. De ändringar som gjorts sedan sparpunkten skapades blir en del av transaktionen.
Dessa funktioner gör ingenting om sparpunkter inte stöds eller om databasen är i autocommit-läge.
Dessutom finns det en nyttofunktion:
- clean_savepoints(using=None)[source]¶
Återställer räknaren som används för att generera unika ID:n för sparpunkter.
Följande exempel visar hur du kan använda savepoints:
from django.db import transaction
# open a transaction
@transaction.atomic
def viewfunc(request):
a.save()
# transaction now contains a.save()
sid = transaction.savepoint()
b.save()
# transaction now contains a.save() and b.save()
if want_to_keep_b:
transaction.savepoint_commit(sid)
# open transaction still contains a.save() and b.save()
else:
transaction.savepoint_rollback(sid)
# open transaction now contains only a.save()
Savepoints kan användas för att återhämta sig från ett databasfel genom att utföra en partiell rollback. Om du gör detta inuti ett atomic()
-block kommer hela blocket ändå att rullas tillbaka, eftersom det inte vet att du har hanterat situationen på en lägre nivå! För att förhindra detta kan du styra rollback-beteendet med följande funktioner.
Om du ställer in rollback-flaggan till True
tvingas en rollback när du lämnar det innersta atomiska blocket. Detta kan vara användbart för att utlösa en rollback utan att skapa ett undantag.
Om du ställer in den på False
förhindras en sådan rollback. Innan du gör det, se till att du har rullat tillbaka transaktionen till en känd bra sparpunkt inom det aktuella atomiska blocket! Annars bryter du atomiciteten och datakorruption kan uppstå.
Databasspecifika anteckningar¶
Sparpunkter i SQLite¶
SQLite stöder visserligen sparpunkter, men ett fel i sqlite3
-modulens design gör att de knappast är användbara.
När autocommit är aktiverat är savepoints inte meningsfulla. När den är inaktiverad, gör sqlite3
implicit commits före savepoint-satser. (Faktum är att den committar före alla andra satser än SELECT
, INSERT
, UPDATE
, DELETE
och REPLACE
) Denna bugg har två konsekvenser:
Transaktioner i MySQL¶
Om du använder MySQL kan det hända att dina tabeller stöder transaktioner eller inte; det beror på din MySQL-version och de tabelltyper du använder. (Med ”tabelltyper” menar vi något som ”InnoDB” eller ”MyISAM”.) MySQL-transaktionens särdrag ligger utanför ramen för denna artikel, men MySQL-webbplatsen har information om MySQL-transaktioner.
Om din MySQL-konfiguration inte stöder transaktioner kommer Django alltid att fungera i autocommit-läge: uttalanden kommer att utföras och bekräftas så snart de anropas. Om din MySQL-installation har stöd för transaktioner, kommer Django att hantera transaktioner enligt vad som förklaras i detta dokument.
Hantering av undantag inom PostgreSQL-transaktioner¶
Observera
Detta avsnitt är endast relevant om du implementerar din egen transaktionshantering. Detta problem kan inte uppstå i Djangos standardläge och atomic()
hanterar det automatiskt.
Inuti en transaktion, när ett anrop till en PostgreSQL-markör ger upphov till ett undantag (vanligtvis `` IntegrityError``), kommer all efterföljande SQL i samma transaktion att misslyckas med felet ”aktuell transaktion avbryts, frågor ignoreras till slutet av transaktionsblocket”. Även om den grundläggande användningen av `` save () `` sannolikt inte kommer att ge upphov till ett undantag i PostgreSQL, finns det mer avancerade användningsmönster som kan, till exempel att spara objekt med unika fält, spara med flaggan force_insert
/ force_update
eller anropa anpassad SQL.
Det finns flera sätt att återhämta sig från den här typen av fel.
Återställning av transaktion¶
Det första alternativet är att rulla tillbaka hela transaktionen. Till exempel:
a.save() # Succeeds, but may be undone by transaction rollback
try:
b.save() # Could throw exception
except IntegrityError:
transaction.rollback()
c.save() # Succeeds, but a.save() may have been undone
Anrop av transaction.rollback()
rullar tillbaka hela transaktionen. Alla obekräftade databasoperationer kommer att gå förlorade. I det här exemplet skulle de ändringar som gjordes av a.save()
gå förlorade, även om den operationen inte gav upphov till något fel i sig.
Återställning av sparpunkt¶
Du kan använda :ref:` savepoints <topics-db-transactions-savepoints> för att styra omfattningen av en återställning. Innan du utför en databasoperation som kan misslyckas kan du ange eller uppdatera sparpunkten; på så sätt kan du, om operationen misslyckas, rulla tillbaka den enskilda felaktiga operationen i stället för hela transaktionen. Till exempel:
a.save() # Succeeds, and never undone by savepoint rollback
sid = transaction.savepoint()
try:
b.save() # Could throw exception
transaction.savepoint_commit(sid)
except IntegrityError:
transaction.savepoint_rollback(sid)
c.save() # Succeeds, and a.save() is never undone
I det här exemplet kommer a.save()
inte att ångras i det fall b.save()
ger upphov till ett undantag.