On May 13, 2008, at 5:15 PM, Randall Nortman wrote:
> handling. I'm curious how SA handles this, especially transient > errors where a retry is the right thing to do. For example, in > several of my current sqlite apps (using bare DB-API), I get > occasional locking errors ("Database is locked"), which happens > sometimes with sqlite when there are concurrent processes accessing > the DB. The right thing to do is retry, but it's a real pain to write > *every* database operation with retry logic. Does SA handle retries > in cases like this automatically? no, if a DBAPI error occurs, the error is thrown. We wrap the error in a DBAPI-neutral wrapper which has the same type as the underlying error (i.e. OperationalError, IntegrityError). Retries are definitely something a user-application needs to work out on its own; SQLAlchemy is not an application framework, just a library. > (Admittedly, I think that more > recent versions of pysqlite handle locked DBs better than the somewhat > stale version I'm currently using, but I'm after the general principle > rather than trying to solve that particular problem.) I dont think "database is locked" errors are all that common in practice. SQLite is supposed to block until the file is available. If you have a highly concurrent transactional application, SQLite is the wrong choice anyway. > Aside from that, how in general does SA handle errors returned by the > underlying DB? For most situations, you write your application such that they don't occur within normal use cases (i.e. dont use IntegrityError as a way to check if a row is present, select from the table explicitly instead; dont overuse locking and/or use a unit-of-work so that deadlocks don't occur). An error thrown by the DB should be considered an application failure. > Does it automatically do a rollback and When using the ORM, if a flush() fails, a ROLLBACK is raised unconditionally, and the error is thrown. At that point, the end user must rollback the Session under normal usage (what Session calls "transactional=True", or in 0.5 is called "autocommit=False"). This is actually not the most ideal behavior as it would be nice someday for a flush() to be able to make partial progress, raise the error without rolling back, and then be able to complete after the state of the Session has been corrected, its something we might do someday. However, some databases themselves prevent this from being doable; current versions of Postgres, for example, require that you ROLLBACK the transaction if an IntegrityError (the most common error) occurs - further statements will raise errors unconditionally until ROLLBACK. > /or reconnect > if necessary? Here's the one place we do something with a raised error. We do detect those errors which indicate a dropped connection, and in response to that we recycle the entire connection pool. The assumption here is that if a disconnect occurs, it is probably due to the database being restarted and it is harmless in any case for the connection pool being recycled. However, we still raise the error, albeit exactly once as opposed to once for each connection in the pool (this is a distinct advantage to the usage of a pool). In this case its virtually impossible for us to "retry" as when a conneciton is lost, an entire transaction is gone. SQLA is not capable of replaying the full script of SQL statements which occured within the transaction since it does not take on the complexity and overhead intrinsic in such an operation. But if you have a multithreaded web app, and you bounce the DB, you would get a minimum of failures. There is also a way to have connetions "pre-ping" before each usage (and recycle if connection lost), which is another way to prevent most "disconnect" errors. We have a "connection pool events" API which I believe some people have used to achieve this result. There's another option on the pool called "pool_recycle=<num of seconds>" which is used to deal with database client libraries (like MySQL's) that time out after a certain number of hours. So again, the emphasis here with reconnecting is to minimize the amount of errors thrown in the first place. > If you're using the ORM layer is it able to still > figure out what objects/fields are dirty if an error was encountered > while trying to flush changes to disk? the unit of work executes its SQL within a transaction, but does not change the bookkeeping on any affected objects until the transaction is committed. In the 0.4 series, there is one exception to this, which is primary key and foreign key identifiers which are set during the transaction currently do not get rolled back on instances, which can create issues. In 0.4 we've generally recommended closing out the session when a flush error occurs (as a side note, this is the long-standing behavior of Hibernate as well which also cant rollback the state of in-memory instances). In version 0.5 which is available now via the trunk, the rollback procedure fully expires all objects present in the session, expunges new objects, and promotes deleted objects back to the persistent status - so while a flush() error still implies a rollback() must occur, the state of the session is fully rolled back in tandem with the transaction and may be reused (this is described here: http://www.sqlalchemy.org/docs/05/session.html#unitofwork_using_rolling ). We also have a branch where we worked with an in-memory rollback of attributes but I think this is overly complex; SQLA's Session, in 0.5 more than ever, uses the underlying transaction as a guide to the current database conversation and the Python side is held closely to that. > Is the programmer responsible > for differentiating between errors that indicate that a reconnect is > necessary and errors where the connection can continue to be used? SQLA handles reconnect for you automatically, but still propagates the error for those connections which receive it. If you are working with the SQL level, and you're trying to be able to "retry" things (which you might be noticing is a practice we dont encourage), then yes you'd have to figure out that a dropped connection has also lost your transaction, whereas an IntegrityError has not (unless youre on postgres, in which case you'd get more errors if you try to continue). > including not losing track of what changes still need to be committed > at the ORM layer. if youre using the session in autocommit mode and disable autoexpire, whatever changes are pending on objects still remain after an error is raised, and the transaction rollback is limited to just the flush() itself since theres otherwise no transaction going on. But as i said if you dont expire instances, new PK identifiers will remain assigned which may or may not create problems depending on DB backend. > Ideally, if the reconnect is successful, I never > want to know anything went wrong -- I just want SA to retry a > reasonable number of times and only throw an exception if it continues > to fail. cant be done, since the transaction in progress is thrown away. I think it would be inappropriate for SQLA to log the entire transaction in its own buffers (enormous overhead and complexity) and then run the whole thing a second time without being told to (debugging failures here would be extremely complicated). > But if the DB just threw a primary key constraint violation, > the connection is still healthy, and I want my DB layer to just let me > know so I can handle the situation appropriately (such as switching > the insert to an update, or deleting the existing record, or passing > the error on up to the user). The patterns encouraged by the ORM remove the need to guess if a row needs an INSERT or UPDATE, and do not use database exceptions to achieve use cases. If you are writing your application based on heavy emphasis of "retrying things that failed within the transaction", the SQLA ORM is not designed around that paradigm. - mike --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "sqlalchemy" group. To post to this group, send email to sqlalchemy@googlegroups.com To unsubscribe from this group, send email to [EMAIL PROTECTED] For more options, visit this group at http://groups.google.com/group/sqlalchemy?hl=en -~----------~----~----~----~------~----~------~--~---