Transactions¶
Transactions allow multiple queries to be committed only once successful.
This is useful for things like migrations, where you can’t have it fail in an inbetween state.
Accessing the Engine
¶
In the examples below we need to access the database Engine
.
Each Table
contains a reference to its Engine
, which is the easiest
way to access it. For example, with our Band
table:
DB = Band._meta.db
Atomic¶
This is useful when you want to programmatically add some queries to the transaction before running it.
transaction = DB.atomic()
transaction.add(Manager.create_table())
transaction.add(Concert.create_table())
await transaction.run()
# You're also able to run this synchronously:
transaction.run_sync()
Transaction¶
This is the preferred way to run transactions - it currently only works with async.
async with DB.transaction():
await Manager.create_table()
await Concert.create_table()
Commit¶
The transaction is automatically committed when you exit the context manager.
async with DB.transaction():
await query_1
await query_2
# Automatically committed if the code reaches here.
You can manually commit it if you prefer:
async with DB.transaction() as transaction:
await query_1
await query_2
await transaction.commit()
print('transaction committed!')
Rollback¶
If an exception is raised within the body of the context manager, then the transaction is automatically rolled back. The exception is still propagated though.
Rather than raising an exception, if you want to rollback a transaction manually you can do so as follows:
async with DB.transaction() as transaction:
await Manager.create_table()
await Band.create_table()
await transaction.rollback()
Nested transactions¶
Nested transactions aren’t supported in Postgres, but we can achieve something similar using savepoints.
Nested context managers¶
If you have nested context managers, for example:
async with DB.transaction():
async with DB.transaction():
...
By default, the inner context manager does nothing, as we’re already inside a transaction.
You can change this behaviour using allow_nested=False
, in which case a
TransactionError
is raised if you try creating a transaction when one
already exists.
async with DB.transaction():
async with DB.transaction(allow_nested=False):
# TransactionError('A transaction is already active.')
transaction_exists
¶
You can check whether your code is currently inside a transaction using the following:
>>> DB.transaction_exists()
True
Savepoints¶
Postgres supports savepoints, which is a way of partially rolling back a transaction.
async with DB.transaction() as transaction:
await Band.insert(Band(name='Pythonistas'))
savepoint_1 = await transaction.savepoint()
await Band.insert(Band(name='Terrible band'))
# Oops, I made a mistake!
await savepoint_1.rollback_to()
In the above example, the first query will be committed, but not the second.
Named savepoints¶
By default, we assign a name to the savepoint for you. But you can explicitly give it a name:
await transaction.savepoint('my_savepoint')
This means you can rollback to this savepoint at any point just using the name:
await transaction.rollback_to('my_savepoint')
Transaction types¶
SQLite¶
For SQLite you may want to specify the transaction type, as it can have an effect on how well the database handles concurrent requests.