On Fri, Aug 16, 2019, at 2:18 PM, Vinit Shah wrote:
> I have code that looks like the following:
> 
> games = session.query(Game)
> game = [g for g in games][0]
> court = game.court # StaleDataError
> game.
> session.add(game)
> 
> Game looks like:
> 
> class Game(Base):
>  __tablename__ = "Games"
>  id = Column(Integer, primary_key=True)
>  courtId = Column('courtId', Integer, ForeignKey('Courts.id'))
>  courtId = Column('courtId', Integer, ForeignKey('Courts.id'))
>  court = relationship(Court, foreign_keys=[courtId], backref="games")
>  revision = Column(Integer, nullable=False)
> 
>  __mapper_args__ = {
>  'version_id_col': revision
>  }
> 
> 
> I've found that this error occurs when another process has updated the same 
> row, which makes sense, since that is the purpose of the version_id_col. 
> In the contrived example above, it's been difficult to reproduce the error. 
> In production, I have found this happens consistently, so long as another 
> process has updated the same Game row. 
> 
> I can easily wrap a "try/except" around where "court" is accessed from 
> "game", but I'm curious as to why an UPDATE statement happens there in the 
> first place.
> 
> Below is the stacktrace:


the stack trace shows you what's going on. the key thing to look for is the 
word "autoflush". Any time you see an UPDATE happening when it seems like you 
are just accessing an attribute, look to autoflush for why that's happening. 
Why does autoflush occur? because the Session has pending data and it's being 
asked to run a query. Why is it running a query when I'm only accessing an 
attribute? it would be because that attrbute is unloaded. Why would an 
attribute be unloaded? Either because it was expired, or just never loaded in 
the first place. In this case I can see in your stack trace the use of 
_emit_lazyload which means that game.court is a many-to-one relationship that 
wasn't loaded, so when you access it, it loads it, does autoflush, then detects 
that the row is stale.

Mitigation is to turn off autoflush in this block, described at 
https://docs.sqlalchemy.org/en/13/orm/session_basics.html#flushing

 with session.no_autoflush:
 court = game.court




> 
>  court = game.court
>  File 
> "/home/prism/.local/lib/python2.7/site-packages/sqlalchemy/orm/attributes.py",
>  line 243, in __get__
>  return self.impl.get(instance_state(instance), dict_)
>  File 
> "/home/prism/.local/lib/python2.7/site-packages/sqlalchemy/orm/attributes.py",
>  line 602, in get
>  value = self.callable_(state, passive)
>  File 
> "/home/prism/.local/lib/python2.7/site-packages/sqlalchemy/orm/strategies.py",
>  line 634, in _load_for_state
>  session, state, primary_key_identity, passive)
>  File "<string>", line 1, in <lambda>
>  File 
> "/home/prism/.local/lib/python2.7/site-packages/sqlalchemy/orm/strategies.py",
>  line 727, in _emit_lazyload
>  primary_key_identity)
>  File 
> "/home/prism/.local/lib/python2.7/site-packages/sqlalchemy/ext/baked.py", 
> line 570, in _load_on_pk_identity
>  result = list(bq.for_session(self.session).params(**params))
>  File 
> "/home/prism/.local/lib/python2.7/site-packages/sqlalchemy/ext/baked.py", 
> line 410, in __iter__
>  self.session._autoflush()
>  File 
> "/home/prism/.local/lib/python2.7/site-packages/sqlalchemy/orm/session.py", 
> line 1493, in _autoflush
>  self.flush()
>  File 
> "/home/prism/.local/lib/python2.7/site-packages/sqlalchemy/orm/session.py", 
> line 2313, in flush
>  self._flush(objects)
>  File 
> "/home/prism/.local/lib/python2.7/site-packages/sqlalchemy/orm/session.py", 
> line 2440, in _flush
>  transaction.rollback(_capture_exception=True)
>  File 
> "/home/prism/.local/lib/python2.7/site-packages/sqlalchemy/util/langhelpers.py",
>  line 66, in __exit__
>  compat.reraise(exc_type, exc_value, exc_tb)
>  File 
> "/home/prism/.local/lib/python2.7/site-packages/sqlalchemy/orm/session.py", 
> line 2404, in _flush
>  flush_context.execute()
>  File 
> "/home/prism/.local/lib/python2.7/site-packages/sqlalchemy/orm/unitofwork.py",
>  line 398, in execute
>  rec.execute(self)
>  File 
> "/home/prism/.local/lib/python2.7/site-packages/sqlalchemy/orm/unitofwork.py",
>  line 563, in execute
>  uow
>  File 
> "/home/prism/.local/lib/python2.7/site-packages/sqlalchemy/orm/persistence.py",
>  line 177, in save_obj
>  mapper, table, update)
>  File 
> "/home/prism/.local/lib/python2.7/site-packages/sqlalchemy/orm/persistence.py",
>  line 797, in _emit_update_statements
>  (table.description, len(records), rows))
> StaleDataError: UPDATE statement on table 'Games' expected to update 1 
> row(s); 0 were matched.
> 
> Environment:
> Python 2.7
> SQLAlchemy 1.3.0b1
> Oracle
> 
> 

> --
>  SQLAlchemy - 
>  The Python SQL Toolkit and Object Relational Mapper
> 
> http://www.sqlalchemy.org/
> 
>  To post example code, please provide an MCVE: Minimal, Complete, and 
> Verifiable Example. See http://stackoverflow.com/help/mcve for a full 
> description.
>  --- 
>  You received this message because you are subscribed to the Google Groups 
> "sqlalchemy" group.
>  To unsubscribe from this group and stop receiving emails from it, send an 
> email to sqlalchemy+unsubscr...@googlegroups.com.
>  To view this discussion on the web visit 
> https://groups.google.com/d/msgid/sqlalchemy/c25108de-14e1-400b-a3d0-439fda06ed17%40googlegroups.com
>  
> <https://groups.google.com/d/msgid/sqlalchemy/c25108de-14e1-400b-a3d0-439fda06ed17%40googlegroups.com?utm_medium=email&utm_source=footer>.

-- 
SQLAlchemy - 
The Python SQL Toolkit and Object Relational Mapper

http://www.sqlalchemy.org/

To post example code, please provide an MCVE: Minimal, Complete, and Verifiable 
Example.  See  http://stackoverflow.com/help/mcve for a full description.
--- 
You received this message because you are subscribed to the Google Groups 
"sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to sqlalchemy+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/sqlalchemy/9b4d8497-a875-433f-b7f4-a5d8e0eda867%40www.fastmail.com.

Reply via email to