Skip to content

Commit d7aa34e

Browse files
committed
Document Lock unique names
1 parent 4dba3a1 commit d7aa34e

File tree

1 file changed

+27
-1
lines changed

1 file changed

+27
-1
lines changed

contributing/LOCKING.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ await session.commit()
5050
select ...
5151
```
5252

53-
> SQLite exhibits "snapshot isolation". When a read transaction starts, that reader continues to see an unchanging "snapshot" of the database file as it existed at the moment in time when the read transaction started. Any write transactions that commit while the read transaction is active are still invisible to the read transaction, because the reader is seeing a snapshot of database file from a prior moment in time. Source: https://www.sqlite.org/isolation.html
53+
> SQLite exhibits Snapshot Isolation. When a read transaction starts, that reader continues to see an unchanging "snapshot" of the database file as it existed at the moment in time when the read transaction started. Any write transactions that commit while the read transaction is active are still invisible to the read transaction, because the reader is seeing a snapshot of database file from a prior moment in time. Source: https://www.sqlite.org/isolation.html
5454
5555
Thus, if a new transaction is not started, you won't see changes that concurrent transactions made before you acquired the lock.
5656

@@ -82,3 +82,29 @@ In fact, using `joinedload` and `.with_for_update()` will trigger an error becau
8282
**Always use `.with_for_update(key_share=True)` unless you plan to delete rows or update a primary key column**
8383

8484
If you `SELECT FOR UPDATE` from a table that is referenced in a child table via a foreign key, it can lead to deadlocks if the child table is updated because Postgres will issue a `FOR KEY SHARE` lock on the parent table rows to ensure valid foreign keys. For this reason, you should always do `SELECT FOR NO KEY UPDATE` (.`with_for_update(key_share=True)`) if primary key columns are not modified. `SELECT FOR NO KEY UPDATE` is not blocked by a `FOR KEY SHARE` lock, so no deadlock.
85+
86+
87+
**Lock unique names**
88+
89+
The following pattern can be used to lock a unique name of some resource type:
90+
91+
```python
92+
lock_namespace = f"fleet_names_{project.name}"
93+
if get_db().dialect_name == "sqlite":
94+
# Start new transaction to see committed changes after lock
95+
await session.commit()
96+
elif get_db().dialect_name == "postgresql":
97+
await session.execute(
98+
select(func.pg_advisory_xact_lock(string_to_lock_id(lock_namespace)))
99+
)
100+
101+
lock, _ = get_locker(get_db().dialect_name).get_lockset(lock_namespace)
102+
async with lock:
103+
# ... select taken names, use a unique name
104+
await session.commit()
105+
```
106+
107+
Note that:
108+
109+
* This pattern works assuming that Postgres is using default isolation level Read Committed. By the time a transaction acquires the advisory lock, all other transactions that can take the name have committed, so their changes can be seen and a unique name is taken.
110+
* SQLite needs a commit before selecting taken names due to Snapshot Isolation as noted above.

0 commit comments

Comments
 (0)