Django le brinda algunas formas de controlar cómo se administran las transacciones de la base de datos.

Gestionar transacciones de bases de datos

Comportamiento de transacción predeterminado de Django

El comportamiento predeterminado de Django es ejecutarse en modo de confirmación automática. Cada consulta se envía inmediatamente a la base de datos, a menos que haya una transacción activa. Consulte a continuación para obtener más detalles..

Django usa transacciones o puntos de guardado automáticamente para garantizar la integridad de las operaciones ORM que requieren múltiples consultas, especialmente Eliminar() y actualizar() consultas.

De Django TestCase class también envuelve cada prueba en una transacción por razones de rendimiento.

Vincular transacciones a solicitudes HTTP

Una forma común de manejar transacciones en la web es incluir cada solicitud en una transacción. Colocar ATOMIC_REQUESTS para True en la configuración de cada base de datos para la que desee habilitar este comportamiento.

Funciona así. Antes de llamar a una función de vista, Django inicia una transacción. Si la respuesta se produce sin problemas, Django confirma la transacción. Si la vista produce una excepción, Django revierte la transacción.

Puede realizar subtransacciones utilizando puntos de guardado en su código de vista, normalmente con el atomic() administrador de contexto. Sin embargo, al final de la vista, se confirmarán todos o ninguno de los cambios.

Advertencia

Si bien la simplicidad de este modelo de transacción es atractiva, también lo hace ineficiente cuando aumenta el tráfico. Abrir una transacción para cada vista tiene algunos gastos generales. El impacto en el rendimiento depende de los patrones de consulta de su aplicación y de qué tan bien su base de datos maneja el bloqueo.

Transacciones por solicitud y respuestas de transmisión

Cuando una vista devuelve un StreamingHttpResponse, leer el contenido de la respuesta a menudo ejecutará código para generar el contenido. Dado que la vista ya ha regresado, dicho código se ejecuta fuera de la transacción.

En términos generales, no es recomendable escribir en la base de datos mientras se genera una respuesta de transmisión, ya que no hay una forma sensata de manejar los errores después de comenzar a enviar la respuesta.

En la práctica, esta característica envuelve todas las funciones de vista en el atomic() decorador que se describe a continuación.

Tenga en cuenta que solo la ejecución de su vista está incluida en las transacciones. El middleware se ejecuta fuera de la transacción, al igual que la representación de las respuestas de la plantilla.

Cuando ATOMIC_REQUESTS está habilitado, aún es posible evitar que las vistas se ejecuten en una transacción.

non_atomic_requests(using=None)

Este decorador negará el efecto de ATOMIC_REQUESTS para una vista determinada:

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()

Solo funciona si se aplica a la vista en sí.

Controlar las transacciones de forma explícita

Django proporciona una única API para controlar las transacciones de la base de datos.

atomic(using=None, savepoint=True, durable=False)

La atomicidad es la propiedad que define las transacciones de la base de datos. atomic nos 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 confirman en la base de datos. Si hay una excepción, los cambios se revierten.

atomic los bloques se pueden anidar. En este caso, cuando un bloque interno se completa con éxito, sus efectos aún se pueden revertir si se genera una excepción en el bloque externo en un momento posterior.

A veces es útil asegurar una atomic el bloque es siempre el más externo atomic block, asegurando que cualquier cambio en la base de datos se confirme cuando se sale del bloque sin errores. Esto se conoce como durabilidad y se puede lograr configurando durable=True. Si el atomic El bloque está anidado dentro de otro, genera un RuntimeError.

atomic se puede utilizar tanto como decorador:

from django.db import transaction

@transaction.atomic
def viewfunc(request):
    # This code executes inside a transaction.
    do_stuff()

y como administrador de contexto:

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()

Envase atomic en un bloque try / except permite el manejo natural de errores de integridad:

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() provoca un error en la base de datos al romper una restricción de integridad, puede ejecutar consultas en add_children(), y los cambios de create_parent() todavía están allí y vinculados a la misma transacción. Tenga en cuenta que cualquier operación intentada en generate_relationships() ya se habrán revertido de forma segura cuando handle_exception() se llama, por lo que el controlador de excepciones también puede operar en la base de datos si es necesario.

Evite atrapar excepciones en el interior atomic!

Al salir de un atomic block, Django analiza si se sale normalmente o con una excepción para determinar si debe confirmarse o retroceder. Si detecta y maneja excepciones dentro de un atomic block, puede ocultarle a Django el hecho de que ha ocurrido un problema. Esto puede resultar en un comportamiento inesperado.

