From: Mike Bayer Date: Mon, 14 Aug 2023 14:13:01 +0000 (-0400) Subject: don't encourage "reader/writer" engines in one session X-Git-Tag: rel_2_0_20~7 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d5e886b282d838b9920175cf23f9c96ca99cda55;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git don't encourage "reader/writer" engines in one session Change-Id: Ia18db210bf22d76147c80e901ac755442faa19be References: #10233 --- diff --git a/doc/build/orm/persistence_techniques.rst b/doc/build/orm/persistence_techniques.rst index dca6cefffd..982f27ebdc 100644 --- a/doc/build/orm/persistence_techniques.rst +++ b/doc/build/orm/persistence_techniques.rst @@ -802,6 +802,10 @@ a custom :class:`.Session` which delivers the following rules: if mapper and issubclass(mapper.class_, MyOtherClass): return engines["other"] elif self._flushing or isinstance(clause, (Update, Delete)): + # NOTE: this is for example, however in practice reader/writer + # splits are likely more straightforward by using two distinct + # Sessions at the top of a "reader" or "writer" operation. + # See note below return engines["leader"] else: return engines[random.choice(["follower1", "follower2"])] @@ -815,6 +819,20 @@ This approach can be combined with multiple :class:`_schema.MetaData` objects, using an approach such as that of using the declarative ``__abstract__`` keyword, described at :ref:`declarative_abstract`. +.. note:: While the above example illustrates routing of specific SQL statements + to a so-called "leader" or "follower" database based on whether or not the + statement expects to write data, this is likely not a practical approach, + as it leads to uncoordinated transaction behavior between reading + and writing within the same operation. In practice, it's likely best + to construct the :class:`_orm.Session` up front as a "reader" or "writer" + session, based on the overall operation / transaction that's proceeding. + That way, an operation that will be writing data will also emit its read-queries + within the same transaction scope. See the example at + :ref:`session_transaction_isolation_enginewide` for a recipe that sets up + one :class:`_orm.sessionmaker` for "read only" operations using autocommit + connections, and another for "write" operations which will include DML / + COMMIT. + .. seealso:: `Django-style Database Routers in SQLAlchemy `_ - blog post on a more comprehensive example of :meth:`.Session.get_bind` diff --git a/doc/build/orm/session_transaction.rst b/doc/build/orm/session_transaction.rst index 85d1cf50be..10da76eda8 100644 --- a/doc/build/orm/session_transaction.rst +++ b/doc/build/orm/session_transaction.rst @@ -474,6 +474,8 @@ order to affect transaction isolation level, we need to act upon the :ref:`dbapi_autocommit` - be sure to review how isolation levels work at the level of the SQLAlchemy :class:`_engine.Connection` object as well. +.. _session_transaction_isolation_enginewide: + Setting Isolation For A Sessionmaker / Engine Wide ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~