|
2 | 2 |
|
3 | 3 | ## Connections |
4 | 4 |
|
5 | | -Connections are the heart-piece of databasez. They are enabling rollback behavior, via transactions |
6 | | -and provide all manipulation functions. |
7 | | -Despite there are shortcut methods on the [Database object](./database.md), they are all using |
8 | | -connections internally. |
9 | | -When wanting for performance reasons one connection doing multiple jobs, retrieving a connection via |
10 | | -`connection()` and perform all tasks within the connection context (connections are async contextmanagers) are the way to go. |
| 5 | +Connections are the core execution unit in Databasez. Even when you call high-level methods on `Database`, those calls are routed through a `Connection`. |
11 | 6 |
|
12 | | -### Multithreading |
| 7 | +If you want multiple operations in one scoped connection, use: |
13 | 8 |
|
14 | | -`sqlalchemy`'s async addon is also multi-threading capable but we cannot use this for two reasons: |
| 9 | +```python |
| 10 | +async with database.connection() as connection: |
| 11 | + await connection.execute( |
| 12 | + "INSERT INTO notes(text) VALUES (:text)", |
| 13 | + {"text": "example"}, |
| 14 | + ) |
| 15 | + rows = await connection.fetch_all("SELECT * FROM notes") |
| 16 | +``` |
| 17 | + |
| 18 | +This is the recommended style for explicit control. |
15 | 19 |
|
16 | | -- Only the `NullPool` is supported which is afaik just no pooling. |
17 | | -- No global connection which is reliable resetted would be possible. |
| 20 | +## Task-local behavior |
18 | 21 |
|
19 | | -Therefor `databasez` uses an other approach: `isolation`: |
| 22 | +Connections are task-local. Inside one task, repeated `database.connection()` calls reuse the same object. Different tasks receive different connections. |
20 | 23 |
|
21 | | -Event-loops are per thread. So in this approach for every new event-loop detected the database object is copied and assigned to it. |
22 | | -This way all connections are running in a per-thread pool. The global connection is a bit different from the rest. |
23 | | -Whenever it is accessed by an other thread, asyncio.run_coroutine_threadsafe is used. |
24 | | -In the full-isolation mode (default) the global connection is even moved to an own thread. |
| 24 | +## Global connection in rollback mode |
25 | 25 |
|
26 | | -### Global connection with auto-rollback |
| 26 | +When `force_rollback=True`, `database.connection()` returns a global connection that is wrapped in rollback behavior. |
27 | 27 |
|
28 | | -Sometimes, especially for tests, you want a connection in which all changes are resetted when the database is closed. |
29 | | -For having this reliable, a global connection is lazily initialized when requested on the database object via the |
30 | | -`force_rollback` contextmanager/parameter/pseudo-attribute. |
| 28 | +This is useful for tests, but remember: |
31 | 29 |
|
32 | | -Whenever `connection()` is called, the same global connection is returned. It behaves nearly normally except you have just one connection |
33 | | -available. |
34 | | -This means you have to be careful when using `iterate` to not open another connection via `connection` (except you set `force_rollback` to False before opening the connection). |
35 | | -Otherwise it will deadlock. |
| 30 | +- you are effectively sharing one connection |
| 31 | +- nested/parallel usage on the same connection must be planned carefully |
36 | 32 |
|
37 | | -Example for `force_rollback`: |
| 33 | +Runnable example: |
38 | 34 |
|
39 | 35 | ```python |
40 | 36 | {!> ../docs_src/connections/force_rollback.py !} |
41 | 37 | ``` |
42 | 38 |
|
43 | | -## Transactions |
44 | | - |
45 | | -Transactions are lazily initialized. You dont't need a connected database to create them via `database.transaction()`. |
| 39 | +## Multiloop and multithreading |
46 | 40 |
|
47 | | -When created, there are three ways to activate the Transaction |
| 41 | +Databasez supports loop-aware routing: |
48 | 42 |
|
49 | | -1. The async context manager way via `async with transaction: ...` |
50 | | -2. Entering a method decorated with a transaction. |
51 | | -3. The manual way via `await transaction` |
| 43 | +- same loop: direct call |
| 44 | +- different loop: operation is proxied using cross-loop helpers |
| 45 | +- optional full isolation: global rollback connection in a dedicated thread |
52 | 46 |
|
53 | | -Whenever the transaction is activated, a connected database is required. |
54 | | - |
55 | | -A second way to get a transaction is via the `Connection`. It has also a `transaction()` method. |
| 47 | +```mermaid |
| 48 | +flowchart TD |
| 49 | + A["Call from current loop"] --> B{"Loop matches database loop?"} |
| 50 | + B -->|Yes| C["Execute directly"] |
| 51 | + B -->|No| D{"Known sub-database for loop?"} |
| 52 | + D -->|Yes| E["Forward to sub-database"] |
| 53 | + D -->|No| F["Proxy call with async helper"] |
| 54 | +``` |
56 | 55 |
|
| 56 | +## Transactions |
57 | 57 |
|
58 | | -### The three ways to use a transaction |
| 58 | +Transactions are lazily initialized and can be used in three ways. |
59 | 59 |
|
60 | | -#### Via the async context manager protocol |
| 60 | +### 1. Async context manager |
61 | 61 |
|
62 | 62 | ```python |
63 | 63 | {!> ../docs_src/transactions/async_context_database.py !} |
64 | 64 | ``` |
65 | | -It can also be acquired from a specific database connection: |
| 65 | + |
| 66 | +Or from a specific connection: |
66 | 67 |
|
67 | 68 | ```python |
68 | 69 | {!> ../docs_src/transactions/async_context_connection.py !} |
69 | 70 | ``` |
70 | 71 |
|
71 | | -#### Via decorating |
72 | | - |
73 | | -You can also use `.transaction()` as a function decorator on any async function: |
| 72 | +### 2. Decorator |
74 | 73 |
|
75 | 74 | ```python |
76 | 75 | {!> ../docs_src/transactions/decorating.py !} |
77 | 76 | ``` |
78 | 77 |
|
79 | | -#### Manually |
80 | | - |
81 | | -For a lower-level transaction API: |
| 78 | +### 3. Manual control |
82 | 79 |
|
83 | 80 | ```python |
84 | 81 | {!> ../docs_src/transactions/manually.py !} |
85 | 82 | ``` |
86 | 83 |
|
87 | | -### Auto-rollback (`force_rollback`) |
| 84 | +## Transaction options |
| 85 | + |
| 86 | +### `force_rollback` |
88 | 87 |
|
89 | | -Transactions support an keyword parameter named `force_rollback` which default to `False`. |
90 | | -When set to `True` at the end of the transaction a rollback is tried. |
91 | | -This means all changes are undone. |
| 88 | +`transaction(force_rollback=True)` always rolls back on exit. |
92 | 89 |
|
93 | | -This is a simpler variant of `force_rollback` of the database object. |
94 | 90 | ```python |
95 | 91 | {!> ../docs_src/transactions/force_rollback_transaction.py !} |
96 | 92 | ``` |
97 | 93 |
|
98 | | - |
99 | 94 | ### Isolation level |
100 | 95 |
|
101 | | -The state of a transaction is liked to the connection used in the currently executing async task. |
102 | | -If you would like to influence an active transaction from another task, the connection must be |
103 | | -shared: |
104 | | - |
105 | | -Transaction isolation-level can be specified if the driver backend supports that: |
| 96 | +You can pass backend-supported isolation levels: |
106 | 97 |
|
107 | 98 | ```python |
108 | 99 | {!> ../docs_src/transactions/isolation_level.py !} |
109 | 100 | ``` |
110 | 101 |
|
111 | 102 | ### Nested transactions |
112 | 103 |
|
113 | | -Nested transactions are fully supported, and are implemented using database savepoints: |
| 104 | +Nested transactions are supported through savepoints: |
114 | 105 |
|
115 | 106 | ```python |
116 | 107 | {!> ../docs_src/transactions/nested_transactions.py !} |
117 | 108 | ``` |
| 109 | + |
| 110 | +## Transaction stack model |
| 111 | + |
| 112 | +```mermaid |
| 113 | +sequenceDiagram |
| 114 | + participant App |
| 115 | + participant Conn as Connection |
| 116 | + participant Stack as Transaction Stack |
| 117 | +
|
| 118 | + App->>Conn: begin outer transaction |
| 119 | + Conn->>Stack: push outer |
| 120 | + App->>Conn: begin nested transaction |
| 121 | + Conn->>Stack: push savepoint |
| 122 | + App->>Conn: rollback nested |
| 123 | + Conn->>Stack: pop savepoint |
| 124 | + App->>Conn: commit outer |
| 125 | + Conn->>Stack: pop outer |
| 126 | +``` |
| 127 | + |
| 128 | +## See also |
| 129 | + |
| 130 | +- [Database](./database.md) |
| 131 | +- [Queries](./queries.md) |
| 132 | +- [Troubleshooting](./troubleshooting.md) |
0 commit comments