Loading AUTHORS +1 −0 Original line number Diff line number Diff line Loading @@ -434,6 +434,7 @@ answer newbie questions, and generally made Django that much better: Andreas Pelme <andreas@pelme.se> permonik@mesias.brnonet.cz peter@mymart.com Christophe Pettus <xof@thebuild.com> pgross@thoughtworks.com phaedo <http://phaedo.cx/> phil@produxion.net Loading django/db/backends/__init__.py +21 −2 Original line number Diff line number Diff line Loading @@ -50,6 +50,12 @@ class BaseDatabaseWrapper(object): # set somewhat aggressively, as the DBAPI doesn't make it easy to # deduce if the connection is in transaction or not. self._dirty = False # Tracks if the connection is in a transaction managed by 'atomic' self.in_atomic_block = False # List of savepoints created by 'atomic' self.savepoint_ids = [] # Hack to provide compatibility with legacy transaction management self._atomic_forced_unmanaged = False # Connection termination related attributes self.close_at = None Loading Loading @@ -148,7 +154,7 @@ class BaseDatabaseWrapper(object): def commit(self): """ Does the commit itself and resets the dirty flag. Commits a transaction and resets the dirty flag. """ self.validate_thread_sharing() self._commit() Loading @@ -156,7 +162,7 @@ class BaseDatabaseWrapper(object): def rollback(self): """ Does the rollback itself and resets the dirty flag. Rolls back a transaction and resets the dirty flag. """ self.validate_thread_sharing() self._rollback() Loading Loading @@ -447,6 +453,12 @@ class BaseDatabaseWrapper(object): if must_close: self.close() def _start_transaction_under_autocommit(self): """ Only required when autocommits_when_autocommit_is_off = True. """ raise NotImplementedError class BaseDatabaseFeatures(object): allows_group_by_pk = False Loading Loading @@ -549,6 +561,10 @@ class BaseDatabaseFeatures(object): # Support for the DISTINCT ON clause can_distinct_on_fields = False # Does the backend decide to commit before SAVEPOINT statements # when autocommit is disabled? http://bugs.python.org/issue8145#msg109965 autocommits_when_autocommit_is_off = False def __init__(self, connection): self.connection = connection Loading Loading @@ -931,6 +947,9 @@ class BaseDatabaseOperations(object): return "BEGIN;" def end_transaction_sql(self, success=True): """ Returns the SQL statement required to end a transaction. """ if not success: return "ROLLBACK;" return "COMMIT;" Loading django/db/backends/sqlite3/base.py +15 −4 Original line number Diff line number Diff line Loading @@ -99,6 +99,7 @@ class DatabaseFeatures(BaseDatabaseFeatures): supports_mixed_date_datetime_comparisons = False has_bulk_insert = True can_combine_inserts_with_and_without_auto_increment_pk = False autocommits_when_autocommit_is_off = True @cached_property def uses_savepoints(self): Loading Loading @@ -360,10 +361,12 @@ class DatabaseWrapper(BaseDatabaseWrapper): BaseDatabaseWrapper.close(self) def _savepoint_allowed(self): # When 'isolation_level' is None, Django doesn't provide a way to # create a transaction (yet) so savepoints can't be created. When it # isn't, sqlite3 commits before each savepoint -- it's a bug. return False # When 'isolation_level' is not None, sqlite3 commits before each # savepoint; it's a bug. When it is None, savepoints don't make sense # because autocommit is enabled. The only exception is inside atomic # blocks. To work around that bug, on SQLite, atomic starts a # transaction explicitly rather than simply disable autocommit. return self.in_atomic_block def _set_autocommit(self, autocommit): if autocommit: Loading Loading @@ -413,6 +416,14 @@ class DatabaseWrapper(BaseDatabaseWrapper): def is_usable(self): return True def _start_transaction_under_autocommit(self): """ Start a transaction explicitly in autocommit mode. Staying in autocommit mode works around a bug of sqlite3 that breaks savepoints when autocommit is disabled. """ self.cursor().execute("BEGIN") FORMAT_QMARK_REGEX = re.compile(r'(?<!%)%s') Loading django/db/transaction.py +150 −7 Original line number Diff line number Diff line Loading @@ -16,7 +16,7 @@ import warnings from functools import wraps from django.db import connections, DEFAULT_DB_ALIAS from django.db import connections, DatabaseError, DEFAULT_DB_ALIAS class TransactionManagementError(Exception): Loading Loading @@ -134,13 +134,13 @@ def rollback_unless_managed(using=None): def commit(using=None): """ Does the commit itself and resets the dirty flag. Commits a transaction and resets the dirty flag. """ get_connection(using).commit() def rollback(using=None): """ This function does the rollback itself and resets the dirty flag. Rolls back a transaction and resets the dirty flag. """ get_connection(using).rollback() Loading @@ -166,9 +166,151 @@ def savepoint_commit(sid, using=None): """ get_connection(using).savepoint_commit(sid) ############## # DECORATORS # ############## ################################# # Decorators / context managers # ################################# class Atomic(object): """ This class guarantees the atomic execution of a given block. An instance can be used either as a decorator or as a context manager. When it's used as a decorator, __call__ wraps the execution of the decorated function in the instance itself, used as a context manager. When it's used as a context manager, __enter__ creates a transaction or a savepoint, depending on whether a transaction is already in progress, and __exit__ commits the transaction or releases the savepoint on normal exit, and rolls back the transaction or to the savepoint on exceptions. A stack of savepoints identifiers is maintained as an attribute of the connection. None denotes a plain transaction. This allows reentrancy even if the same AtomicWrapper is reused. For example, it's possible to define `oa = @atomic('other')` and use `@ao` or `with oa:` multiple times. Since database connections are thread-local, this is thread-safe. """ def __init__(self, using): self.using = using def _legacy_enter_transaction_management(self, connection): if not connection.in_atomic_block: if connection.transaction_state and connection.transaction_state[-1]: connection._atomic_forced_unmanaged = True connection.enter_transaction_management(managed=False) else: connection._atomic_forced_unmanaged = False def _legacy_leave_transaction_management(self, connection): if not connection.in_atomic_block and connection._atomic_forced_unmanaged: connection.leave_transaction_management() def __enter__(self): connection = get_connection(self.using) # Ensure we have a connection to the database before testing # autocommit status. connection.ensure_connection() # Remove this when the legacy transaction management goes away. self._legacy_enter_transaction_management(connection) if not connection.in_atomic_block and not connection.autocommit: raise TransactionManagementError( "'atomic' cannot be used when autocommit is disabled.") if connection.in_atomic_block: # We're already in a transaction; create a savepoint. sid = connection.savepoint() connection.savepoint_ids.append(sid) else: # We aren't in a transaction yet; create one. # The usual way to start a transaction is to turn autocommit off. # However, some database adapters (namely sqlite3) don't handle # transactions and savepoints properly when autocommit is off. # In such cases, start an explicit transaction instead, which has # the side-effect of disabling autocommit. if connection.features.autocommits_when_autocommit_is_off: connection._start_transaction_under_autocommit() connection.autocommit = False else: connection.set_autocommit(False) connection.in_atomic_block = True connection.savepoint_ids.append(None) def __exit__(self, exc_type, exc_value, traceback): connection = get_connection(self.using) sid = connection.savepoint_ids.pop() if exc_value is None: if sid is None: # Commit transaction connection.in_atomic_block = False try: connection.commit() except DatabaseError: connection.rollback() # Remove this when the legacy transaction management goes away. self._legacy_leave_transaction_management(connection) raise finally: if connection.features.autocommits_when_autocommit_is_off: connection.autocommit = True else: connection.set_autocommit(True) else: # Release savepoint try: connection.savepoint_commit(sid) except DatabaseError: connection.savepoint_rollback(sid) # Remove this when the legacy transaction management goes away. self._legacy_leave_transaction_management(connection) raise else: if sid is None: # Roll back transaction connection.in_atomic_block = False try: connection.rollback() finally: if connection.features.autocommits_when_autocommit_is_off: connection.autocommit = True else: connection.set_autocommit(True) else: # Roll back to savepoint connection.savepoint_rollback(sid) # Remove this when the legacy transaction management goes away. self._legacy_leave_transaction_management(connection) def __call__(self, func): @wraps(func) def inner(*args, **kwargs): with self: return func(*args, **kwargs) return inner def atomic(using=None): # Bare decorator: @atomic -- although the first argument is called # `using`, it's actually the function being decorated. if callable(using): return Atomic(DEFAULT_DB_ALIAS)(using) # Decorator: @atomic(...) or context manager: with atomic(...): ... else: return Atomic(using) ############################################ # Deprecated decorators / context managers # ############################################ class Transaction(object): """ Loading Loading @@ -279,7 +421,8 @@ def commit_on_success_unless_managed(using=None): """ Transitory API to preserve backwards-compatibility while refactoring. """ if get_autocommit(using): connection = get_connection(using) if connection.autocommit and not connection.in_atomic_block: return commit_on_success(using) else: def entering(using): Loading docs/topics/db/transactions.txt +89 −8 Original line number Diff line number Diff line ============================== Managing database transactions ============================== ===================== Database transactions ===================== .. module:: django.db.transaction Django gives you a few ways to control how database transactions are managed. Managing database transactions ============================== Django's default transaction behavior ===================================== ------------------------------------- Django's default behavior is to run in autocommit mode. Each query is immediately committed to the database. :ref:`See below for details Loading @@ -24,7 +27,7 @@ immediately committed to the database. :ref:`See below for details behavior <transactions-changes-from-1.5>`. Tying transactions to HTTP requests =================================== ----------------------------------- The recommended way to handle transactions in Web requests is to tie them to the request and response phases via Django's ``TransactionMiddleware``. Loading Loading @@ -63,6 +66,85 @@ connection internally. multiple databases and want transaction control over databases other than "default", you will need to write your own transaction middleware. Controlling transactions explicitly ----------------------------------- .. versionadded:: 1.6 Django provides a single API to control database transactions. .. function:: atomic(using=None) This function creates an atomic block for writes to the database. (Atomicity is the defining property of database transactions.) When the block completes successfully, the changes are committed to the database. When it raises an exception, the changes are rolled back. ``atomic`` can be nested. In this case, when an inner block completes successfully, its effects can still be rolled back if an exception is raised in the outer block at a later point. ``atomic`` takes a ``using`` argument which should be the name of a database. If this argument isn't provided, Django uses the ``"default"`` database. ``atomic`` is 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() Wrapping ``atomic`` in a try/except block allows for natural handling of integrity errors:: from django.db import IntegrityError, transaction @transaction.atomic def viewfunc(request): do_stuff() try: with transaction.atomic(): do_stuff_that_could_fail() except IntegrityError: handle_exception() do_more_stuff() In this example, even if ``do_stuff_that_could_fail()`` causes a database error by breaking an integrity constraint, you can execute queries in ``do_more_stuff()``, and the changes from ``do_stuff()`` are still there. In order to guarantee atomicity, ``atomic`` disables some APIs. Attempting to commit, roll back, or change the autocommit state of the database connection within an ``atomic`` block will raise an exception. ``atomic`` can only be used in autocommit mode. It will raise an exception if autocommit is turned off. Under the hood, Django's transaction management code: - opens a transaction when entering the outermost ``atomic`` block; - creates a savepoint when entering an inner ``atomic`` block; - releases or rolls back to the savepoint when exiting an inner block; - commits or rolls back the transaction when exiting the outermost block. .. _transaction-management-functions: Controlling transaction management in views Loading Loading @@ -325,9 +407,8 @@ When autocommit is enabled, savepoints don't make sense. When it's disabled, commits before any statement other than ``SELECT``, ``INSERT``, ``UPDATE``, ``DELETE`` and ``REPLACE``.) As a consequence, savepoints are only usable if you start a transaction manually while in autocommit mode, and Django doesn't provide an API to achieve that. As a consequence, savepoints are only usable inside a transaction ie. inside an :func:`atomic` block. Transactions in MySQL --------------------- Loading Loading
AUTHORS +1 −0 Original line number Diff line number Diff line Loading @@ -434,6 +434,7 @@ answer newbie questions, and generally made Django that much better: Andreas Pelme <andreas@pelme.se> permonik@mesias.brnonet.cz peter@mymart.com Christophe Pettus <xof@thebuild.com> pgross@thoughtworks.com phaedo <http://phaedo.cx/> phil@produxion.net Loading
django/db/backends/__init__.py +21 −2 Original line number Diff line number Diff line Loading @@ -50,6 +50,12 @@ class BaseDatabaseWrapper(object): # set somewhat aggressively, as the DBAPI doesn't make it easy to # deduce if the connection is in transaction or not. self._dirty = False # Tracks if the connection is in a transaction managed by 'atomic' self.in_atomic_block = False # List of savepoints created by 'atomic' self.savepoint_ids = [] # Hack to provide compatibility with legacy transaction management self._atomic_forced_unmanaged = False # Connection termination related attributes self.close_at = None Loading Loading @@ -148,7 +154,7 @@ class BaseDatabaseWrapper(object): def commit(self): """ Does the commit itself and resets the dirty flag. Commits a transaction and resets the dirty flag. """ self.validate_thread_sharing() self._commit() Loading @@ -156,7 +162,7 @@ class BaseDatabaseWrapper(object): def rollback(self): """ Does the rollback itself and resets the dirty flag. Rolls back a transaction and resets the dirty flag. """ self.validate_thread_sharing() self._rollback() Loading Loading @@ -447,6 +453,12 @@ class BaseDatabaseWrapper(object): if must_close: self.close() def _start_transaction_under_autocommit(self): """ Only required when autocommits_when_autocommit_is_off = True. """ raise NotImplementedError class BaseDatabaseFeatures(object): allows_group_by_pk = False Loading Loading @@ -549,6 +561,10 @@ class BaseDatabaseFeatures(object): # Support for the DISTINCT ON clause can_distinct_on_fields = False # Does the backend decide to commit before SAVEPOINT statements # when autocommit is disabled? http://bugs.python.org/issue8145#msg109965 autocommits_when_autocommit_is_off = False def __init__(self, connection): self.connection = connection Loading Loading @@ -931,6 +947,9 @@ class BaseDatabaseOperations(object): return "BEGIN;" def end_transaction_sql(self, success=True): """ Returns the SQL statement required to end a transaction. """ if not success: return "ROLLBACK;" return "COMMIT;" Loading
django/db/backends/sqlite3/base.py +15 −4 Original line number Diff line number Diff line Loading @@ -99,6 +99,7 @@ class DatabaseFeatures(BaseDatabaseFeatures): supports_mixed_date_datetime_comparisons = False has_bulk_insert = True can_combine_inserts_with_and_without_auto_increment_pk = False autocommits_when_autocommit_is_off = True @cached_property def uses_savepoints(self): Loading Loading @@ -360,10 +361,12 @@ class DatabaseWrapper(BaseDatabaseWrapper): BaseDatabaseWrapper.close(self) def _savepoint_allowed(self): # When 'isolation_level' is None, Django doesn't provide a way to # create a transaction (yet) so savepoints can't be created. When it # isn't, sqlite3 commits before each savepoint -- it's a bug. return False # When 'isolation_level' is not None, sqlite3 commits before each # savepoint; it's a bug. When it is None, savepoints don't make sense # because autocommit is enabled. The only exception is inside atomic # blocks. To work around that bug, on SQLite, atomic starts a # transaction explicitly rather than simply disable autocommit. return self.in_atomic_block def _set_autocommit(self, autocommit): if autocommit: Loading Loading @@ -413,6 +416,14 @@ class DatabaseWrapper(BaseDatabaseWrapper): def is_usable(self): return True def _start_transaction_under_autocommit(self): """ Start a transaction explicitly in autocommit mode. Staying in autocommit mode works around a bug of sqlite3 that breaks savepoints when autocommit is disabled. """ self.cursor().execute("BEGIN") FORMAT_QMARK_REGEX = re.compile(r'(?<!%)%s') Loading
django/db/transaction.py +150 −7 Original line number Diff line number Diff line Loading @@ -16,7 +16,7 @@ import warnings from functools import wraps from django.db import connections, DEFAULT_DB_ALIAS from django.db import connections, DatabaseError, DEFAULT_DB_ALIAS class TransactionManagementError(Exception): Loading Loading @@ -134,13 +134,13 @@ def rollback_unless_managed(using=None): def commit(using=None): """ Does the commit itself and resets the dirty flag. Commits a transaction and resets the dirty flag. """ get_connection(using).commit() def rollback(using=None): """ This function does the rollback itself and resets the dirty flag. Rolls back a transaction and resets the dirty flag. """ get_connection(using).rollback() Loading @@ -166,9 +166,151 @@ def savepoint_commit(sid, using=None): """ get_connection(using).savepoint_commit(sid) ############## # DECORATORS # ############## ################################# # Decorators / context managers # ################################# class Atomic(object): """ This class guarantees the atomic execution of a given block. An instance can be used either as a decorator or as a context manager. When it's used as a decorator, __call__ wraps the execution of the decorated function in the instance itself, used as a context manager. When it's used as a context manager, __enter__ creates a transaction or a savepoint, depending on whether a transaction is already in progress, and __exit__ commits the transaction or releases the savepoint on normal exit, and rolls back the transaction or to the savepoint on exceptions. A stack of savepoints identifiers is maintained as an attribute of the connection. None denotes a plain transaction. This allows reentrancy even if the same AtomicWrapper is reused. For example, it's possible to define `oa = @atomic('other')` and use `@ao` or `with oa:` multiple times. Since database connections are thread-local, this is thread-safe. """ def __init__(self, using): self.using = using def _legacy_enter_transaction_management(self, connection): if not connection.in_atomic_block: if connection.transaction_state and connection.transaction_state[-1]: connection._atomic_forced_unmanaged = True connection.enter_transaction_management(managed=False) else: connection._atomic_forced_unmanaged = False def _legacy_leave_transaction_management(self, connection): if not connection.in_atomic_block and connection._atomic_forced_unmanaged: connection.leave_transaction_management() def __enter__(self): connection = get_connection(self.using) # Ensure we have a connection to the database before testing # autocommit status. connection.ensure_connection() # Remove this when the legacy transaction management goes away. self._legacy_enter_transaction_management(connection) if not connection.in_atomic_block and not connection.autocommit: raise TransactionManagementError( "'atomic' cannot be used when autocommit is disabled.") if connection.in_atomic_block: # We're already in a transaction; create a savepoint. sid = connection.savepoint() connection.savepoint_ids.append(sid) else: # We aren't in a transaction yet; create one. # The usual way to start a transaction is to turn autocommit off. # However, some database adapters (namely sqlite3) don't handle # transactions and savepoints properly when autocommit is off. # In such cases, start an explicit transaction instead, which has # the side-effect of disabling autocommit. if connection.features.autocommits_when_autocommit_is_off: connection._start_transaction_under_autocommit() connection.autocommit = False else: connection.set_autocommit(False) connection.in_atomic_block = True connection.savepoint_ids.append(None) def __exit__(self, exc_type, exc_value, traceback): connection = get_connection(self.using) sid = connection.savepoint_ids.pop() if exc_value is None: if sid is None: # Commit transaction connection.in_atomic_block = False try: connection.commit() except DatabaseError: connection.rollback() # Remove this when the legacy transaction management goes away. self._legacy_leave_transaction_management(connection) raise finally: if connection.features.autocommits_when_autocommit_is_off: connection.autocommit = True else: connection.set_autocommit(True) else: # Release savepoint try: connection.savepoint_commit(sid) except DatabaseError: connection.savepoint_rollback(sid) # Remove this when the legacy transaction management goes away. self._legacy_leave_transaction_management(connection) raise else: if sid is None: # Roll back transaction connection.in_atomic_block = False try: connection.rollback() finally: if connection.features.autocommits_when_autocommit_is_off: connection.autocommit = True else: connection.set_autocommit(True) else: # Roll back to savepoint connection.savepoint_rollback(sid) # Remove this when the legacy transaction management goes away. self._legacy_leave_transaction_management(connection) def __call__(self, func): @wraps(func) def inner(*args, **kwargs): with self: return func(*args, **kwargs) return inner def atomic(using=None): # Bare decorator: @atomic -- although the first argument is called # `using`, it's actually the function being decorated. if callable(using): return Atomic(DEFAULT_DB_ALIAS)(using) # Decorator: @atomic(...) or context manager: with atomic(...): ... else: return Atomic(using) ############################################ # Deprecated decorators / context managers # ############################################ class Transaction(object): """ Loading Loading @@ -279,7 +421,8 @@ def commit_on_success_unless_managed(using=None): """ Transitory API to preserve backwards-compatibility while refactoring. """ if get_autocommit(using): connection = get_connection(using) if connection.autocommit and not connection.in_atomic_block: return commit_on_success(using) else: def entering(using): Loading
docs/topics/db/transactions.txt +89 −8 Original line number Diff line number Diff line ============================== Managing database transactions ============================== ===================== Database transactions ===================== .. module:: django.db.transaction Django gives you a few ways to control how database transactions are managed. Managing database transactions ============================== Django's default transaction behavior ===================================== ------------------------------------- Django's default behavior is to run in autocommit mode. Each query is immediately committed to the database. :ref:`See below for details Loading @@ -24,7 +27,7 @@ immediately committed to the database. :ref:`See below for details behavior <transactions-changes-from-1.5>`. Tying transactions to HTTP requests =================================== ----------------------------------- The recommended way to handle transactions in Web requests is to tie them to the request and response phases via Django's ``TransactionMiddleware``. Loading Loading @@ -63,6 +66,85 @@ connection internally. multiple databases and want transaction control over databases other than "default", you will need to write your own transaction middleware. Controlling transactions explicitly ----------------------------------- .. versionadded:: 1.6 Django provides a single API to control database transactions. .. function:: atomic(using=None) This function creates an atomic block for writes to the database. (Atomicity is the defining property of database transactions.) When the block completes successfully, the changes are committed to the database. When it raises an exception, the changes are rolled back. ``atomic`` can be nested. In this case, when an inner block completes successfully, its effects can still be rolled back if an exception is raised in the outer block at a later point. ``atomic`` takes a ``using`` argument which should be the name of a database. If this argument isn't provided, Django uses the ``"default"`` database. ``atomic`` is 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() Wrapping ``atomic`` in a try/except block allows for natural handling of integrity errors:: from django.db import IntegrityError, transaction @transaction.atomic def viewfunc(request): do_stuff() try: with transaction.atomic(): do_stuff_that_could_fail() except IntegrityError: handle_exception() do_more_stuff() In this example, even if ``do_stuff_that_could_fail()`` causes a database error by breaking an integrity constraint, you can execute queries in ``do_more_stuff()``, and the changes from ``do_stuff()`` are still there. In order to guarantee atomicity, ``atomic`` disables some APIs. Attempting to commit, roll back, or change the autocommit state of the database connection within an ``atomic`` block will raise an exception. ``atomic`` can only be used in autocommit mode. It will raise an exception if autocommit is turned off. Under the hood, Django's transaction management code: - opens a transaction when entering the outermost ``atomic`` block; - creates a savepoint when entering an inner ``atomic`` block; - releases or rolls back to the savepoint when exiting an inner block; - commits or rolls back the transaction when exiting the outermost block. .. _transaction-management-functions: Controlling transaction management in views Loading Loading @@ -325,9 +407,8 @@ When autocommit is enabled, savepoints don't make sense. When it's disabled, commits before any statement other than ``SELECT``, ``INSERT``, ``UPDATE``, ``DELETE`` and ``REPLACE``.) As a consequence, savepoints are only usable if you start a transaction manually while in autocommit mode, and Django doesn't provide an API to achieve that. As a consequence, savepoints are only usable inside a transaction ie. inside an :func:`atomic` block. Transactions in MySQL --------------------- Loading