Unit 4.5 · about 5 h
23 / 324Transactions, isolation anomalies and ORM boundaries
Concurrent writers share your tables. Without explicit transactions and a chosen isolation level, a retry or a second writer turns an invariant into a suggestion. The database prevents what you ask it to prevent, and nothing more.
- explain
- measure
You can now
- Explain commit, rollback, savepoints and the PostgreSQL isolation anomaly table, including where PostgreSQL exceeds the standard.
- Demonstrate one isolation anomaly or prevented anomaly on the live database at a named isolation level with the interleaving written out.
- Draw the ORM transaction boundary with
Session, identity map and eager loading, and fix one \(N+1\) withselectinload.
Covers
- Transactions, isolation anomalies and concurrency-sensitive invariants
- ORM identity maps, lazy loading and transaction boundaries
Study, in this order
10 steps
- What Is a TransactionAlgoMaster SQL - selected route
BEGIN,COMMIT, andROLLBACK; Auto-Commit Mode;SAVEPOINTand Partial Rollback; Common Mistakes (Forgetting toCOMMITorROLLBACK, Long-Running Transactions) - ACID PropertiesAlgoMaster SQL - selected route
Atomicity; Consistency; Isolation; Durability with Write-Ahead Logging (WAL) and What Durability Does Not Cover
- PostgreSQL 3.4 TransactionsPostgreSQL transactions and EXPLAIN
The bank transfer wrapped in
BEGINandCOMMIT, then theSAVEPOINTandROLLBACK TOexample - Isolation LevelsAlgoMaster SQL - selected route
Read Phenomena; The Four Isolation Levels; PostgreSQL vs MySQL Defaults; Choosing the Right Level
- PostgreSQL 13.2 Transaction IsolationPostgreSQL transactions and EXPLAIN
Table 13.1 Transaction Isolation Levels (
READ UNCOMMITTEDbehaves asREAD COMMITTED, andREPEATABLE READallows no phantom reads); 13.2.2 Repeatable Read Isolation Level (the snapshot taken at the first non-transaction-control statement, andcould not serialize access due to concurrent update) - Locking MechanismsAlgoMaster SQL - selected route
Lock Granularity: Row-Level vs Table-Level; Explicit Row Locking:
SELECT FOR UPDATE;NOWAITandSKIP LOCKED; Pessimistic vs Optimistic Locking - DeadlocksAlgoMaster SQL - selected route
How Deadlocks Form; Deadlock Detection (PostgreSQL); Prevention: Consistent Lock Ordering
- MVCCAlgoMaster SQL - selected route
PostgreSQL's Approach: Heap-Based MVCC (
xminandxmax, Row Visibility Rules, Dead Tuples andVACUUM); Snapshots and Isolation Levels; Why MVCC Doesn't Prevent Lost Updates - SQLAlchemy `Session` BasicsSQLAlchemy 2.1 documentation - select, Session and eager loading
What does the
Sessiondo?; Framing out abegin/commit/rollbackblock; Flushing; Expiring / Refreshing; Is theSessiona cache? - SQLAlchemy Relationship Loading TechniquesSQLAlchemy 2.1 documentation - select, Session and eager loading
Lazy Loading and Preventing unwanted lazy loads using
raiseload; Joined Eager Loading; Select IN loading; What Kind of Loading to Use?
Focus on
- PostgreSQL has three real levels:
READ UNCOMMITTEDbehaves asREAD COMMITTED, andREPEATABLE READblocks phantom reads, which is stronger than the standard requires. REPEATABLE READtakes its snapshot at the first non-transaction-control statement. AnUPDATEof a row that another transaction changed and committed since then raisescould not serialize access(SQLSTATE 40001), and the application retries the whole transaction.- The
Sessionis the ORM transaction boundary plus an identity map: one object per primary key. Touching lazy relationships after commit issues new SELECTs or fails outside a session. SAVEPOINTdiscards part of a transaction while keeping the rest. ForgettingCOMMITleaves an idle transaction that holds locks and bloats the database.
Reproduce closed-book
REPEATABLE READ and reads balance 100; session B updates to 200 and commits; session A re-reads (still 100), commits, and reads 200. State the isolation level, the anomaly prevented, and the retry rule.Traps
REPEATABLE READequals serializable. It does not: serialization anomalies are still possible, and onlySERIALIZABLEplus retry logic onSQLSTATE 40001prevents them.- No retry loop on serialization failures. Concurrent writers make these routine, not exceptional; abort and rerun the whole transaction.
- Touching relationships after session close is free. Lazy loading fires one
SELECTper parent (\(N+1\)) inside a session and fails outside it; useselectinloadorjoinedloadand guard withraiseload.
Practice
PostgreSQL tutorial transactions: bank transfer with savepoints
Section 3.4: the transfer with BEGIN, COMMIT and ROLLBACK plus the savepoint example
Replay on a scratch PostgreSQL 18 database (a throwaway postgres:18 container works): wrap a three-statement transfer with one bad statement, roll back, and show balances unchanged. Then run the reproduce interleaving, record both reads, and have session A update the row to see the 40001 error.
Self-test
-
In the reproduce interleaving, what does session A read second, and why?
100.REPEATABLE READholds the snapshot from the first statement of the transaction. After commit it reads 200. Verified on PostgreSQL 18.6. -
Which cells of Table 13.1 differ between the SQL standard and PostgreSQL?
PostgreSQLREAD UNCOMMITTEDbehaves asREAD COMMITTED, andREPEATABLE READhas no phantom reads. Both guarantees are stronger than the standard minimums. -
A
REPEATABLE READupdate raisescould not serialize access due to concurrent update. What does the application do?Roll back and retry the whole transaction from the beginning. The retried transaction sees the committed change in its new snapshot. -
Loading \(N\) parents then touching children issues how many statements under lazy loading, and after
selectinload?\(N+1\) under lazy loading. Withselectinload, one for the parents plus oneINquery per 500 parent keys: 2 for 10 parents, 4 for 1200. Verified on SQLAlchemy 2.1.4.
Completion criterion
Stop when
The transfer rollback leaves balances unchanged, the REPEATABLE READ interleaving prints 100 then 200 and an update in session A raises 40001, and the \(N+1\) conversion shows \(N+1\) statements before and 2 after.
Leads to
- 4.6 Indexes, selectivity and query plans Measure the plans behind the invariant checks.
- 4.8 HTTP APIs, pagination, streaming and idempotency Retried requests need transaction boundaries that make replay safe.
Comes after
- 4.4 Relational modeling, joins, aggregation and window functionsGuard the new schema with transactions and a retry policy.
Resources for this unit
- AlgoMaster SQL - selected route Study SQL Foundations, Querying Data, Joins, Subqueries and CTEs, Window Functions, DDL, DML and Views, Performance Optimization, Transactions and Concurrency, and Data Modeling. Stop when the service queries and transaction tests pass.
- PostgreSQL transactions and EXPLAIN Use the transaction tutorial and EXPLAIN guide to demonstrate commit, rollback, one isolation anomaly or prevented anomaly, and a measured plan improvement on the database actually used.
- SQLAlchemy 2.1 documentation - select, Session and eager loading Use the
select()tutorial for 2.x query style,Sessionbasics for the transaction boundary and identity map, and relationship loading for lazy versusselectinloadversusjoinedloadplusraiseload. Useselect()style andSession.executeorSession.scalarsonly, never the legacyQueryAPI.
Also in this section
- Official docs typed-service build path Build one record service in this order: structural protocols and generics; Pydantic models, validators, strict mode, serialization and settings; FastAPI request bodies, dependencies, errors and tests; then PostgreSQL transactions and EXPLAIN. Replay valid, malformed, duplicate and previous-schema requests through CLI and HTTP.
- FastAPI tutorial Use Request Body, Dependencies, Handling Errors and Testing to expose the same service layer through HTTP. Keep framework models outside the domain core and preserve the same error semantics as the CLI.
- AlgoMaster Low-Level Design - one rehearsal Use one fitting design prompt to rehearse interface contracts, dependency direction and failure ownership. Treat the review as feedback, then verify behavior with your own tests.
- Stas' Python Cookbook - data and interfaces Use Serialization and Deserialization plus Environment and Arguments for focused implementation help. Compare JSON, JSONL, CSV and compressed data, and never use pickle for untrusted interchange.
- AlgoMaster Python - interfaces, generics and data files Use Interfaces and Protocols plus Generic Types for structural typing, and Working with JSON plus Working with CSV for JSONL, error handling,
newlineandDictReaderbehavior. Lesson quizzes check recall; implementation is verified with your own scripts. - Alembic tutorial - migration scripts and downgrading Use Create a Migration Script, Running our First and Second Migration, Getting Information and Downgrading to run a paired
upgradeanddowngraderound trip on a scratch database. - PyArrow Parquet read and write docs Use Reading and Writing Single Files plus
Tablefrom_pydictandfrom_pylistto convert normalized records to Parquet and test types, nulls, column projection and schema. - Stripe idempotent requests Use the
Idempotency-Keyheader contract, stored-response replay, parameter-mismatch errors andPOST-only scope to implement keyed replay for one endpoint.