lock_rows¶
You can use the lock_rows
clause with the following queries:
It returns a query that locks rows until the end of the transaction, generating a SELECT ... FOR UPDATE
SQL statement or similar with other lock strengths.
Note
Postgres and CockroachDB only.
Basic Usage¶
Basic usage without parameters:
await Band.select(Band.name == 'Pythonistas').lock_rows()
Equivalent to:
SELECT ... FOR UPDATE
lock_strength¶
The parameter lock_strength
controls the strength of the row lock when performing an operation in PostgreSQL.
The value can be a predefined constant from the LockStrength
enum or one of the following strings (case-insensitive):
UPDATE
(default): Acquires an exclusive lock on the selected rows, preventing other transactions from modifying or locking them until the current transaction is complete.NO KEY UPDATE
(Postgres only): Similar toUPDATE
, but allows other transactions to insert or delete rows that do not affect the primary key or unique constraints.KEY SHARE
(Postgres only): Permits other transactions to acquire key-share or share locks, allowing non-key modifications while preventing updates or deletes.SHARE
: Acquires a shared lock, allowing other transactions to read the rows but not modify or lock them.
You can specify a different lock strength:
await Band.select(Band.name == 'Pythonistas').lock_rows('SHARE')
Which is equivalent to:
SELECT ... FOR SHARE
nowait¶
If another transaction has already acquired a lock on one or more selected rows, an exception will be raised instead of waiting for the other transaction to release the lock.
await Band.select(Band.name == 'Pythonistas').lock_rows('UPDATE', nowait=True)
skip_locked¶
Ignore locked rows.
await Band.select(Band.name == 'Pythonistas').lock_rows('UPDATE', skip_locked=True)
of¶
By default, if there are many tables in a query (e.g. when joining), all tables will be locked.
Using of
, you can specify which tables should be locked.
await Band.select().where(Band.manager.name == 'Guido').lock_rows('UPDATE', of=(Band, ))
Full example¶
If we have this table:
class Concert(Table):
name = Varchar()
tickets_available = Integer()
And we want to make sure that tickets_available
never goes below 0, we can
do the following:
async def book_tickets(ticket_count: int):
async with Concert._meta.db.transaction():
concert = await Concert.objects().where(
Concert.name == "Awesome Concert"
).first().lock_rows()
if concert.tickets_available >= ticket_count:
await concert.update_self({
Concert.tickets_available: Concert.tickets_available - ticket_count
})
else:
raise ValueError("Not enough tickets are available!")
This means that when multiple transactions are running at the same time, it isn’t possible to book more tickets than are available.
Note
There is a video tutorial on YouTube.