Django te da unas pocas formas de controlar cómo se gestionan las transacciones de la base de datos.
El comportamiento predeterminado de Django es ejecutarse en modo autocomitido. Cada consulta se comite inmediatamente a la base de datos, a menos que esté activa una transacción. Ver detalles más abajo.
Django utiliza transacciones o puntos de salvaguarda automáticamente para garantizar la integridad de las operaciones ORM que requieren múltiples consultas, especialmente las consultas delete() y update().
La clase de Django :class:`~django.test.TestCase también envuelve cada prueba en una transacción por razones de rendimiento.
Una forma común de manejar transacciones en la web es envolver cada solicitud dentro de una transacción. Establece ATOMIC_REQUESTS a True en la configuración de cada base de datos para las que deseas habilitar este comportamiento.
Funciona de la siguiente manera. Antes de llamar una función de vista, Django inicia una transacción. Si se produce la respuesta sin problemas, Django confirma la transacción. Si la vista produce una excepción, Django vuelve atrás en la transacción.
Puedes realizar subtransacciones utilizando puntos de control (atomic()) en tu código de vista, típicamente con el administrador de contexto atomic(). Sin embargo, al final de la vista, se comprometerán todos o ninguno de los cambios.
Advertencia
Aunque la simplicidad del modelo de transacción es atractiva, también lo hace ineficiente cuando aumenta el tráfico. La apertura de una transacción por cada vista tiene un cierto overhead. El impacto en el rendimiento depende de los patrones de consulta de tu aplicación y de cómo maneje la base de datos las restricciones de bloqueo.
Transacciones por solicitud y respuestas en streaming
Cuando una vista devuelve un StreamingHttpResponse, leer el contenido de la respuesta ejecutará a menudo código para generar el contenido. Dado que la vista ya ha devuelto, tal código se ejecuta fuera de la transacción.
En general, no es recomendable escribir en la base de datos mientras se genera una respuesta en streaming, ya que no hay una forma sensata de manejar errores después de empezar a enviar la respuesta.
En la práctica, esta característica envuelve cada función de vista con el decorador atomic() descrito a continuación.
Ten en cuenta que solo se encierra la ejecución de tu vista dentro de las transacciones. El middleware se ejecuta fuera de la transacción y lo hace la renderización de respuestas de plantilla.
Si ATOMIC_REQUESTS está habilitado, todavía es posible prevenir que las vistas se ejecuten en una transacción.
Este decorador anulará el efecto de ATOMIC_REQUESTS para una vista dada:
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()
Funciona solo si se aplica a la vista misma.
Django proporciona una sola API para controlar las transacciones de base de datos.
La atomicidad es la propiedad definitoria de las transacciones de base de datos. atomic permite crear un bloque de código dentro del cual se garantiza la atomicidad en la base de datos. Si el bloque de código se completa con éxito, los cambios se comiten a la base de datos. Si hay una excepción, los cambios se deshacen.
Los bloques atomic pueden ser anidados. En este caso, cuando un bloque interior se completa con éxito, sus efectos aún pueden deshacerse si se produce una excepción en el bloque exterior en un punto posterior.
A veces es útil asegurarse de que un bloque atomic siempre sea el bloque más externo atomic, garantizando que cualquier cambio en la base de datos se comite cuando el bloque se sale sin errores. Esto se conoce como durabilidad y se puede lograr estableciendo durable=True. Si el bloque atomic está anidado dentro de otro, se produce un RuntimeError.
atomic es usable tanto como decorador decorator
from django.db import transaction
@transaction.atomic
def viewfunc(request):
# This code executes inside a transaction.
do_stuff()
como como administrador de contexto 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()
Envolver atomic en un bloque try/except permite manejar errores de integridad de manera natural:
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()
En este ejemplo, incluso si generate_relationships() causa un error de base de datos al romper una restricción de integridad, puedes ejecutar consultas en add_children(), y los cambios de create_parent() aún están allí y vinculados a la misma transacción. Tenga en cuenta que cualquier operación intentada en generate_relationships() ya habrá sido deshecha con seguridad cuando se llame a handle_exception(), por lo que el manejador de excepciones también puede operar en la base de datos si es necesario.
Evita atrapar excepciones dentro de atomic!
Al salir de un bloque atomic, Django mira si se ha salido normalmente o con una excepción para determinar si debe confirmar o deshacer la transacción. Si atrapas y manejas las excepciones dentro de un bloque atomic, puedes ocultar a Django el hecho de que ha ocurrido un problema. Esto puede dar lugar a comportamientos inesperados.
Esto es principalmente una preocupación para DatabaseError y sus subclases como IntegrityError. Después de tal error, la transacción está rota y Django realizará un deshacer al final del bloque atomic. Si intentas ejecutar consultas de base de datos antes de que suceda el deshacer, Django levantará una TransactionManagementError. También puedes encontrar este comportamiento cuando un manejador de señal relacionado con ORM levanta una excepción.
La forma correcta de atrapar errores de base de datos es alrededor de un bloque atomic como se muestra arriba. Si es necesario, agrega un bloque atomic adicional para este propósito. Este patrón tiene otra ventaja: delimita explícitamente cuáles operaciones se desharán si ocurre una excepción.
Si atrapas excepciones levantadas por consultas SQL crudas, el comportamiento de Django es no especificado y depende de la base de datos.
Puede necesitar revertir manualmente el estado de la aplicación cuando se deshace una transacción.
Los valores de los campos de un modelo no se reestablecerán cuando suceda un deshacer de transacción. Esto podría dar lugar a un estado inconsistente del modelo a menos que restabiles manualmente los valores originales de los campos.
Por ejemplo, dado MyModel con un campo active, este snippet asegura que el if obj.active check al final utilice el valor correcto si actualizar active a True falla en la transacción:
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:
...
Esto también se aplica a cualquier otro mecanismo que pueda mantener el estado de la aplicación, como caché o variables globales. Por ejemplo, si el código actualiza proactivamente datos en el caché después de guardar un objeto, se recomienda utilizar transaction.on_commit() en su lugar, para diferir las alteraciones del caché hasta que la transacción esté realmente confirmada.
Para garantizar la atomicidad, atomic deshabilita algunas APIs. Intentar confirmar, deshacer o cambiar el estado de autocomit de la conexión a la base de datos dentro de un bloque atomic levantará una excepción.
atomic toma un argumento using que debería ser el nombre de una base de datos. Si no se proporciona este argumento, Django utiliza la base de datos "default".
Bajo la capa, el código de gestión de transacciones de Django:
abre una transacción al entrar en el bloque atomic más externo;
crea un punto de salvaguarda cuando entra en un bloque atomic interno;
libera o vuelve a cargar hasta el punto de salvaguarda cuando sale de un bloque interno;
confirma o vuelve a cargar la transacción al salir del bloque más externo.
Puedes deshabilitar la creación de puntos de salvaguardia para bloques internos estableciendo el argumento savepoint en False. Si ocurre una excepción, Django realizará el rollback cuando salga del primer bloque padre con un punto de salvaguarda si lo hay, y del bloque más externo en caso contrario. La atomicidad sigue garantizándose por la transacción exterior. Esta opción solo debe usarse si el overhead de los puntos de salvaguardia es notorio. Tiene el inconveniente de romper el manejo de errores descrito anteriormente.
Puedes usar atomic cuando autocomit está desactivado. Sólo utilizará puntos de salvaguardia, incluso para el bloque más externo.
Consideraciones sobre rendimiento
Las transacciones abiertas tienen un costo en términos de rendimiento para tu servidor de base de datos. Para minimizar este overhead, mantén tus transacciones lo más cortas posible. Esto es especialmente importante si estás utilizando atomic() en procesos que se ejecutan durante mucho tiempo, fuera del ciclo de solicitud / respuesta de Django.
En los estándares SQL, cada consulta SQL inicia una transacción a menos que ya esté activa. Tales transacciones deben ser explícitamente confirmadas o revertidas.
No siempre es conveniente para los desarrolladores de aplicaciones. Para aliviar este problema, la mayoría de las bases de datos proporcionan un modo autocomit. Cuando el autocomit está habilitado y no hay ninguna transacción activa, cada consulta SQL se envuelve en su propia transacción. En otras palabras, no solo cada tal consulta inicia una transacción, sino que también se confirma o rechaza automáticamente, dependiendo de si la consulta tuvo éxito.
PEP 249, la especificación de la API de bases de datos de Python v2.0, requiere que el autocomit esté inicialmente desactivado. Django sobreescribe este valor por defecto y habilita el autocomit.
Para evitar esto, puedes desactivar la gestión de transacciones, pero no se recomienda.
Puedes deshabilitar completamente la gestión de transacciones de Django para una base de datos determinada estableciendo AUTOCOMMIT a False en su configuración. Si haces esto, Django no habilitará el autocomit y no realizará ninguna confirmación. Obtendrás el comportamiento regular de la biblioteca de base de datos subyacente.
Esto requiere que confirmes explícitamente cada transacción, incluso aquellas iniciadas por Django o por bibliotecas de terceros. Por lo tanto, esto se debe utilizar en situaciones donde quieras ejecutar tu propio controlador de transacciones o hacer algo realmente extraño.
A veces necesitas realizar una acción relacionada con la transacción de base de datos actual, pero solo si la transacción se comite correctamente. Los ejemplos pueden incluir una tarea de fondo, una notificación por correo electrónico o una invalidación de caché.
La función on_commit() te permite registrar callbacks que se ejecutarán después de que la transacción abierta se comita correctamente:
Pasa una función, o cualquier objeto callable, a on_commit():
from django.db import transaction
def send_welcome_email(): ...
transaction.on_commit(send_welcome_email)
Los callbacks no recibirán argumentos, pero puedes vincularlos con functools.partial():
from functools import partial
for user in users:
transaction.on_commit(partial(send_invite_email, user=user))
Los callbacks se llamarán después de que la transacción abierta se comita correctamente. Si en cambio la transacción se revoca (normalmente cuando una excepción no manejada se levanta en un bloque atomic()), el callback será descartado y nunca llamado.
Si llamas a on_commit() mientras no hay una transacción abierta, el callback se ejecutará inmediatamente.
A veces es útil registrar callbacks que pueden fallar. Pasando robust=True permite que los siguientes callbacks se ejecuten incluso si el actual lanza una excepción. Todos los errores derivados de la clase Exception de Python se capturan y se registran en el logger django.db.backends.base.
Puedes utilizar TestCase.captureOnCommitCallbacks() para probar callbacks registrados con on_commit().
Los puntos de salvaguarda (es decir, bloques atomic() anidados) se manejan correctamente. Es decir, un callable on_commit() registrado después de un punto de salvaguarda (en un bloque atomic() anidado) se llamará después de que la transacción exterior se comita, pero no si una revocación al punto de salvaguardo o a cualquier punto de salvaguardo anterior ocurrió durante la transacción:
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
On el otro lado, cuando se deshace un punto de control (debido a una excepción levantada), no se llamará al callable interno:
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()
Las funciones on-commit para una transacción determinada se ejecutan en el orden en que fueron registradas.
Si una función on-commit registrada con robust=False dentro de una transacción determinada levanta una excepción no atrapada, las funciones registradas posteriormente en la misma transacción no se ejecutarán. Este comportamiento es el mismo que si hubieras ejecutado las funciones secuencialmente tú mismo sin on_commit().
Tus callbacks se ejecutan después de un commit exitoso, por lo que una falla en una callback no causará que la transacción se deshaga. Se ejecutan condicionalmente sobre el éxito de la transacción, pero no son parte de la transacción. Para los casos de uso previstos (notificaciones por correo electrónico, tareas de fondo, etc.), esto debería ser suficiente. Si no lo es (si tu acción secundaria es tan crítica que su falla debe significar la falla de la transacción misma), entonces no quieres usar el hook on_commit(). En su lugar, podrías querer dos-fase commit como el protocolo de dos fases del soporte de psycopg <psycopg:two-phase-commit> y las extensiones de dos fases en la especificación de la API de bases de datos de Python <249#optional-two-phase-commit-extensions>.
Las callbacks no se ejecutan hasta que se restaura el modo autocommit en la conexión después del commit (porque de lo contrario cualquier consulta realizada en una callback abriría una transacción implícita, impidiendo que la conexión vuelva a modo autocomit).
Cuando estás en modo autocomit y fuera de un bloque atomic(), la función se ejecutará inmediatamente, no al commit.
Las funciones on-commit solo funcionan con el modo <managing-autocommit> y la API de transacciones atomic() (o la configuración ATOMIC_REQUESTS). Llamar a on_commit() cuando el autocomit está deshabilitado y no estás dentro de un bloque atomic dará como resultado un error.
La clase de Django TestCase envuelve cada prueba en una transacción y vuelve a rodar esa transacción después de cada prueba, con el fin de proporcionar aislamiento de prueba. Esto significa que ninguna transacción se comite jamás realmente, por lo tanto tus llamadas a on_commit() nunca serán ejecutadas.
Puedes superar esta limitación utilizando TestCase.captureOnCommitCallbacks(). Esta captura tus llamadas a on_commit() en una lista, permitiéndote hacer afirmaciones sobre ellas o emular la transacción comitando llamándolas.
Otra forma de superar esta limitación es utilizar TransactionTestCase en lugar de TestCase. Esto significa que tus transacciones se comiten y las llamadas a on_commit() se ejecutan. Sin embargo, TransactionTestCase vacía la base de datos entre pruebas, lo cual es significativamente más lento que el aislamiento de TestCase.
Una función de rollback es más difícil de implementar robustamente que una función de commit, ya que una variedad de cosas pueden causar un rollback implícito.
Por ejemplo, si la conexión a tu base de datos se cae porque tu proceso fue matado sin darle la oportunidad de cerrarse con normalidad, tu función de rollback nunca se ejecutará.
Pero hay una solución: en lugar de hacer algo dentro del bloque atómico (transacción) y luego deshacerlo si la transacción falla, utiliza on_commit() para retrasar hacerlo hasta después de que la transacción tenga éxito. Es mucho más fácil deshacer algo que nunca se hizo en primer lugar!
Advertencia
Preferir siempre atomic() si es posible. Cuenta con las idiosincrasias de cada base de datos y previene operaciones inválidas.
Las traducciones son:
Django proporciona una API en el módulo django.db.transaction para gestionar el estado de autocomit de cada conexión a la base de datos.
Estas funciones toman un argumento using que debería ser el nombre de una base de datos. Si no se proporciona, Django utiliza la base de datos "default".
El autocomit está inicialmente encendido. Si lo desactivas, es tu responsabilidad restaurarlo.
Una vez que hayas desactivado el autocomit, obtienes el comportamiento por defecto del adaptador de base de datos y Django no te ayudará. Aunque ese comportamiento está especificado en PEP 249, las implementaciones de los adaptadores no siempre son consistentes entre sí. Revisa cuidadosamente la documentación del adaptador que estás utilizando.
Debes asegurarte de que no haya ninguna transacción activa, lo que suele hacerse mediante una commit() o un rollback(), antes de volver a encender el autocomit.
Django se negará a desactivar el autocomit cuando esté activo un bloque atomic(), porque eso rompería la atomicidad.
Una transacción es un conjunto atómico de consultas a la base de datos. Incluso si tu programa se cae, la base de datos garantiza que se aplicarán todos los cambios o ninguno de ellos.
Django no proporciona una API para iniciar una transacción. La forma esperada de iniciar una transacción es desactivando el autocomit con set_autocommit().
Una vez que estés en una transacción, puedes elegir aplicar los cambios realizados hasta este punto con commit(), o cancelarlos con rollback(). Estas funciones están definidas en django.db.transaction.
Estas funciones toman un argumento using que debería ser el nombre de una base de datos. Si no se proporciona, Django utiliza la base de datos "default".
Django rechazará la comitación o el rollback cuando un bloque atomic() esté activo, porque eso rompería la atomicidad.
Un punto de control es un marcador dentro de una transacción que te permite dar marcha atrás parte de una transacción, en lugar de la transacción completa. Los puntos de control están disponibles con los backends SQLite, PostgreSQL, Oracle y MySQL (cuando se utiliza el motor de almacenamiento InnoDB). Los demás backends proporcionan las funciones de punto de control, pero son operaciones vacías – no hacen nada real.
Los puntos de control no son especialmente útiles si estás utilizando autocomit, el comportamiento predeterminado de Django. Sin embargo, una vez que abras una transacción con atomic(), construirás una serie de operaciones en la base de datos esperando a ser comitadas o dadas marcha atrás. Si emites un rollback, toda la transacción se da marcha atrás. Los puntos de control proporcionan la capacidad de realizar un rollback fino-grano, en lugar del rollback completo que se realizaría con transaction.rollback().
Cuando el decorador atomic() está anidado, crea un punto de control para permitir comitaciones parciales o rollbacks. Te aconsejamos fuertemente utilizar atomic() en lugar de las funciones descritas a continuación, pero aún forman parte de la API pública y no hay planes para deshabilitarlas.
Cada una de estas funciones toma un argumento using que debería ser el nombre de una base de datos para la cual se aplica el comportamiento. Si no se proporciona el argumento using, se utiliza la base de datos "default".
Los puntos de control están controlados por tres funciones en django.db.transaction:
Crea un nuevo punto de control. Este marca un punto en la transacción que es conocido como estar en un estado «bueno». Devuelve el ID del punto de control (sid).
Libera el punto de control sid. Los cambios realizados desde que se creó el punto de control se convierten en parte de la transacción.
Da marcha atrás la transacción hasta el punto de control sid.
Estas funciones no hacen nada si los puntos de control no están soportados o si la base de datos está en modo autocomit.
Además, hay una función utilitaria:
Resetea el contador utilizado para generar IDs únicos de puntos de control.
El siguiente ejemplo demuestra el uso de puntos de control:
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()
Los puntos de control pueden usarse para recuperarse de un error en la base de datos realizando un rollback parcial. Si estás haciendo esto dentro de un bloque atomic(), el bloque entero todavía se rechazará, porque no sabe que has manejado la situación a un nivel inferior! Para evitar esto, puedes controlar el comportamiento del rollback con las siguientes funciones.
Establecer la bandera de rollback en True fuerza un rollback al salir del bloque atomic() más interno. Esto puede ser útil para desencadenar un rollback sin levantar una excepción.
Establecerla en False impide que se produzca tal rollback. Antes de hacer eso, asegúrate de haber rechazado la transacción a un punto de control conocido bueno dentro del bloque atomic() actual! De lo contrario, estarás rompiendo la atomicidad y puede ocurrir corrupción de datos.
Si bien SQLite admite puntos de control, un defecto en el diseño del módulo sqlite3 los hace casi inutilizables.
Cuando está habilitada la autocommit, los puntos de control no tienen sentido. Cuando está deshabilitado, sqlite3 comienza a comitar implícitamente antes de las sentencias de punto de control. (De hecho, comita antes que cualquier otra sentencia excepto SELECT, INSERT, UPDATE, DELETE y REPLACE.) Este bug tiene dos consecuencias:
Los textos traducidos son:
Es imposible utilizar atomic() cuando se ha desactivado el autocomit.
Si estás utilizando MySQL, tus tablas pueden o no soportar transacciones; depende de la versión de MySQL que estés utilizando y del tipo de tabla que estés usando. (Por «tipo de tabla», nos referimos a algo como «InnoDB» o «MyISAM».) Las particularidades de las transacciones en MySQL están fuera del alcance de este artículo, pero el sitio web de MySQL tiene información sobre transacciones de MySQL.
Si tu configuración de MySQL no soporta transacciones, entonces Django siempre funcionará en modo autocomit: las sentencias se ejecutarán y se comitarán tan pronto como sean llamadas. Si tu configuración de MySQL soporta transacciones, Django manejará las transacciones tal como se explica en este documento.
Nota
Esta sección es relevante solo si estás implementando la gestión de tus propias transacciones. Este problema no puede ocurrir en el modo predeterminado de Django y atomic() lo maneja automáticamente.
Dentro de una transacción, cuando una llamada a un cursor PostgreSQL levanta una excepción (generalmente IntegrityError), todas las sentencias SQL posteriores dentro de la misma transacción fallarán con el error «la transacción actual está abortada, consultas ignoradas hasta el final del bloque de transacción». Si bien el uso básico de save() es poco probable que levante una excepción en PostgreSQL, hay patrones de uso más avanzados que podrían hacerlo, como guardar objetos con campos únicos, guardar utilizando la bandera force_insert/force_update, o invocar SQL personalizado.
Hay varias formas de recuperarse de este tipo de error.
La primera opción es revertir la transacción completa. Por ejemplo:
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
Llamando a transaction.rollback() se revierte toda la transacción. Cualquier operación de base de datos sin comprobar se perderá. En este ejemplo, los cambios realizados por a.save() se perderían, incluso aunque esa operación no levantó ningún error en sí misma.
Puedes utilizar savepoints para controlar el alcance de un rollback. Antes de realizar una operación en la base de datos que podría fallar, puedes establecer o actualizar el punto de salvaguarda; de esa manera, si la operación falla, puedes deshacer solo la operación ofendida individualmente, en lugar de toda la transacción. Por ejemplo:
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
En este ejemplo, a.save() no será deshecho en el caso donde b.save() levanta una excepción.
may 31, 2026