DatabastransaktionerLink to this heading
Django ger dig några sätt att kontrollera hur databastransaktioner hanteras.
Hantering av databastransaktionerLink to this heading
Djangos standardbeteende för transaktionerLink to this heading
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ågningarLink to this heading
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.
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)Link to this definition
Denna dekorator upphäver effekten av
ATOMIC_REQUESTSfö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 transaktionerLink to this heading
Django tillhandahåller ett enda API för att styra databastransaktioner.
- atomic(using=None, savepoint=True, durable=False)Link to this definition
Atomicity är den definierande egenskapen för databastransaktioner. med
atomickan 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.atomiskablock 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
atomisktblock alltid är det ytterstaatomiskablocket, 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 detatomiskablocket är kapslat i ett annat block ger det upphov till ettRuntimeError.atomicis usable both as a decorator:from django.db import transaction @transaction.atomic def viewfunc(request): # This code executes inside a transaction. do_stuff()and as a context manager:
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
atomici 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.För att garantera atomicitet inaktiverar
atomicvissa 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å.atomictar ettusingargument 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
atomiskablocket;skapar en sparpunkt när du går in i ett inre
atomisktblock;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
savepointtillFalse. 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
atomicnär autocommit är avstängt. Det kommer bara att använda sparpunkter, även för det yttersta blocket.
AutocommitLink to this heading
Varför Django använder autocommitLink to this heading
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 transaktionshanteringLink to this heading
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 commitLink to this heading
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:
- on_commit(func, using=None, robust=False)Link to this definition
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ängLink to this heading
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ällighetsordningLink to this heading
On-commit-funktioner för en viss transaktion utförs i den ordning de registrerades.
Hantering av undantagLink to this heading
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ällandeLink to this heading
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()-hook. 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 testerLink to this heading
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-hook?Link to this heading
En rollback-hook är svårare att implementera på ett robust sätt än en commit-hook, 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-hook 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åLink to this heading
AutocommitLink to this heading
Django tillhandahåller ett API i modulen django.db.transaction för att hantera autocommit-läget för varje databasanslutning.
- get_autocommit(using=None)Link to this definition
- set_autocommit(autocommit, using=None)Link to this definition
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.
TransaktionerLink to this heading
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.
- commit(using=None)Link to this definition
- rollback(using=None)Link to this definition
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ängLink to this heading
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.
Each of these functions takes a using argument which should be the name of
a database for which the behavior applies. If no using argument is
provided then the "default" database is used.
Sparpunkter styrs av tre funktioner i django.db.transaction:
- savepoint(using=None)Link to this definition
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)Link to this definition
Frigör lagringspunkten
sid. De ändringar som gjorts sedan sparpunkten skapades blir en del av transaktionen.
- savepoint_rollback(sid, using=None)Link to this definition
Rullar tillbaka transaktionen till sparpunkten
sid.
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)Link to this definition
Å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.
- get_rollback(using=None)Link to this definition
- set_rollback(rollback, using=None)Link to this definition
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 anteckningarLink to this heading
Sparpunkter i SQLiteLink to this heading
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 MySQLLink to this heading
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-transaktionerLink to this heading
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 transaktionLink to this heading
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 sparpunktLink to this heading
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.