--- /dev/null
+.. change::
+ :tags: bug, orm
+ :tickets: 6072, 6487
+
+ Clarified the current purpose of the
+ :paramref:`_orm.relationship.bake_queries` flag, which in 1.4 is to enable
+ or disable "lambda caching" of statements within the "lazyload" and
+ "selectinload" loader strategies; this is separate from the more
+ foundational SQL query cache that is used for most statements.
+ Additionally, the lazy loader no longer uses its own cache for many-to-one
+ SQL queries, which was an implementation quirk that doesn't exist for any
+ other loader scenario. Finally, the "lru cache" warning that the lazyloader
+ and selectinloader strategies could emit when handling a wide array of
+ class/relationship combinations has been removed; based on analysis of some
+ end-user cases, this warning doesn't suggest any significant issue. While
+ setting ``bake_queries=False`` for such a relationship will remove this
+ cache from being used, there's no particular performance gain in this case
+ as using no caching vs. using a cache that needs to refresh often likely
+ still wins out on the caching being used side.
+
:ref:`error_qzyx` - usage example
:param bake_queries=True:
- Use the :class:`.BakedQuery` cache to cache the construction of SQL
- used in lazy loads. True by default. Set to False if the
- join condition of the relationship has unusual features that
- might not respond well to statement caching.
-
- .. versionchanged:: 1.2
- "Baked" loading is the default implementation for the "select",
- a.k.a. "lazy" loading strategy for relationships.
-
- .. versionadded:: 1.0.0
-
- .. seealso::
+ Enable :ref:`lambda caching <engine_lambda_caching>`_ for loader
+ strategies, if applicable, which adds a performance gain to the
+ construction of SQL constructs used by loader strategies, in addition
+ to the usual SQL statement caching used throughout SQLAlchemy. This
+ parameter currently applies only to the "lazy" and "selectin" loader
+ strategies. There is generally no reason to set this parameter to
+ False.
- :ref:`baked_toplevel`
+ .. versionchanged:: 1.4 Relationship loaders no longer use the
+ previous "baked query" system of query caching. The "lazy"
+ and "selectin" loaders make use of the "lambda cache" system
+ for the construction of SQL constructs,
+ as well as the usual SQL caching system that is throughout
+ SQLAlchemy as of the 1.4 series.
:param cascade:
A comma-separated list of cascade rules which determines how
self.target = self.parent_property.target
self.uselist = self.parent_property.uselist
- def _size_alert(self, lru_cache):
- util.warn("LRU cache size alert for loader strategy: %s" % self)
-
@log.class_logger
@relationships.RelationshipProperty.strategy_for(do_nothing=True)
"_simple_lazy_clause",
"_raise_always",
"_raise_on_sql",
- "_query_cache",
+ "_lambda_cache",
)
def __init__(self, parent, strategy_key):
for pk in self.mapper.primary_key
]
- def _memoized_attr__query_cache(self):
- # cache is per lazy loader; stores not only cached SQL but also
+ def _memoized_attr__lambda_cache(self):
+ # cache is per lazy loader, and is used for caching of
# sqlalchemy.sql.lambdas.AnalyzedCode and
# sqlalchemy.sql.lambdas.AnalyzedFunction objects which are generated
# from the StatementLambda used.
- return util.LRUCache(30, size_alert=self._size_alert)
+ return util.LRUCache(30)
@util.preload_module("sqlalchemy.orm.strategy_options")
def _emit_lazyload(
.set_label_style(LABEL_STYLE_TABLENAME_PLUS_COL)
._set_compile_options(ORMCompileState.default_compile_options),
global_track_bound_values=False,
- lambda_cache=self._query_cache,
+ lambda_cache=self._lambda_cache,
track_on=(self,),
)
self._invoke_raise_load(state, passive, "raise_on_sql")
return loading.load_on_pk_identity(
- session,
- stmt,
- primary_key_identity,
- load_options=load_options,
- execution_options={"compiled_cache": self._query_cache},
+ session, stmt, primary_key_identity, load_options=load_options
)
if self._order_by:
execution_options = {
"_sa_orm_load_options": load_options,
}
- if not self.parent_property.bake_queries:
- execution_options["compiled_cache"] = None
if self.key in state.dict:
return attributes.ATTR_WAS_SET
q = self.subq
assert q.session is None
- if "compiled_cache" in self.execution_options:
- q = q.execution_options(
- compiled_cache=self.execution_options["compiled_cache"]
- )
+
q = q.with_session(self.session)
if self.load_options._populate_existing:
"_parent_alias",
"_query_info",
"_fallback_query_info",
- "_query_cache",
+ "_lambda_cache",
)
query_info = collections.namedtuple(
(("lazy", "select"),)
).init_class_attribute(mapper)
- def _memoized_attr__query_cache(self):
- return util.LRUCache(30, size_alert=self._size_alert)
+ def _memoized_attr__lambda_cache(self):
+ # cache is per lazy loader, and is used for caching of
+ # sqlalchemy.sql.lambdas.AnalyzedCode and
+ # sqlalchemy.sql.lambdas.AnalyzedFunction objects which are generated
+ # from the StatementLambda used.
+ return util.LRUCache(30)
def create_row_processor(
self,
"plugin_subject": effective_entity,
}
),
- lambda_cache=self._query_cache,
+ lambda_cache=self._lambda_cache,
global_track_bound_values=False,
track_on=(self, effective_entity) + (tuple(pk_cols),),
)
self.assert_sql_count(testing.db, go2, 2)
-class LoadManyToOneFromIdentityTest(NoCache, fixtures.MappedTest):
+class LoadManyToOneFromIdentityTest(fixtures.MappedTest):
"""test overhead associated with many-to-one fetches.
sess = Session(connection)
with mock.patch(
- "sqlalchemy.orm.strategies.LazyLoader._query_cache", cache
+ "sqlalchemy.orm.strategies.LazyLoader._lambda_cache", cache
), mock.patch(
- "sqlalchemy.orm.strategies.SelectInLoader._query_cache", cache
+ "sqlalchemy.orm.strategies.SelectInLoader._lambda_cache", cache
):
def go():
self.assert_sql_count(testing.db, go, 2)
- def test_query_is_cached(self):
- users, Address, addresses, User = (
- self.tables.users,
- self.classes.Address,
- self.tables.addresses,
- self.classes.User,
- )
-
- mapper(
- User,
- users,
- properties={
- "addresses": relationship(
- mapper(Address, addresses),
- lazy="subquery",
- order_by=Address.id,
- )
- },
- )
- query_cache = {}
- sess = fixture_session()
-
- def go():
- sess.close()
-
- stmt = select(User).filter(User.id == 7)
-
- sess.execute(
- stmt, execution_options={"compiled_cache": query_cache}
- ).one()
-
- for i in range(3):
- go()
-
- qclen = len(query_cache)
-
- for i in range(5):
- go()
-
- eq_(len(query_cache), qclen)
-
def test_params_arent_cached(self):
users, Address, addresses, User = (
self.tables.users,