Esto es principalmente una preocupación para DatabaseError y sus subclases como IntegrityError. Después de tal error, la transacción se rompe y Django realizará una reversión al final de la atomic cuadra. Si intenta ejecutar consultas a la base de datos antes de que ocurra la reversión, Django generará un TransactionManagementError. También puede encontrar este comportamiento cuando un controlador de señales relacionado con ORM genera una excepción.

La forma correcta de detectar errores en la base de datos es alrededor de un atomic bloque como se muestra arriba. Si es necesario, agregue un extra atomic bloque para este propósito. Este patrón tiene otra ventaja: delimita explícitamente qué operaciones se revertirán si ocurre una excepción.

Si detecta excepciones generadas por consultas SQL sin procesar, el comportamiento de Django no está especificado y depende de la base de datos.

Es posible que deba revertir manualmente el estado del modelo al revertir una transacción.

Los valores de los campos de un modelo no se revertirán cuando se produzca una reversión de la transacción. Esto podría dar lugar a un estado de modelo incoherente a menos que restaure manualmente los valores de campo originales.

Por ejemplo, dado MyModel con un active campo, este fragmento garantiza que el if obj.active comprobar al final utiliza el valor correcto si se actualiza active para 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:
    ...

Para garantizar la atomicidad, atomic deshabilita algunas API. Intentar confirmar, revertir o cambiar el estado de confirmación automática de la conexión de la base de datos dentro de una atomic block generará una excepción.

atomic toma una using argumento que debe ser el nombre de una base de datos. Si no se proporciona este argumento, Django usa el "default" base de datos.

Bajo el capó, el código de gestión de transacciones de Django:

  • abre una transacción al ingresar al más externo atomic cuadra;
  • crea un punto de guardado al entrar en un interior atomic cuadra;
  • libera o retrocede al punto de guardado al salir de un bloque interno;
  • confirma o revierte la transacción al salir del bloque más externo.

Puede deshabilitar la creación de puntos de guardado para bloques internos configurando el savepoint argumento para False. Si ocurre una excepción, Django realizará la reversión al salir del primer bloque padre con un punto de guardado si hay uno, y el bloque más externo en caso contrario. La atomicidad todavía está garantizada por la transacción externa. Esta opción solo debe usarse si se nota la sobrecarga de los puntos de guardado. Tiene el inconveniente de romper el manejo de errores descrito anteriormente.

Puedes utilizar atomic cuando la confirmación automática está desactivada. Solo usará puntos de guardado, incluso para el bloque más externo.

Consideraciones de rendimiento

Las transacciones abiertas tienen un costo de rendimiento para su servidor de base de datos. Para minimizar estos gastos generales, mantenga sus transacciones lo más cortas posible. Esto es especialmente importante si está usando atomic() en procesos de larga ejecución, fuera del ciclo de solicitud / respuesta de Django.

Advertencia

django.test.TestCase desactiva la comprobación de durabilidad para permitir la prueba de bloques atómicos duraderos en una transacción por motivos de rendimiento. Usar django.test.TransactionTestCase para probar la durabilidad.

Cambiado en Django 3.2:

los durable se agregó el argumento.

Autocommit

Por qué Django usa autocommit

En los estándares SQL, cada consulta SQL inicia una transacción, a menos que ya haya una activa. A continuación, dichas transacciones deben confirmarse o deshacerse explícitamente.

Esto no siempre es conveniente para los desarrolladores de aplicaciones. Para aliviar este problema, la mayoría de las bases de datos proporcionan un modo de confirmación automática. Cuando la confirmación automática está activada y no hay ninguna transacción activa, cada consulta SQL se incluye en su propia transacción. En otras palabras, cada consulta de este tipo no solo inicia una transacción, sino que la transacción también se confirma o revierte automáticamente, dependiendo de si la consulta se realizó correctamente.

PEP 249, la especificación de la API de la base de datos de Python v2.0, requiere que la confirmación automática se desactive inicialmente. Django anula este valor predeterminado y activa el compromiso automático.

Para evitar esto, puedes desactivar la gestión de transacciones, pero no se recomienda.

Desactivación de la gestión de transacciones

