mirror of
https://github.com/django/django.git
synced 2025-11-07 07:15:35 +00:00
Fixed #21171 -- Avoided starting a transaction when a single (or atomic queries) are executed.
Checked the following locations:
* Model.save(): If there are parents involved, take the safe way and use
transactions since this should be an all or nothing operation.
If the model has no parents:
* Signals are executed before and after the previous existing
transaction -- they were never been part of the transaction.
* if `force_insert` is set then only one query is executed -> atomic
by definition and no transaction needed.
* same applies to `force_update`.
* If a primary key is set and no `force_*` is set Django will try an
UPDATE and if that returns zero rows it tries an INSERT. The first
case is completly save (single query). In the second case a
transaction should not produce different results since the update
query is basically a no-op then (might miss something though).
* QuerySet.update(): no signals issued, single query -> no transaction
needed.
* Model/Collector.delete(): This one is fun due to the fact that is
does many things at once.
Most importantly though: It does send signals as part of the
transaction, so for maximum backwards compatibility we need to be
conservative.
To ensure maximum compatibility the transaction here is removed only
if the following holds true:
* A single instance is being deleted.
* There are no signal handlers attached to that instance.
* There are no deletions/updates to cascade.
* There are no parents which also need deletion.
This commit is contained in:
committed by
Florian Apolloner
parent
38f3de86bd
commit
bc7dd8490b
@@ -1,4 +1,4 @@
|
||||
from contextlib import ContextDecorator
|
||||
from contextlib import ContextDecorator, contextmanager
|
||||
|
||||
from django.db import (
|
||||
DEFAULT_DB_ALIAS, DatabaseError, Error, ProgrammingError, connections,
|
||||
@@ -92,6 +92,34 @@ def set_rollback(rollback, using=None):
|
||||
return get_connection(using).set_rollback(rollback)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def mark_for_rollback_on_error(using=None):
|
||||
"""
|
||||
Internal low-level utility to mark a transaction as "needs rollback" when
|
||||
an exception is raised while not enforcing the enclosed block to be in a
|
||||
transaction. This is needed by Model.save() and friends to avoid starting a
|
||||
transaction when in autocommit mode and a single query is executed.
|
||||
|
||||
It's equivalent to:
|
||||
|
||||
connection = get_connection(using)
|
||||
if connection.get_autocommit():
|
||||
yield
|
||||
else:
|
||||
with transaction.atomic(using=using, savepoint=False):
|
||||
yield
|
||||
|
||||
but it uses low-level utilities to avoid performance overhead.
|
||||
"""
|
||||
try:
|
||||
yield
|
||||
except Exception:
|
||||
connection = get_connection(using)
|
||||
if connection.in_atomic_block:
|
||||
connection.needs_rollback = True
|
||||
raise
|
||||
|
||||
|
||||
def on_commit(func, using=None):
|
||||
"""
|
||||
Register `func` to be called when the current transaction is committed.
|
||||
|
||||
Reference in New Issue
Block a user