diff options
| author | Aymeric Augustin <aymeric.augustin@m4x.org> | 2013-03-04 23:26:31 +0100 |
|---|---|---|
| committer | Aymeric Augustin <aymeric.augustin@m4x.org> | 2013-03-11 14:48:55 +0100 |
| commit | 7c46c8d5f27fe305507359588ca0635b6d87c59a (patch) | |
| tree | ca99a91cedfd6d8d03a3fbe334ee9589a51ef002 /docs/topics | |
| parent | d7bc4fbc94df6c231d71dffa45cf337ff13512ee (diff) | |
Added some assertions to enforce the atomicity of atomic.
Diffstat (limited to 'docs/topics')
| -rw-r--r-- | docs/topics/db/transactions.txt | 439 |
1 files changed, 199 insertions, 240 deletions
diff --git a/docs/topics/db/transactions.txt b/docs/topics/db/transactions.txt index 2a4cd306c6..91b2cf41b3 100644 --- a/docs/topics/db/transactions.txt +++ b/docs/topics/db/transactions.txt @@ -24,7 +24,7 @@ immediately committed to the database. :ref:`See below for details .. versionchanged:: 1.6 Previous version of Django featured :ref:`a more complicated default - behavior <transactions-changes-from-1.5>`. + behavior <transactions-upgrading-from-1.5>`. Tying transactions to HTTP requests ----------------------------------- @@ -89,7 +89,7 @@ Django provides a single API to control database transactions. database. If this argument isn't provided, Django uses the ``"default"`` database. - ``atomic`` is usable both as a decorator:: + ``atomic`` is usable both as a `decorator`_:: from django.db import transaction @@ -98,7 +98,7 @@ Django provides a single API to control database transactions. # This code executes inside a transaction. do_stuff() - and as a context manager:: + and as a `context manager`_:: from django.db import transaction @@ -110,6 +110,9 @@ Django provides a single API to control database transactions. # This code executes inside a transaction. do_more_stuff() + .. _decorator: http://docs.python.org/glossary.html#term-decorator + .. _context manager: http://docs.python.org/glossary.html#term-context-manager + Wrapping ``atomic`` in a try/except block allows for natural handling of integrity errors:: @@ -145,158 +148,116 @@ Django provides a single API to control database transactions. - 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 -=========================================== - -For most people, implicit request-based transactions work wonderfully. However, -if you need more fine-grained control over how transactions are managed, you can -use a set of functions in ``django.db.transaction`` to control transactions on a -per-function or per-code-block basis. - -These functions, described in detail below, can be used in two different ways: - -* As a decorator_ on a particular function. For example:: - - from django.db import transaction - - @transaction.commit_on_success - def viewfunc(request): - # ... - # this code executes inside a transaction - # ... - -* As a `context manager`_ around a particular block of code:: - - from django.db import transaction - - def viewfunc(request): - # ... - # this code executes using default transaction management - # ... - - with transaction.commit_on_success(): - # ... - # this code executes inside a transaction - # ... - -Both techniques work with all supported version of Python. +.. _topics-db-transactions-savepoints: -.. _decorator: http://docs.python.org/glossary.html#term-decorator -.. _context manager: http://docs.python.org/glossary.html#term-context-manager +Savepoints +========== -For maximum compatibility, all of the examples below show transactions using the -decorator syntax, but all of the follow functions may be used as context -managers, too. +A savepoint is a marker within a transaction that enables you to roll back +part of a transaction, rather than the full transaction. Savepoints are +available with the SQLite (≥ 3.6.8), PostgreSQL, Oracle and MySQL (when using +the InnoDB storage engine) backends. Other backends provide the savepoint +functions, but they're empty operations -- they don't actually do anything. -.. note:: +Savepoints aren't especially useful if you are using autocommit, the default +behavior of Django. However, once you open a transaction with :func:`atomic`, +you build up a series of database operations awaiting a commit or rollback. If +you issue a rollback, the entire transaction is rolled back. Savepoints +provide the ability to perform a fine-grained rollback, rather than the full +rollback that would be performed by ``transaction.rollback()``. - Although the examples below use view functions as examples, these - decorators and context managers can be used anywhere in your code - that you need to deal with transactions. +.. versionchanged:: 1.6 -.. _topics-db-transactions-autocommit: +When the :func:`atomic` decorator is nested, it creates a savepoint to allow +partial commit or rollback. You're strongly encouraged to use :func:`atomic` +rather than the functions described below, but they're still part of the +public API, and there's no plan to deprecate them. -.. function:: autocommit +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. - Use the ``autocommit`` decorator to switch a view function to Django's - default commit behavior. +Savepoints are controlled by three methods on the transaction object: - Example:: +.. method:: transaction.savepoint(using=None) - from django.db import transaction + Creates a new savepoint. This marks a point in the transaction that + is known to be in a "good" state. - @transaction.autocommit - def viewfunc(request): - .... + Returns the savepoint ID (sid). - @transaction.autocommit(using="my_other_database") - def viewfunc2(request): - .... +.. method:: transaction.savepoint_commit(sid, using=None) - Within ``viewfunc()``, transactions will be committed as soon as you call - ``model.save()``, ``model.delete()``, or any other function that writes to - the database. ``viewfunc2()`` will have this same behavior, but for the - ``"my_other_database"`` connection. + Updates the savepoint to include any operations that have been performed + since the savepoint was created, or since the last commit. -.. function:: commit_on_success +.. method:: transaction.savepoint_rollback(sid, using=None) - Use the ``commit_on_success`` decorator to use a single transaction for all - the work done in a function:: + Rolls the transaction back to the last point at which the savepoint was + committed. - from django.db import transaction +The following example demonstrates the use of savepoints:: - @transaction.commit_on_success - def viewfunc(request): - .... + from django.db import transaction - @transaction.commit_on_success(using="my_other_database") - def viewfunc2(request): - .... + # open a transaction + @transaction.atomic + def viewfunc(request): - If the function returns successfully, then Django will commit all work done - within the function at that point. If the function raises an exception, - though, Django will roll back the transaction. + a.save() + # transaction now contains a.save() -.. function:: commit_manually + sid = transaction.savepoint() - Use the ``commit_manually`` decorator if you need full control over - transactions. It tells Django you'll be managing the transaction on your - own. + b.save() + # transaction now contains a.save() and b.save() - Whether you are writing or simply reading from the database, you must - ``commit()`` or ``rollback()`` explicitly or Django will raise a - :exc:`TransactionManagementError` exception. This is required when reading - from the database because ``SELECT`` statements may call functions which - modify tables, and thus it is impossible to know if any data has been - modified. + 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() - Manual transaction management looks like this:: +Autocommit +========== - from django.db import transaction +.. _autocommit-details: - @transaction.commit_manually - def viewfunc(request): - ... - # You can commit/rollback however and whenever you want - transaction.commit() - ... +Why Django uses autocommit +-------------------------- - # But you've got to remember to do it yourself! - try: - ... - except: - transaction.rollback() - else: - transaction.commit() +In the SQL standards, each SQL query starts a transaction, unless one is +already in progress. Such transactions must then be committed or rolled back. - @transaction.commit_manually(using="my_other_database") - def viewfunc2(request): - .... +This isn't always convenient for application developers. To alleviate this +problem, most databases provide an autocommit mode. When autocommit is turned +on, each SQL query is wrapped in its own transaction. In other words, the +transaction is not only automatically started, but also automatically +committed. -.. _topics-db-transactions-requirements: +:pep:`249`, the Python Database API Specification v2.0, requires autocommit to +be initially turned off. Django overrides this default and turns autocommit +on. -Requirements for transaction handling -===================================== +To avoid this, you can :ref:`deactivate the transaction management +<deactivate-transaction-management>`, but it isn't recommended. -Django requires that every transaction that is opened is closed before the -completion of a request. +.. versionchanged:: 1.6 + Before Django 1.6, autocommit was turned off, and it was emulated by + forcing a commit after write operations in the ORM. -If you are using :func:`autocommit` (the default commit mode) or -:func:`commit_on_success`, this will be done for you automatically. However, -if you are manually managing transactions (using the :func:`commit_manually` -decorator), you must ensure that the transaction is either committed or rolled -back before a request is completed. +.. warning:: -This applies to all database operations, not just write operations. Even -if your transaction only reads from the database, the transaction must -be committed or rolled back before you complete a request. + If you're using the database API directly — for instance, you're running + SQL queries with ``cursor.execute()`` — be aware that autocommit is on, + and consider wrapping your operations in a transaction, with + :func:`atomic`, to ensure consistency. .. _managing-autocommit: Managing autocommit -=================== +------------------- .. versionadded:: 1.6 @@ -310,10 +271,17 @@ database connection, if you need to. These functions take a ``using`` argument which should be the name of a database. If it isn't provided, Django uses the ``"default"`` database. +Autocommit is initially turned on. If you turn it off, it's your +responsibility to restore it. + +:func:`atomic` requires autocommit to be turned on; it will raise an exception +if autocommit is off. Django will also refuse to turn autocommit off when an +:func:`atomic` block is active, because that would break atomicity. + .. _deactivate-transaction-management: -How to globally deactivate transaction management -================================================= +Deactivating transaction management +----------------------------------- Control freaks can totally disable all transaction management by setting :setting:`TRANSACTIONS_MANAGED` to ``True`` in the Django settings file. If @@ -328,71 +296,6 @@ something really strange. In almost all situations, you'll be better off using the default behavior, or the transaction middleware, and only modify selected functions as needed. -.. _topics-db-transactions-savepoints: - -Savepoints -========== - -A savepoint is a marker within a transaction that enables you to roll back -part of a transaction, rather than the full transaction. Savepoints are -available with the SQLite (≥ 3.6.8), PostgreSQL, Oracle and MySQL (when using -the InnoDB storage engine) backends. Other backends provide the savepoint -functions, but they're empty operations -- they don't actually do anything. - -Savepoints aren't especially useful if you are using the default -``autocommit`` behavior of Django. However, if you are using -``commit_on_success`` or ``commit_manually``, each open transaction will build -up a series of database operations, awaiting a commit or rollback. If you -issue a rollback, the entire transaction is rolled back. Savepoints provide -the ability to perform a fine-grained rollback, rather than the full rollback -that would be performed by ``transaction.rollback()``. - -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. - -Savepoints are controlled by three methods on the transaction object: - -.. method:: transaction.savepoint(using=None) - - Creates a new savepoint. This marks a point in the transaction that - is known to be in a "good" state. - - Returns the savepoint ID (sid). - -.. method:: transaction.savepoint_commit(sid, using=None) - - Updates the savepoint to include any operations that have been performed - since the savepoint was created, or since the last commit. - -.. method:: transaction.savepoint_rollback(sid, using=None) - - Rolls the transaction back to the last point at which the savepoint was - committed. - -The following example demonstrates the use of savepoints:: - - from django.db import transaction - - @transaction.commit_manually - def viewfunc(request): - - a.save() - # open transaction now contains a.save() - sid = transaction.savepoint() - - b.save() - # open 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() - - transaction.commit() - Database-specific notes ======================= @@ -477,45 +380,57 @@ transaction. For example:: In this example, ``a.save()`` will not be undone in the case where ``b.save()`` raises an exception. -Under the hood -============== +.. _transactions-upgrading-from-1.5: -.. _autocommit-details: +Changes from Django 1.5 and earlier +=================================== -Details on autocommit ---------------------- +The features described below were deprecated in Django 1.6 and will be removed +in Django 1.8. They're documented in order to ease the migration to the new +transaction management APIs. -In the SQL standards, each SQL query starts a transaction, unless one is -already in progress. Such transactions must then be committed or rolled back. +Legacy APIs +----------- -This isn't always convenient for application developers. To alleviate this -problem, most databases provide an autocommit mode. When autocommit is turned -on, each SQL query is wrapped in its own transaction. In other words, the -transaction is not only automatically started, but also automatically -committed. +The following functions, defined in ``django.db.transaction``, provided a way +to control transactions on a per-function or per-code-block basis. They could +be used as decorators or as context managers, and they accepted a ``using`` +argument, exactly like :func:`atomic`. -:pep:`249`, the Python Database API Specification v2.0, requires autocommit to -be initially turned off. Django overrides this default and turns autocommit -on. +.. function:: autocommit -To avoid this, you can :ref:`deactivate the transaction management -<deactivate-transaction-management>`, but it isn't recommended. + Enable Django's default autocommit behavior. -.. versionchanged:: 1.6 - Before Django 1.6, autocommit was turned off, and it was emulated by - forcing a commit after write operations in the ORM. + Transactions will be committed as soon as you call ``model.save()``, + ``model.delete()``, or any other function that writes to the database. -.. warning:: +.. function:: commit_on_success - If you're using the database API directly — for instance, you're running - SQL queries with ``cursor.execute()`` — be aware that autocommit is on, - and consider wrapping your operations in a transaction to ensure - consistency. + Use a single transaction for all the work done in a function. + + If the function returns successfully, then Django will commit all work done + within the function at that point. If the function raises an exception, + though, Django will roll back the transaction. + +.. function:: commit_manually + + Tells Django you'll be managing the transaction on your own. + + Whether you are writing or simply reading from the database, you must + ``commit()`` or ``rollback()`` explicitly or Django will raise a + :exc:`TransactionManagementError` exception. This is required when reading + from the database because ``SELECT`` statements may call functions which + modify tables, and thus it is impossible to know if any data has been + modified. .. _transaction-states: -Transaction management states ------------------------------ +Transaction states +------------------ + +The three functions described above relied on a concept called "transaction +states". This mechanisme was deprecated in Django 1.6, but it's still +available until Django 1.8.. At any time, each database connection is in one of these two states: @@ -529,35 +444,80 @@ Django starts in auto mode. ``TransactionMiddleware``, Internally, Django keeps a stack of states. Activations and deactivations must be balanced. -For example, at the beginning of each HTTP request, ``TransactionMiddleware`` -switches to managed mode; at the end of the request, it commits or rollbacks, +For example, ``commit_on_success`` switches to managed mode when entering the +block of code it controls; when exiting the block, it commits or rollbacks, and switches back to auto mode. -.. admonition:: Nesting decorators / context managers +So :func:`commit_on_success` really has two effects: it changes the +transaction state and it defines an transaction block. Nesting will give the +expected results in terms of transaction state, but not in terms of +transaction semantics. Most often, the inner block will commit, breaking the +atomicity of the outer block. - :func:`commit_on_success` has two effects: it changes the transaction - state, and defines an atomic transaction block. +:func:`autocommit` and :func:`commit_manually` have similar limitations. - Nesting with :func:`autocommit` and :func:`commit_manually` will give the - expected results in terms of transaction state, but not in terms of - transaction semantics. Most often, the inner block will commit, breaking - the atomicity of the outer block. +API changes +----------- -Django currently doesn't provide any APIs to create transactions in auto mode. +Managing transactions +~~~~~~~~~~~~~~~~~~~~~ -.. _transactions-changes-from-1.5: +Starting with Django 1.6, :func:`atomic` is the only supported API for +defining a transaction. Unlike the deprecated APIs, it's nestable and always +guarantees atomicity. -Changes from Django 1.5 and earlier -=================================== +In most cases, it will be a drop-in replacement for :func:`commit_on_success`. -Since version 1.6, Django uses database-level autocommit in auto mode. +During the deprecation period, it's possible to use :func:`atomic` within +:func:`autocommit`, :func:`commit_on_success` or :func:`commit_manually`. +However, the reverse is forbidden, because nesting the old decorators / +context managers breaks atomicity. + +If you enter :func:`atomic` while you're in managed mode, it will trigger a +commit to start from a clean slate. + +Managing autocommit +~~~~~~~~~~~~~~~~~~~ + +Django 1.6 introduces an explicit :ref:`API for mananging autocommit +<managing-autocommit>`. + +To disable autocommit temporarily, instead of:: + with transaction.commit_manually(): + # do stuff + +you should now use:: + + transaction.set_autocommit(autocommit=False) + try: + # do stuff + finally: + transaction.set_autocommit(autocommit=True) + +To enable autocommit temporarily, instead of:: + + with transaction.autocommit(): + # do stuff + +you should now use:: + + transaction.set_autocommit(autocommit=True) + try: + # do stuff + finally: + transaction.set_autocommit(autocommit=False) + +Backwards incompatibilities +--------------------------- + +Since version 1.6, Django uses database-level autocommit in auto mode. Previously, it implemented application-level autocommit by triggering a commit after each ORM write. -As a consequence, each database query (for instance, an -ORM read) started a transaction that lasted until the next ORM write. Such -"automatic transactions" no longer exist in Django 1.6. +As a consequence, each database query (for instance, an ORM read) started a +transaction that lasted until the next ORM write. Such "automatic +transactions" no longer exist in Django 1.6. There are four known scenarios where this is backwards-incompatible. @@ -565,7 +525,7 @@ Note that managed mode isn't affected at all. This section assumes auto mode. See the :ref:`description of modes <transaction-states>` above. Sequences of custom SQL queries -------------------------------- +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you're executing several :ref:`custom SQL queries <executing-custom-sql>` in a row, each one now runs in its own transaction, instead of sharing the @@ -577,20 +537,20 @@ usually followed by a call to ``transaction.commit_unless_managed``, which isn't necessary any more and should be removed. Select for update ------------------ +~~~~~~~~~~~~~~~~~ If you were relying on "automatic transactions" to provide locking between :meth:`~django.db.models.query.QuerySet.select_for_update` and a subsequent write operation — an extremely fragile design, but nonetheless possible — you -must wrap the relevant code in :func:`commit_on_success`. +must wrap the relevant code in :func:`atomic`. Using a high isolation level ----------------------------- +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you were using the "repeatable read" isolation level or higher, and if you relied on "automatic transactions" to guarantee consistency between successive -reads, the new behavior is backwards-incompatible. To maintain consistency, -you must wrap such sequences in :func:`commit_on_success`. +reads, the new behavior might be backwards-incompatible. To enforce +consistency, you must wrap such sequences in :func:`atomic`. MySQL defaults to "repeatable read" and SQLite to "serializable"; they may be affected by this problem. @@ -602,10 +562,9 @@ PostgreSQL and Oracle default to "read committed" and aren't affected, unless you changed the isolation level. Using unsupported database features ------------------------------------ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ With triggers, views, or functions, it's possible to make ORM reads result in database modifications. Django 1.5 and earlier doesn't deal with this case and it's theoretically possible to observe a different behavior after upgrading to -Django 1.6 or later. In doubt, use :func:`commit_on_success` to enforce -integrity. +Django 1.6 or later. In doubt, use :func:`atomic` to enforce integrity. |
