-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Should savepoints be released after rolling back? #3634
Comments
see rusqlite for how to handle it properly |
I started writing a reply refuting this report when I realized there was an important distinction I was missing. On the surface, it doesn't appear necessary to
If we look at the SQLite code that interprets transaction bytecode instructions, we find this block that destroys savepoints in both the It is clear here that savepoints are stored in a linked list, and this routine is pruning the list from the front (the most recent savepoint) backwards toward the designated savepoint. However, what it doesn't do is release the actual savepoint that was rolled back to. Because of SQLx's API design, the savepoint cannot be accessed again and beginning another nested transaction will create a new, redundant savepoint. Even though we reuse savepoint names, this merely shadows the old savepoint. This effectively leaks the savepoint. If this is the point you were trying to make, you could have gone into a little more detail here and saved me a bunch of digging, although I might have done it anyway as you'll see shortly. Savepoints cannot be permanently leaked though, because they're destroyed anyway when the parent transaction/savepoint is committed or rolled back, so while this is suboptimal, it's unlikely to cause any real issues unless many, many savepoints are created and then rolled back to in the same transaction. While looking at Postgres to compare what it does, I realized that we likely have the same bug in all the database drivers, because both MySQL and Postgres have the same semantics as SQLite in this case: In MySQL, this doesn't strictly result in redundant savepoints because creating a savepoint with the same name as an existing one overwrites it: https://dev.mysql.com/doc/refman/8.4/en/savepoint.html
So the savepoint is only leaked until you call Postgres documents its behavior as an explicit deviation from the standard (see "Compatibility"): https://www.postgresql.org/docs/current/sql-savepoint.html
This sounds identical to what SQLite does. Digging through the Postgres source code, there's an argument to be made for not releasing savepoints unless necessary, because it requires non-trivial work: https://github.com/postgres/postgres/blob/a43567483c617fb046c805b61964d5168c9a0553/src/backend/access/transam/xact.c#L5096-L5205 This is on top of the work that was already done to roll back to the savepoint: https://github.com/postgres/postgres/blob/a43567483c617fb046c805b61964d5168c9a0553/src/backend/access/transam/xact.c#L5211-L5362 Same as in SQLite, releasing a savepoint is non-trivial, though most of the work is also done by At the end of the day, I believe most of the use-cases for savepoints involve rolling back to them and then trying identical changes again from that same point. Thus, it doesn't make a whole lot of sense to release a savepoint just to create another. Instead, I think we should treat an immediate The question becomes, what do we do if the user doesn't call I think in this case it's fine to release the savepoint at the beginning of the next command, because to the user, the side-effects are almost the same. It may just retain resources for a little longer. |
SQLite doesn't look that problematic tbh (other than the hypothetical situation that we do a ton of savepoints, but that is fine per se IMO) but if PostgreSQL is doing non-trivial stuff when releasing the savepoint, I would interpret that as an indication that not releasing the savepoint might cause weird things beyond our naive awareness to happen (e.g. |
Bug Description
SQLx SQLite driver uses
ROLLBACK TO
for rolling back savepoint here. Per SQLx semantics it should also immediatelyRELEASE
the savepoint to remove it from the transaction stack; otherwise savepoint leak would occur. See SQLite docs.The text was updated successfully, but these errors were encountered: