diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2015-06-22 21:33:40 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2015-06-22 21:33:40 -0400 |
commit | be2b35f7f2156c4b723df9e7be389e6575afedc4 (patch) | |
tree | 74f3150b2bc7cbe53dd012de26e2119424b33cb0 | |
parent | ee5bf27eb80b1f14d797bc410225d64f20747806 (diff) | |
download | sqlalchemy-be2b35f7f2156c4b723df9e7be389e6575afedc4.tar.gz |
- add a new FAQ on re-reading of data within a transaction, since
this is definitely something that is asked regularly.
-rw-r--r-- | doc/build/faq/sessions.rst | 62 | ||||
-rw-r--r-- | doc/build/orm/session_basics.rst | 12 |
2 files changed, 71 insertions, 3 deletions
diff --git a/doc/build/faq/sessions.rst b/doc/build/faq/sessions.rst index 300b4bdbc..acdd7b35f 100644 --- a/doc/build/faq/sessions.rst +++ b/doc/build/faq/sessions.rst @@ -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) --------------------------------------------------------------------------------------------------------- diff --git a/doc/build/orm/session_basics.rst b/doc/build/orm/session_basics.rst index 8919864ca..de0092268 100644 --- a/doc/build/orm/session_basics.rst +++ b/doc/build/orm/session_basics.rst @@ -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. |