Transactions¶
conn.transaction() starts a transaction.
It commits when the block ends, and rolls back if the block raises.
await conn.execute_script("CREATE TEMP TABLE users (email text PRIMARY KEY)")
async with conn.transaction() as tx:
await tx.execute("INSERT INTO users VALUES ($1)", "a@example.com")
await tx.execute("INSERT INTO users VALUES ($1)", "b@example.com")
print(await conn.select_many("SELECT email FROM users ORDER BY email"))
# [('a@example.com',), ('b@example.com',)]
tx has all the query methods of a connection, bulk writes included.
With a pool, use pool.transaction(): it takes the same options.
Options¶
isolation=:"read_uncommitted","read_committed","repeatable_read"or"serializable". The default is the server's.readonly=Truefor a read-only transaction.deferrable=Truefor a deferrable one.
async with conn.transaction(isolation="serializable", readonly=True) as tx:
print(await tx.select_one("SELECT count(*) FROM users")) # (2,)
Savepoints¶
When a statement fails, PostgreSQL aborts the whole transaction.
To catch an error and carry on, wrap the statement in tx.savepoint():
async with conn.transaction() as tx:
try:
async with tx.savepoint():
await tx.execute("INSERT INTO users VALUES ($1)", "a@example.com")
except fastpg.UniqueViolationError:
print("already registered")
await tx.execute("INSERT INTO users VALUES ($1)", "c@example.com")
The savepoint is rolled back and the error is raised again; the transaction carries on. Savepoints can be nested.
Good to know¶
- Retry the whole transaction on
SerializationErrororDeadlockDetectedError. - A connection runs one transaction at a time.
Don't share a connection that has an open transaction with other tasks: their queries
would run inside it.
pool.transaction()gives each transaction its own connection. - If a table changes while a transaction uses it, a query can fail with
NotSupportedError. Retry the transaction.