Puede deshabilitar totalmente la gestión de transacciones de Django para una base de datos determinada configurando AUTOCOMMIT para False en su configuración. Si hace esto, Django no habilitará la confirmación automática y no realizará ninguna confirmación. Obtendrá el comportamiento habitual de la biblioteca de la base de datos subyacente.

Esto requiere que confirme explícitamente todas las transacciones, incluso las iniciadas por Django o por bibliotecas de terceros. Por lo tanto, esto se usa mejor en situaciones en las que desea ejecutar su propio middleware de control de transacciones o hacer algo realmente extraño.

Realización de acciones después del compromiso

A veces es necesario realizar una acción relacionada con la transacción actual de la base de datos, pero solo si la transacción se confirma correctamente. Los ejemplos pueden incluir un Apio tarea, una notificación por correo electrónico o una invalidación de caché.

Django proporciona la on_commit() función para registrar funciones de devolución de llamada que deben ejecutarse después de que una transacción se haya confirmado con éxito:

on_commit(func, using=None)

Pase cualquier función (que no tenga argumentos) a on_commit():

from django.db import transaction

def do_something():
    pass  # send a mail, invalidate a cache, fire off a Celery task, etc.

transaction.on_commit(do_something)

También puede envolver su función en una lambda:

transaction.on_commit(lambda: some_celery_task.delay('arg1'))

La función que pase se llamará inmediatamente después de una escritura hipotética de base de datos realizada donde on_commit() se llama se confirmaría con éxito.

Si llamas on_commit() mientras no haya una transacción activa, la devolución de llamada se ejecutará inmediatamente.

Si esa escritura hipotética de la base de datos se revierte (generalmente cuando se genera una excepción no controlada en un atomic() block), su función se descartará y nunca se llamará.

Puntos de guardado

Puntos de guardado (es decir, anidados atomic() bloques) se manejan correctamente. Es decir, un on_commit() invocable registrado después de un punto de guardado (en un anidado atomic() block) se llamará después de que se confirme la transacción externa, pero no si se produjo una reversión a ese punto de guardado o cualquier punto de guardado anterior 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

Por otro lado, cuando se revierte un punto de guardado (debido a que se genera una excepción), no se llamará al invocable 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()

Orden de ejecución

Las funciones de confirmación para una transacción determinada se ejecutan en el orden en que se registraron.

Manejo de excepciones

Si una función de confirmación dentro de una transacción determinada genera una excepción no detectada, no se ejecutarán funciones registradas posteriormente en esa misma transacción. Este es el mismo comportamiento que si hubiera ejecutado las funciones secuencialmente usted mismo sin on_commit().

Momento de ejecución

Sus devoluciones de llamada se ejecutan después una confirmación exitosa, por lo que una falla en una devolución de llamada no hará que la transacción se revierta. Se ejecutan condicionalmente al éxito de la transacción, pero no son parte de la transacción. Para los casos de uso previstos (notificaciones por correo, tareas de apio, etc.), esto debería estar bien. Si no lo es (si su acción de seguimiento es tan crítica que su falla debería significar la falla de la transacción en sí), entonces no desea utilizar el on_commit() gancho. En cambio, es posible que desee compromiso de dos fases tales como el Soporte del protocolo de compromiso en dos fases de psycopg y el Extensiones de confirmación de dos fases opcionales en la especificación Python DB-API.

Las devoluciones de llamada no se ejecutan hasta que se restaura la confirmación automática en la conexión que sigue a la confirmación (porque, de lo contrario, cualquier consulta realizada en una devolución de llamada abriría una transacción implícita, evitando que la conexión vuelva al modo de confirmación automática).

Cuando está en modo de confirmación automática y fuera de un atomic() block, la función se ejecutará inmediatamente, no en la confirmación.

Las funciones de confirmación solo funcionan con modo de confirmación automática y el atomic() (o ATOMIC_REQUESTS) API de transacciones. Vocación on_commit() cuando la confirmación automática está desactivada y no se encuentra dentro de un bloque atómico, se producirá un error.

Usar en pruebas

De Django TestCase class envuelve cada prueba en una transacción y revierte esa transacción después de cada prueba, para proporcionar aislamiento de prueba. Esto significa que nunca se compromete ninguna transacción, por lo que su on_commit() las devoluciones de llamada nunca se ejecutarán.

Puede superar esta limitación utilizando TestCase.captureOnCommitCallbacks(). Esto captura tu on_commit() devoluciones de llamada en una lista, lo que le permite hacer afirmaciones sobre ellas o emular la transacción que se confirma llamándolas.

