-
Notifications
You must be signed in to change notification settings - Fork 14.1k
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
[SIP-99A] Primer on managing SQLAlchemy sessions #25107
Labels
sip
Superset Improvement Proposal
Comments
This was referenced Aug 28, 2023
john-bodley
changed the title
[SIP-98A] Primer on managing SQLAlchemy sessions
[SIP-99A] Primer on managing SQLAlchemy sessions
Aug 29, 2023
9 tasks
@john-bodley this is a really great summary. Thank you for writing it up, and I'm sure it will help a lot of people to better understand best practices in this space that is often quite murky. |
9 tasks
rusackas
moved this from Pre-discussion
to [RESULT][VOTE] Approved
in SIPs (Superset Improvement Proposals)
Dec 6, 2023
Approved! |
rusackas
moved this from [RESULT][VOTE] Approved
to In Development
in SIPs (Superset Improvement Proposals)
Dec 6, 2023
This was referenced Dec 7, 2023
Anyone know what's left to do here? |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
[SIP-99A] Primer on managing SQLAlchemy sessions
This SIP is part of the [SIP-99] Proposal for correctly handling business logic series. Specifically, it serves as a primer—reiterating best practices—for working with SQLAlchemy sessions as these were typically mismanaged. The topics outlined here are highly coupled with [SIP-99B] Proposal for (re)defining a "unit of work" as they both relate to managing database transactions.
Session Management
The SQLAlchemy session—which establishes all conversations with the database—is managed by Flask-SQLAlchemy which also handles cleaning up connections and sessions after each request.
The preconfigured scoped session (called
db.session
) should be used for the lifetime of a request to ensure there is only one “virtual” transaction—guaranteeing that the “unit of work” is indeed atomic (SIP-99B).The SQLAlchemy Managing Transactions documentation states:
The transaction is typically framed as follows,
though, via leveraging a context manager, the
Session.begin()
method serves as a demarcated transaction and provides the same sequence of operations:Session States
There are five states which an object can have within a session:
The session tracks objects that are either new, dirty, or deleted.
The
Session.flush()
method communicates a series of operations to the database (INSERT
,UPDATE
,DELETE
). The database maintains them as pending operations in a transaction. The changes are not persisted permanently to disk, or visible to other transactions until the database receives aCOMMIT
statement via theSession.commit()
method—thus ending the transaction. IfSession.commit()
is called, thenSession.flush()
is called automatically.The
Session.add()
method adds a object into the session whereas theSession.merge()
convenience method copies the state of a given instance into a corresponding instance within the session.The following examples illustrate how
Session.merge()
works:Note the use of the
Session.merge()
method is often misinterpreted, i.e., a persistent object does not need to be (re)merged. If the object is dirty the pending changes will be persisted during a subsequent flush.Transient
An object that is not in the session.
Pending
An object which has been added to the session but has not yet been flushed to the database.
Note that constraints will only be checked when the object is flushed.
Persistent
An object which is present in the session and has a corresponding record in the database. The persisted state occurs either during flushing (which is invoked by
Session.merge()
) or querying.It is worth noting that objects within the session, in either pending* or persisted state, can be queried albeit not having been committed to the database. We have a tendency to over commit (be that in the code or tests) resulting in fractured/partial atomic units. Typically working with objects in a persisted state is sufficient.
* The
Session.query()
method invoked aSession.flush()
which meant that the pending object became persisted.Deleted
An object which has been deleted within a flush, but the transaction has not yet completed.
Detached
An object that corresponds (or previously corresponded) to a record in the database, but is not currently in any session.
Examples
The following examples illustrate where the session was mismanaged:
TableSchemaView.expanded()
method unnecessarily commits even though the function only fetches, i.e., there are no new, dirty, or deleted objects in the session.UpdateKeyValueCommand.update()
method unnecessarily merges the dirtyevent
object even though it was already persisted—per theSession.query()
call—in the session.entry
object to the database given that theGetKeyValueCommand.run()
method leverages the same Flask-SQLAlchemy session. This requires additional work to then delete the object from the database to ensure that the test remains idempotent.Proposed Change
None beyond adhering to best practices when managing SQLAlchemy sessions—via the Flask-SQLAlchemy singleton scoped session.
New or Changed Public Interfaces
None.
New Dependencies
None.
Migration Plan and Compatibility
None.
Rejected Alternatives
None.
The text was updated successfully, but these errors were encountered: