From: Mike Bayer Date: Mon, 1 Nov 2010 19:59:53 +0000 (-0400) Subject: - write a new section describing the "subtransactions=True" flag in full detail X-Git-Tag: rel_0_7b1~286 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=125c7e2dbb8a0fb05d3846b429c6109a2c6f3601;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - write a new section describing the "subtransactions=True" flag in full detail --- diff --git a/doc/build/core/connections.rst b/doc/build/core/connections.rst index 91e1d698b3..7591e73e22 100644 --- a/doc/build/core/connections.rst +++ b/doc/build/core/connections.rst @@ -144,6 +144,11 @@ guaranteed to ``rollback()`` or ``commit()``:: trans.rollback() raise +.. _connections_nested_transactions: + +Nesting of Transaction Blocks +------------------------------ + The :class:`~sqlalchemy.engine.base.Transaction` object also handles "nested" behavior by keeping track of the outermost begin/commit pair. In this example, two functions both issue a transaction on a Connection, but only the outermost diff --git a/doc/build/orm/session.rst b/doc/build/orm/session.rst index 16ca7aff00..fabac0470f 100644 --- a/doc/build/orm/session.rst +++ b/doc/build/orm/session.rst @@ -996,6 +996,8 @@ statement:: item1.foo = 'bar' item2.bar = 'foo' +.. _session_begin_nested: + Using SAVEPOINT --------------- @@ -1028,6 +1030,95 @@ session is expired, thus causing all subsequent attribute/instance access to reference the full state of the :class:`~sqlalchemy.orm.session.Session` right before :func:`~sqlalchemy.orm.session.Session.begin_nested` was called. +.. _session_subtransactions: + +Using Subtransactions +--------------------- + +A subtransaction, as offered by the ``subtransactions=True`` flag of :meth:`.Session.begin`, +is a non-transactional, delimiting construct that +allows nesting of calls to :meth:`~.Session.begin` and :meth:`~.Session.commit`. +It's purpose is to allow the construction of code that can function within a transaction +both independently of any external code that starts a transaction, +as well as within a block that has already demarcated a transaction. By "non-transactional", we +mean that no actual transactional dialogue with the database is generated by this flag beyond that of +a single call to :meth:`~.Session.begin`, regardless of how many times the method +is called within a transaction. + +The subtransaction feature is in fact intrinsic to any call to :meth:`~.Session.flush`, which uses +it internally to ensure that the series of flush steps are enclosed within a transaction, +regardless of the setting of ``autocommit`` or the presence of an existing transactional context. +However, explicit usage of the ``subtransactions=True`` flag is generally only useful with an +application that uses the +:class:`.Session` in "autocommit=True" mode, and calls :meth:`~.Session.begin` explicitly +in order to demarcate transactions. For this reason the subtransaction feature is not +commonly used in an explicit way, except for apps that integrate SQLAlchemy-level transaction control with +the transaction control of another library or subsystem. For true, general purpose "nested" +transactions, where a rollback affects only a portion of the work which has proceeded, +savepoints should be used, documented in :ref:`session_begin_nested`. + +The feature is the ORM equivalent to the pattern described at :ref:`connections_nested_transactions`, +where any number of functions can call :meth:`.Connection.begin` and :meth:`.Transaction.commit` +as though they are the initiator of the transaction, but in fact may be participating +in an already ongoing transaction. + +As is the case with the non-ORM :class:`.Transaction` object, +calling :meth:`.Session.rollback` rolls back the **entire** +transaction, which was initiated by the first call to +:meth:`.Session.begin` (whether this call was explicit by the +end user, or implicit in an ``autocommit=False`` scenario). +However, the :class:`.Session` still considers itself to be in a +"partially rolled back" state until :meth:`.Session.rollback` is +called explicitly for each call that was made to +:meth:`.Session.begin`, where "partially rolled back" means that +no further SQL operations can proceed until each level +of the transaction has been acounted for, unless the :meth:`~.Session.close` method +is called which cancels all transactional markers. For a full exposition on +the rationale for this, +please see `But why isn't the one automatic call to ROLLBACK +enough ? Why must I ROLLBACK again? +`_. +In short, if subtransactions are used as intended, that is, as a means to nest multiple +begin/commit pairs, the appropriate rollback calls naturally occur in any case. + +An example of ``subtransactions=True`` is nearly identical to that of the non-ORM method. The nesting +of transactions, as well as the natural calling of "rollback" for all transactions, is illustrated:: + + # method_a starts a transaction and calls method_b + def method_a(session): + session.begin(subtransactions=True) # open a transaction. If there was + # no previous call to begin(), this will + # be a real transaction. + try: + method_b(session) + session.commit() # transaction is committed here + except: + session.rollback() # rolls back the transaction + raise + + # method_b also starts a transaction + def method_b(connection): + session.begin(subtransactions=True) # open a transaction - this + # runs in the context of method_a()'s + # transaction + try: + session.add(SomeObject('bat', 'lala')) + session.commit() # transaction is not committed yet + except: + session.rollback() # rolls back the transaction, in this case + # the one that was initiated in method_a(). + raise + + # create a Session and call method_a + session = Session(autocommit=True) + method_a(session) + session.close() + +Since the :meth:`.Session.flush` method uses a subtransaction, a failed flush +will always issue a rollback which then affects the state of the outermost transaction (unless a SAVEPOINT +is in use). This forces the need to issue :meth:`~.Session.rollback` for the full operation +before subsequent SQL operations can proceed. + Enabling Two-Phase Commit ------------------------- diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py index f2bbb9e458..80c353ebca 100644 --- a/lib/sqlalchemy/orm/session.py +++ b/lib/sqlalchemy/orm/session.py @@ -532,20 +532,13 @@ class Session(object): transaction or nested transaction, an error is raised, unless ``subtransactions=True`` or ``nested=True`` is specified. - The ``subtransactions=True`` flag indicates that this ``begin()`` can - create a subtransaction if a transaction is already in progress. A - subtransaction is a non-transactional, delimiting construct that - allows matching begin()/commit() pairs to be nested together, with - only the outermost begin/commit pair actually affecting transactional - state. When a rollback is issued, the subtransaction will directly - roll back the innermost real transaction, however each subtransaction - still must be explicitly rolled back to maintain proper stacking of - subtransactions. - - If no transaction is in progress, then a real transaction is begun. - + The ``subtransactions=True`` flag indicates that this :meth:`~.Session.begin` + can create a subtransaction if a transaction is already in progress. + For documentation on subtransactions, please see :ref:`session_subtransactions`. + The ``nested`` flag begins a SAVEPOINT transaction and is equivalent - to calling ``begin_nested()``. + to calling :meth:`~.Session.begin_nested`. For documentation on SAVEPOINT + transactions, please see :ref:`session_begin_nested`. """ if self.transaction is not None: @@ -567,10 +560,8 @@ class Session(object): The target database(s) must support SQL SAVEPOINTs or a SQLAlchemy-supported vendor implementation of the idea. - The nested transaction is a real transation, unlike a "subtransaction" - which corresponds to multiple ``begin()`` calls. The next - ``rollback()`` or ``commit()`` call will operate upon this nested - transaction. + For documentation on SAVEPOINT + transactions, please see :ref:`session_begin_nested`. """ return self.begin(nested=True)