I've got several questions about dealing with concurrency issues in SQLAlchemy. To try and help with the asking, I'll use a small example similar to a situation I've got in an SQLAlchemy application. In this application, there is a table that tracks objects and their order/ position, something like below...
Name Pos ---- --- ObjA 1 ObjB 2 ObjC 3 And this can change to the following with a new object inserted "before" (position-wise) ObjB... Name Pos ---- --- ObjA 1 ObjB 3 ObjC 4 NewObj 2 Some sample code is whipped up to do this is at the bottom of this post. Worth noting is that my application is currently database-agnostic (SQLAlchemy enabling this is a great feature), and I'd like to keep it that way if at all possible. My questions revolve around concurrency issues because the insert operation can potentially update all rows in the table. It seems that this is very vulnerable to concurrency problems since each transaction will only update its own rows that existed at the start of the transaction. If a parallel transaction is started doing a similar insertion, a record can be potentially get a duplicate position number, which would destroy the application logic. I'm much more of an application person than a DB person, so to solve this my immediate thinking is to simply do it all at the application layer and use a thread lock on the table updating function. All table access is routed through the application (which uses scoped sessions) so this would certainly "work", but I'm certain that those more familiar with sqla and the DB layer would likely cringe at that approach. I really want to learn more about dealing with concurrency at the database level through sqlalchemy. What is a good way to deal with this in sqla? One possibility seems to be to get it all done in one atomic commit. But how can I use the ORM (together with the expression language?) to increment by 1 all positions where the position value is > than the position for a given object name? For a single value, the docs show me that can get the proper SQL emitted for a record with something like: obj.position = obj.__table__.c.position + 1 where obj is one of my mapped objects. But how do I do it for multiple rows and not just the one obj? Also - is going through __table__ the most direct way for achieving this? After the application layer blocking, the next most brutal thing that comes to mind is locking the entire table during the operation to avoid problems. I'm not clear this will work since it seems a read lock is needed, and I don't yet know if you can actually do that. It may be irrelevant, since I certainly do not see any way of doing table locks through sqla at the moment. If locking were an option, it seems it would require direct (potentialy DB specifc) SQL statement execution through session.execute() or conn.execute(). Is that correct? Something that I've seen referenced a few times for dealing with concurrency in sqlalchemy is to use "with_lockmode()" on a query to lock the relevant rows. However - I'm wary of this for a few reasons: First is that I don't think it would work in my example case because read blocking is needed (I think). Second is that I can't actually figure out what with_lockmode actually does. Digging in the code I see the lock options are "read", "update", "update_nowait". Short of finding this in query.py, where are the docs for these options?? The third bit of wariness is uncertain support for different dialects. Digging through my 'SQL in a Nutshell' I see varying DB support for "SELECT FOR UPDATE" syntax (which is what with_lockmode seems to employ?) across various RDMS so I'd rather stay away from it if possible. What is the best way to figure out the database dialect support for each of these options, short of trying with all potential database types? Although I'm certainly interested in specific ideas on dealing with something similar to the table update described, any links or tips anyone has for dealing with concurrency in SQLAlchemy would be appreciated. This includes any expansions anyone has on how the UOW pattern and ORM transactions alone help with some forms of concurrency, as I know they do to some extent. Thanks, Russ ## sample code with concurrency issue... from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Table, Column, Integer, String, MetaData from sqlalchemy import create_engine from pprint import pprint engine = create_engine('sqlite:///:memory:', echo = False) metadata = MetaData() from sqlalchemy.orm import sessionmaker Session = sessionmaker(bind = engine) Base = declarative_base() class OrderedObject(Base): __tablename__ = 'objects' id = Column(Integer, primary_key = True) name = Column(String) position = Column(Integer) def __init__(self, name, name_to_bump = ""): self._ormSess = Session() #does embedding this scare anyone? self.name = name if name_to_bump: self._InsertBefore(name_to_bump) else: #Count below may be inaccurate with concurrent users! newPos = self._ormSess.query(OrderedObject).count() + 1 self.position = newPos self._ormSess.add(self) self._ormSess.commit() def _InsertBefore(self, name_to_bump): #This (potentially) modifies all rows in the table. Need to watch out #for concurrency issues... self._ormSess = Session() allObjects = self._ormSess.query(OrderedObject).all() pos = 0 for obj in allObjects: pos += 1 if obj.name == name_to_bump: self.position = pos pos += 1 obj.position = pos def __repr__(self): return "<Object('%s' at %d)>" % (self.name, self.position) Base.metadata.create_all(engine) sess = Session() initialObjects = [OrderedObject("Object_%s" % chr(65 + i)) for i in range(5)] print "Initial Objects..." print "------------------" pprint(sess.query(OrderedObject).order_by(OrderedObject.position).all()) OrderedObject("BEFORE_B", "Object_B") print "\nBumped Objects..." print "------------------" pprint(sess.query(OrderedObject).order_by(OrderedObject.position).all()) -- You received this message because you are subscribed to the Google Groups "sqlalchemy" group. To post to this group, send email to sqlalch...@googlegroups.com. To unsubscribe from this group, send email to sqlalchemy+unsubscr...@googlegroups.com. For more options, visit this group at http://groups.google.com/group/sqlalchemy?hl=en.