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/context.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/context.py')
-rw-r--r-- | lib/sqlalchemy/orm/context.py | 100 |
1 files changed, 54 insertions, 46 deletions
diff --git a/lib/sqlalchemy/orm/context.py b/lib/sqlalchemy/orm/context.py index 3acab7df7..09f3e7a12 100644 --- a/lib/sqlalchemy/orm/context.py +++ b/lib/sqlalchemy/orm/context.py @@ -39,11 +39,12 @@ from ..sql.visitors import InternalTraversal _path_registry = PathRegistry.root +_EMPTY_DICT = util.immutabledict() + class QueryContext(object): __slots__ = ( "compile_state", - "orm_query", "query", "load_options", "bind_arguments", @@ -74,8 +75,7 @@ class QueryContext(object): _yield_per = None _refresh_state = None _lazy_loaded_from = None - _orm_query = None - _params = util.immutabledict() + _params = _EMPTY_DICT def __init__( self, @@ -87,10 +87,9 @@ class QueryContext(object): ): self.load_options = load_options - self.execution_options = execution_options or {} - self.bind_arguments = bind_arguments or {} + self.execution_options = execution_options or _EMPTY_DICT + self.bind_arguments = bind_arguments or _EMPTY_DICT self.compile_state = compile_state - self.orm_query = compile_state.orm_query self.query = query = compile_state.query self.session = session @@ -118,20 +117,16 @@ class QueryContext(object): % ", ".join(compile_state._no_yield_pers) ) - @property - def is_single_entity(self): - # used for the check if we return a list of entities or tuples. - # this is gone in 2.0 when we no longer make this decision. - return ( - not self.load_options._only_return_tuples - and len(self.compile_state._entities) == 1 - and self.compile_state._entities[0].supports_single_entity - ) + def dispose(self): + self.attributes.clear() + self.load_options._refresh_state = None + self.load_options._lazy_loaded_from = None class ORMCompileState(CompileState): class default_compile_options(CacheableOptions): _cache_key_traversal = [ + ("_use_legacy_query_style", InternalTraversal.dp_boolean), ("_orm_results", InternalTraversal.dp_boolean), ("_bake_ok", InternalTraversal.dp_boolean), ( @@ -140,7 +135,6 @@ class ORMCompileState(CompileState): ), ("_current_path", InternalTraversal.dp_has_cache_key), ("_enable_single_crit", InternalTraversal.dp_boolean), - ("_statement", InternalTraversal.dp_clauseelement), ("_enable_eagerloads", InternalTraversal.dp_boolean), ("_orm_only_from_obj_alias", InternalTraversal.dp_boolean), ("_only_load_props", InternalTraversal.dp_plain_obj), @@ -148,6 +142,7 @@ class ORMCompileState(CompileState): ("_for_refresh_state", InternalTraversal.dp_boolean), ] + _use_legacy_query_style = False _orm_results = True _bake_ok = True _with_polymorphic_adapt_map = () @@ -159,37 +154,36 @@ class ORMCompileState(CompileState): _set_base_alias = False _for_refresh_state = False - # non-cache-key elements mostly for legacy use - _statement = None - _orm_query = None - @classmethod def merge(cls, other): return cls + other._state_dict() - orm_query = None current_path = _path_registry def __init__(self, *arg, **kw): raise NotImplementedError() + def dispose(self): + self.attributes.clear() + @classmethod def create_for_statement(cls, statement_container, compiler, **kw): raise NotImplementedError() @classmethod - def _create_for_legacy_query(cls, query, for_statement=False): + def _create_for_legacy_query(cls, query, toplevel, for_statement=False): stmt = query._statement_20(orm_results=not for_statement) - if query.compile_options._statement is not None: - compile_state_cls = ORMFromStatementCompileState - else: - compile_state_cls = ORMSelectCompileState + # this chooses between ORMFromStatementCompileState and + # ORMSelectCompileState. We could also base this on + # query._statement is not None as we have the ORM Query here + # however this is the more general path. + compile_state_cls = CompileState._get_plugin_class_for_plugin( + stmt, "orm" + ) - # true in all cases except for two tests in test/orm/test_events.py - # assert stmt.compile_options._orm_query is query return compile_state_cls._create_for_statement_or_query( - stmt, for_statement=for_statement + stmt, toplevel, for_statement=for_statement ) @classmethod @@ -199,6 +193,10 @@ class ORMCompileState(CompileState): raise NotImplementedError() @classmethod + def get_column_descriptions(self, statement): + return _column_descriptions(statement) + + @classmethod def orm_pre_session_exec( cls, session, statement, execution_options, bind_arguments ): @@ -219,10 +217,16 @@ class ORMCompileState(CompileState): # as the statement is built. "subject" mapper is the generally # standard object used as an identifier for multi-database schemes. - if "plugin_subject" in statement._propagate_attrs: - bind_arguments["mapper"] = statement._propagate_attrs[ - "plugin_subject" - ].mapper + # we are here based on the fact that _propagate_attrs contains + # "compile_state_plugin": "orm". The "plugin_subject" + # needs to be present as well. + + try: + plugin_subject = statement._propagate_attrs["plugin_subject"] + except KeyError: + assert False, "statement had 'orm' plugin but no plugin_subject" + else: + bind_arguments["mapper"] = plugin_subject.mapper if load_options._autoflush: session._autoflush() @@ -296,11 +300,14 @@ class ORMFromStatementCompileState(ORMCompileState): @classmethod def create_for_statement(cls, statement_container, compiler, **kw): compiler._rewrites_selected_columns = True - return cls._create_for_statement_or_query(statement_container) + toplevel = not compiler.stack + return cls._create_for_statement_or_query( + statement_container, toplevel + ) @classmethod def _create_for_statement_or_query( - cls, statement_container, for_statement=False, + cls, statement_container, toplevel, for_statement=False, ): # from .query import FromStatement @@ -309,8 +316,9 @@ class ORMFromStatementCompileState(ORMCompileState): self = cls.__new__(cls) self._primary_entity = None - self.orm_query = statement_container.compile_options._orm_query - + self.use_orm_style = ( + statement_container.compile_options._use_legacy_query_style + ) self.statement_container = self.query = statement_container self.requested_statement = statement_container.element @@ -325,12 +333,13 @@ class ORMFromStatementCompileState(ORMCompileState): self.current_path = statement_container.compile_options._current_path - if statement_container._with_options: + if toplevel and statement_container._with_options: self.attributes = {"_unbound_load_dedupes": set()} for opt in statement_container._with_options: if opt._is_compile_state: opt.process_compile_state(self) + else: self.attributes = {} @@ -411,24 +420,24 @@ class ORMSelectCompileState(ORMCompileState, SelectState): _where_criteria = () _having_criteria = () - orm_query = None - @classmethod def create_for_statement(cls, statement, compiler, **kw): if not statement._is_future: return SelectState(statement, compiler, **kw) + toplevel = not compiler.stack + compiler._rewrites_selected_columns = True orm_state = cls._create_for_statement_or_query( - statement, for_statement=True + statement, for_statement=True, toplevel=toplevel ) SelectState.__init__(orm_state, orm_state.statement, compiler, **kw) return orm_state @classmethod def _create_for_statement_or_query( - cls, query, for_statement=False, _entities_only=False, + cls, query, toplevel, for_statement=False, _entities_only=False ): assert isinstance(query, future.Select) @@ -440,9 +449,8 @@ class ORMSelectCompileState(ORMCompileState, SelectState): self._primary_entity = None - self.orm_query = query.compile_options._orm_query - self.query = query + self.use_orm_style = query.compile_options._use_legacy_query_style self.select_statement = select_statement = query @@ -484,7 +492,7 @@ class ORMSelectCompileState(ORMCompileState, SelectState): # rather than LABEL_STYLE_NONE, and if we can use disambiguate style # for new style ORM selects too. if self.select_statement._label_style is LABEL_STYLE_NONE: - if self.orm_query and not for_statement: + if self.use_orm_style and not for_statement: self.label_style = LABEL_STYLE_TABLENAME_PLUS_COL else: self.label_style = LABEL_STYLE_DISAMBIGUATE_ONLY @@ -495,7 +503,7 @@ class ORMSelectCompileState(ORMCompileState, SelectState): self.eager_order_by = () - if select_statement._with_options: + if toplevel and select_statement._with_options: self.attributes = {"_unbound_load_dedupes": set()} for opt in self.select_statement._with_options: |