diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2020-05-25 22:36:44 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2020-05-28 14:38:56 -0400 |
commit | 77f1b7d236dba6b1c859bb428ef32d118ec372e6 (patch) | |
tree | 7fae8eaaf303d6ce02bd423abf216550001e2f7b /lib/sqlalchemy/orm/query.py | |
parent | 366e88ea0e5c5417184c1dd4776cff752560631d (diff) | |
download | sqlalchemy-77f1b7d236dba6b1c859bb428ef32d118ec372e6.tar.gz |
callcount reductions and refinement for cached queries
This commit includes that we've removed the "_orm_query"
attribute from compile state as well as query context.
The attribute created reference cycles and also added
method call overhead. As part of this change,
the interface for ORMExecuteState changes a bit, as well
as the interface for the horizontal sharding extension
which now deprecates the "query_chooser" callable
in favor of "execute_chooser", which receives the contextual
object. This will also work more nicely when we implement
the new execution path for bulk updates and deletes.
Pre-merge execution options for statement, connection,
arguments all up front in Connection. that way they
can be passed to the before_execute / after_execute events,
and the ExecutionContext doesn't have to merge as second
time. Core execute is pretty close to 1.3 now.
baked wasn't using the new one()/first()/one_or_none() methods,
fixed that.
Convert non-buffered cursor strategy to be a stateless
singleton. inline all the paths by which the strategy
gets chosen, oracle and SQL Server dialects make use of the
already-invoked post_exec() hook to establish the alternate
strategies, and this is actually much nicer than it was before.
Add caching to mapper instance processor for getters.
Identified a reference cycle per query that was showing
up as a lot of gc cleanup, fixed that.
After all that, performance not budging much. Even
test_baked_query now runs with significantly fewer function
calls than 1.3, still 40% slower.
Basically something about the new patterns just makes
this slower and while I've walked a whole bunch of them
back, it hardly makes a dent. that said, the performance
issues are relatively small, in the 20-40% time increase
range, and the new caching feature
does provide for regular ORM and Core queries that
are cached, and they are faster than non-cached.
Change-Id: I7b0b0d8ca550c05f79e82f75cd8eff0bbfade053
Diffstat (limited to 'lib/sqlalchemy/orm/query.py')
-rw-r--r-- | lib/sqlalchemy/orm/query.py | 93 |
1 files changed, 28 insertions, 65 deletions
diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index 25d6f4736..97a81e30f 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -128,6 +128,7 @@ class Query( _aliased_generation = None _enable_assertions = True _last_joined_entity = None + _statement = None # mirrors that of ClauseElement, used to propagate the "orm" # plugin as well as the "subject" of the plugin, e.g. the mapper @@ -232,7 +233,7 @@ class Query( return if ( self._where_criteria - or self.compile_options._statement is not None + or self._statement is not None or self._from_obj or self._legacy_setup_joins or self._limit_clause is not None @@ -250,7 +251,7 @@ class Query( self._no_criterion_assertion(meth, order_by, distinct) self._from_obj = self._legacy_setup_joins = () - if self.compile_options._statement is not None: + if self._statement is not None: self.compile_options += {"_statement": None} self._where_criteria = () self._distinct = False @@ -270,7 +271,7 @@ class Query( def _no_statement_condition(self, meth): if not self._enable_assertions: return - if self.compile_options._statement is not None: + if self._statement is not None: raise sa_exc.InvalidRequestError( ( "Query.%s() being called on a Query with an existing full " @@ -356,7 +357,6 @@ class Query( if ( not self.compile_options._set_base_alias and not self.compile_options._with_polymorphic_adapt_map - # and self.compile_options._statement is None ): # if we don't have legacy top level aliasing features in use # then convert to a future select() directly @@ -383,48 +383,25 @@ class Query( if not fn._bake_ok: self.compile_options += {"_bake_ok": False} - if self.compile_options._statement is not None: - stmt = FromStatement( - self._raw_columns, self.compile_options._statement - ) - # TODO: once SubqueryLoader uses select(), we can remove - # "_orm_query" from this structure + compile_options = self.compile_options + compile_options += {"_use_legacy_query_style": True} + + if self._statement is not None: + stmt = FromStatement(self._raw_columns, self._statement) stmt.__dict__.update( _with_options=self._with_options, _with_context_options=self._with_context_options, - compile_options=self.compile_options - + {"_orm_query": self.with_session(None)}, + compile_options=compile_options, _execution_options=self._execution_options, ) stmt._propagate_attrs = self._propagate_attrs else: + # Query / select() internal attributes are 99% cross-compatible stmt = FutureSelect.__new__(FutureSelect) - + stmt.__dict__.update(self.__dict__) stmt.__dict__.update( - _raw_columns=self._raw_columns, - _where_criteria=self._where_criteria, - _from_obj=self._from_obj, - _legacy_setup_joins=self._legacy_setup_joins, - _order_by_clauses=self._order_by_clauses, - _group_by_clauses=self._group_by_clauses, - _having_criteria=self._having_criteria, - _distinct=self._distinct, - _distinct_on=self._distinct_on, - _with_options=self._with_options, - _with_context_options=self._with_context_options, - _hints=self._hints, - _statement_hints=self._statement_hints, - _correlate=self._correlate, - _auto_correlate=self._auto_correlate, - _limit_clause=self._limit_clause, - _offset_clause=self._offset_clause, - _for_update_arg=self._for_update_arg, - _prefixes=self._prefixes, - _suffixes=self._suffixes, _label_style=self._label_style, - compile_options=self.compile_options - + {"_orm_query": self.with_session(None)}, - _execution_options=self._execution_options, + compile_options=compile_options, ) if not orm_results: @@ -897,9 +874,11 @@ class Query( :return: The object instance, or ``None``. """ + self._no_criterion_assertion("get", order_by=False, distinct=False) return self._get_impl(ident, loading.load_on_pk_identity) def _get_impl(self, primary_key_identity, db_load_fn, identity_token=None): + # convert composite types to individual args if hasattr(primary_key_identity, "__composite_values__"): primary_key_identity = primary_key_identity.__composite_values__() @@ -977,33 +956,14 @@ class Query( """An :class:`.InstanceState` that is using this :class:`_query.Query` for a lazy load operation. - The primary rationale for this attribute is to support the horizontal - sharding extension, where it is available within specific query - execution time hooks created by this extension. To that end, the - attribute is only intended to be meaningful at **query execution - time**, and importantly not any time prior to that, including query - compilation time. - - .. note:: - - Within the realm of regular :class:`_query.Query` usage, this - attribute is set by the lazy loader strategy before the query is - invoked. However there is no established hook that is available to - reliably intercept this value programmatically. It is set by the - lazy loading strategy after any mapper option objects would have - been applied, and now that the lazy loading strategy in the ORM - makes use of "baked" queries to cache SQL compilation, the - :meth:`.QueryEvents.before_compile` hook is also not reliable. + .. deprecated:: 1.4 This attribute should be viewed via the + :attr:`.ORMExecuteState.lazy_loaded_from` attribute, within + the context of the :meth:`.SessionEvents.do_orm_execute` + event. - Currently, setting the :paramref:`_orm.relationship.bake_queries` - to ``False`` on the target :func:`_orm.relationship`, and then - making use of the :meth:`.QueryEvents.before_compile` event hook, - is the only available programmatic path to intercepting this - attribute. In future releases, there will be new hooks available - that allow interception of the :class:`_query.Query` before it is - executed, rather than before it is compiled. + .. seealso:: - .. versionadded:: 1.2.9 + :attr:`.ORMExecuteState.lazy_loaded_from` """ return self.load_options._lazy_loaded_from @@ -2713,6 +2673,7 @@ class Query( statement = coercions.expect( roles.SelectStatementRole, statement, apply_propagate_attrs=self ) + self._statement = statement self.compile_options += {"_statement": statement} def first(self): @@ -2736,7 +2697,7 @@ class Query( """ # replicates limit(1) behavior - if self.compile_options._statement is not None: + if self._statement is not None: return self._iter().first() else: return self.limit(1)._iter().first() @@ -2918,7 +2879,9 @@ class Query( "for linking ORM results to arbitrary select constructs.", version="1.4", ) - compile_state = ORMCompileState._create_for_legacy_query(self) + compile_state = ORMCompileState._create_for_legacy_query( + self, toplevel=True + ) context = QueryContext( compile_state, self.session, self.load_options ) @@ -3332,7 +3295,7 @@ class Query( def _compile_state(self, for_statement=False, **kw): return ORMCompileState._create_for_legacy_query( - self, for_statement=for_statement, **kw + self, toplevel=True, for_statement=for_statement, **kw ) def _compile_context(self, for_statement=False): @@ -3366,7 +3329,7 @@ class FromStatement(SelectStatementGrouping, Executable): super(FromStatement, self).__init__(element) def _compiler_dispatch(self, compiler, **kw): - compile_state = self._compile_state_factory(self, self, **kw) + compile_state = self._compile_state_factory(self, compiler, **kw) toplevel = not compiler.stack |