Otra forma de superar la limitación es utilizar TransactionTestCase en lugar de TestCase. Esto significará que sus transacciones están confirmadas y se ejecutarán las devoluciones de llamada. Sin embargo TransactionTestCase vacía la base de datos entre pruebas, lo que es significativamente más lento que TestCaseAislamiento.

¿Por qué no hay gancho de retroceso?

Un gancho de reversión es más difícil de implementar de manera robusta que un gancho de confirmación, ya que una variedad de cosas pueden causar una reversión implícita.

Por ejemplo, si su conexión a la base de datos se interrumpe porque su proceso se mató sin la posibilidad de cerrarse correctamente, su gancho de reversión nunca se ejecutará.

Pero hay una solución: en lugar de hacer algo durante el bloque atómico (transacción) y luego deshacerlo si la transacción falla, use on_commit() retrasar hacerlo en primer lugar hasta después de que la transacción sea exitosa. ¡Es mucho más fácil deshacer algo que nunca hiciste en primer lugar!

API de bajo nivel

Advertencia

Siempre prefiero atomic() si es posible en absoluto. Tiene en cuenta las idiosincrasias de cada base de datos y evita operaciones no válidas.

Las API de bajo nivel solo son útiles si está implementando su propia gestión de transacciones.

Autocommit

Django proporciona una API en el django.db.transaction módulo para administrar el estado de confirmación automática de cada conexión de base de datos.

get_autocommit(using=None)
set_autocommit(autocommit, using=None)

Estas funciones toman un using argumento que debe ser el nombre de una base de datos. Si no se proporciona, Django usa el "default" base de datos.

La confirmación automática está activada inicialmente. Si lo apaga, es su responsabilidad restaurarlo.

Una vez que desactive la confirmación automática, obtendrá el comportamiento predeterminado de su adaptador de base de datos y Django no le ayudará. Aunque ese comportamiento se especifica en PEP 249, las implementaciones de adaptadores no siempre son coherentes entre sí. Revise detenidamente la documentación del adaptador que está utilizando.

Debe asegurarse de que ninguna transacción esté activa, generalmente emitiendo una commit() o un rollback(), antes de volver a activar la confirmación automática.

Django se negará a desactivar el compromiso automático cuando un atomic() el bloque está activo, porque eso rompería la atomicidad.

Actas

Una transacción es un conjunto atómico de consultas de bases de datos. Incluso si su programa falla, 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 deshabilitar el compromiso automático con set_autocommit().

Una vez que esté en una transacción, puede optar por aplicar los cambios que ha realizado hasta este punto con commit(), o cancelarlos con rollback(). Estas funciones se definen en django.db.transaction.

commit(using=None)
rollback(using=None)

Estas funciones toman un using argumento que debe ser el nombre de una base de datos. Si no se proporciona, Django usa el "default" base de datos.

Django se negará a comprometerse o retroceder cuando un atomic() el bloque está activo, porque eso rompería la atomicidad.

Puntos de guardado

Un punto de rescate es un marcador dentro de una transacción que le permite revertir parte de una transacción, en lugar de la transacción completa. Los puntos de guardado están disponibles con los backends de SQLite, PostgreSQL, Oracle y MySQL (cuando se usa el motor de almacenamiento InnoDB). Otros backends proporcionan las funciones de punto de guardado, pero son operaciones vacías, en realidad no hacen nada.

Los puntos de guardado no son especialmente útiles si está utilizando la confirmación automática, el comportamiento predeterminado de Django. Sin embargo, una vez que abra una transacción con atomic(), crea una serie de operaciones de base de datos en espera de una confirmación o una reversión. Si emite una reversión, se revierte toda la transacción. Los puntos de guardado brindan la capacidad de realizar una reversión detallada, en lugar de la reversión completa que realizaría transaction.rollback().

Cuando el atomic() decorador está anidado, crea un punto de guardado para permitir la confirmación parcial o la reversión. Se le recomienda encarecidamente que utilice atomic() en lugar de las funciones que se describen a continuación, pero siguen siendo parte de la API pública y no hay ningún plan para desaprobarlas.

Cada una de estas funciones toma un using argumento que debe ser el nombre de una base de datos a la que se aplica el comportamiento. Si no using se proporciona el argumento, entonces el "default" se utiliza la base de datos.

Los puntos de guardado están controlados por tres funciones en django.db.transaction:

savepoint(using=None)

