]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- add a new FAQ on re-reading of data within a transaction, since
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 23 Jun 2015 01:33:40 +0000 (21:33 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 23 Jun 2015 01:33:40 +0000 (21:33 -0400)
this is definitely something that is asked regularly.

doc/build/faq/sessions.rst
doc/build/orm/session_basics.rst

index 300b4bdbcce23199202fea4af8df98d9a3f7acc2..acdd7b35fb5e8fe1f772c211e64a35e413cf26fb 100644 (file)
@@ -7,6 +7,68 @@ Sessions / Queries
     :backlinks: none
 
 
+I'm re-loading data with my Session but it isn't seeing changes that I committed elsewhere
+------------------------------------------------------------------------------------------
+
+The main issue regarding this behavior is that the session acts as though
+the transaction is in the *serializable *isolation state, even if it's not
+(and it usually is not).   In practical terms, this means that the session
+does not alter any data that it's already read within the scope of a transaction.
+
+If the term "isolation level" made no sense to you, then you first need to read this link:
+
+* https://en.wikipedia.org/wiki/Isolation_%28database_systems%29
+
+Now we know what database isolation is, and what serializable means;
+it means that once you SELECT a series of rows in a transaction, you will get
+*the identical data* each time you re-emit that SELECT.   If you are in
+the next-lower isolation level, "repeatable read", you'll
+see new rows, but for rows that you've *already* loaded, you won't see
+any change.   Only if you are in a lower isolation level, e.g. "read committed",
+does it become possible to see a row of data change its value.
+
+For information on controlling the isolation level from SQLAlchemy,
+see :ref:`session_transaction_isolation`.
+
+To simplify things dramatically, the :class:`.Session` itself works in
+terms of an  isolated transaction, and doesn't overwrite any mapped attributes
+it's already read unless you tell it to; because the use case of trying to re-read
+data you've already loaded in an ongoing transaction is an *uncommon* use
+case that in many cases has no effect.
+
+To understand what we mean by "the transaction" when we talk about the
+:class:`.Session`, your :class:`.Session` is intended to only work within
+a transaction.  An overview of this is at :ref:`unitofwork_transaction`.
+
+Once we've figured out what our isolation level is, and we think that
+our isolation level is set at a low level so that if we re-SELECT a row,
+we should see new data in our :class:`.Session`, how do we see it?
+
+Three ways, from most common to least:
+
+1. We simply end our transaction and start a new one on next access
+   with our :class:`.Session` by calling :meth:`.Session.commit` (note
+   that if the :class:`.Session` is in the lesser-used "autocommit"
+   mode, there would be a call to :meth:`.Session.begin` as well). The
+   vast majority of applications and use cases do not have any issues
+   with not being able to "see" data in other transactions because
+   they stick to this pattern, which is at the core of the best practice of
+   **short lived transactions**.
+   See :ref:`session_faq_whentocreate` for some thoughts on this.
+
+2. We tell our :class:`.Session` to re-read rows that it has already read,
+   either when we next query for them using :meth:`.Session.expire_all`
+   or :meth:`.Session.expire`, or immediately on an object using
+   :class:`.Session.refresh`.  See :ref:`session_expire` for detail on this.
+
+3. We can run whole queries while setting them to definitely overwrite
+   already-loaded objects as they read rows by using
+   :meth:`.Query.populate_existing`.
+
+But remember, **the ORM cannot see changes in rows if our isolation
+level is repeatable read or higher, unless we start a new transaction**.
+
+
 "This Session's transaction has been rolled back due to a previous exception during flush." (or similar)
 ---------------------------------------------------------------------------------------------------------
 
index 8919864caf57633825bd5fc54c6f000c6fe6c0b1..de0092268af564b909e7611ed9ae8e9119185ac8 100644 (file)
@@ -192,9 +192,15 @@ When do I construct a :class:`.Session`, when do I commit it, and when do I clos
 
 .. topic:: tl;dr;
 
-    As a general rule, keep the lifecycle of the session **separate and
-    external** from functions and objects that access and/or manipulate
-    database data.
+    1. As a general rule, keep the lifecycle of the session **separate and
+       external** from functions and objects that access and/or manipulate
+       database data.  This will greatly help with achieving a predictable
+       and consistent transactional scope.
+
+    2. Make sure you have a **clean and clear** notion of where transactions
+       begin and end, and keep transactions **short**, meaning, they end
+       at the series of a sequence of operations, instead of being held
+       open indefinitely.
 
 A :class:`.Session` is typically constructed at the beginning of a logical
 operation where database access is potentially anticipated.