Crea un nuevo punto de guardado. Esto marca un punto en la transacción que se sabe que está en un estado “bueno”. Devuelve el ID del punto de guardado (sid).

savepoint_commit(sid, using=None)

Lanza savepoint sid. Los cambios realizados desde que se creó el punto de guardado pasan a formar parte de la transacción.

savepoint_rollback(sid, using=None)

Revierte la transacción al punto de guardado sid.

Estas funciones no hacen nada si los puntos de guardado no son compatibles o si la base de datos está en modo de confirmación automática.

Además, hay una función de utilidad:

clean_savepoints(using=None)

Restablece el contador utilizado para generar ID de puntos de guardado únicos.

El siguiente ejemplo demuestra el uso de puntos de guardado:

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 guardado se pueden utilizar para recuperarse de un error de la base de datos realizando una reversión parcial. Si estás haciendo esto dentro de un atomic() bloque, todo el bloque se revertirá, ¡porque no sabe que has manejado la situación en un nivel inferior! Para evitar esto, puede controlar el comportamiento de reversión con las siguientes funciones.

get_rollback(using=None)
set_rollback(rollback, using=None)

Establecer la bandera de retroceso en True fuerza un retroceso al salir del bloque atómico más interno. Esto puede resultar útil para activar una reversión sin generar una excepción.

Poniéndolo en False previene tal reversión. Antes de hacer eso, asegúrese de haber revertido la transacción a un punto de guardado en buen estado dentro del bloque atómico actual. De lo contrario, está rompiendo la atomicidad y se pueden producir daños en los datos.

Notas específicas de la base de datos

Puntos de guardado en SQLite

Si bien SQLite admite puntos de guardado, una falla en el diseño del sqlite3 el módulo los hace difícilmente utilizables.

Cuando la confirmación automática está habilitada, los puntos de guardado no tienen sentido. Cuando está deshabilitado sqlite3 se compromete implícitamente antes de las sentencias de punto de guardado. (De hecho, se compromete antes de cualquier declaración que no sea SELECT, INSERT, UPDATE, DELETE y REPLACE.) Este error tiene dos consecuencias:

  • Las API de bajo nivel para puntos de guardado solo se pueden utilizar dentro de una transacción, es decir. dentro de un atomic() cuadra.
  • Es imposible de usar atomic() cuando la confirmación automática está desactivada.

Transacciones en MySQL

Si está utilizando MySQL, sus tablas pueden o no admitir transacciones; depende de su versión de MySQL y de los tipos de tabla que esté utilizando. (Por “tipos de tabla”, nos referimos a algo como “InnoDB” o “MyISAM”). Las peculiaridades de las transacciones MySQL están fuera del alcance de este artículo, pero el sitio MySQL tiene información sobre transacciones MySQL.

Si su configuración de MySQL lo hace no admitir transacciones, entonces Django siempre funcionará en modo de confirmación automática: las declaraciones se ejecutarán y confirmarán tan pronto como se llamen. Si su configuración de MySQL lo hace admitir transacciones, Django manejará las transacciones como se explica en este documento.

Manejo de excepciones dentro de transacciones de PostgreSQL

Nota

Esta sección es relevante solo si está implementando su propia gestión de 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 de PostgreSQL genera una excepción (normalmente IntegrityError), todos los SQL subsiguientes en la misma transacción fallarán con el error “la transacción actual se canceló, las consultas se ignoraron hasta el final del bloque de la transacción”. Si bien el uso básico de save() Es poco probable que genere una excepción en PostgreSQL, existen patrones de uso más avanzados que podrían, como guardar objetos con campos únicos, guardar usando el indicador force_insert / force_update o invocar SQL personalizado.

Hay varias formas de recuperarse de este tipo de error.

Reversión de transacciones

La primera opción es revertir toda la transacción. 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

Vocación transaction.rollback() revierte toda la transacción. Se perderán todas las operaciones de base de datos no confirmadas. En este ejemplo, los cambios realizados por a.save() se perdería, a pesar de que esa operación no generó ningún error en sí misma.

Reversión de puntos de guardado

Puedes usar puntos de guardado para controlar el alcance de una reversión. Antes de realizar una operación de base de datos que podría fallar, puede configurar o actualizar el punto de guardado; de esa manera, si la operación falla, puede revertir la operación infractora única, en lugar de la transacción completa. 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 se deshará en el caso en que b.save() plantea una excepción.