Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

SQL (PostgreSQL, MySQL, SQLite)  Module 11 – Transactions, isolation and locking

Isolation levels and read anomalies

Dirty reads, non-repeatable reads, phantoms, lost updates and write skew, the isolation levels of PostgreSQL, MySQL and SQLite, and how to pick one.

  • Intermediate
  • 30 minutes
  • Examples run with sql.js 1.14.2 and Python 3.14.8
  • By MySmartCoPilot
Free core, Pro depth: 12 of 30 minutes free
Free: Five read anomalies, one sentence each · The four levels, and what each engine does · A non-repeatable read on a real database · Check yourself. With a pass: 2 in-depth parts and 3 interview answers, about 18 minutes.

What you will learn

  • Describe dirty reads, non-repeatable reads, phantoms, lost updates and write skew
  • Map the isolation levels and defaults of PostgreSQL, MySQL and SQLite
  • Set an isolation level for one transaction or a session in each engine
  • Pick an isolation level for a given workload

Before you start

What you’ll learn · Before you start

What you will learn

  • Describe dirty reads, non-repeatable reads, phantoms, lost updates and write skew
  • Map the isolation levels and defaults of PostgreSQL, MySQL and SQLite
  • Set an isolation level for one transaction or a session in each engine
  • Pick an isolation level for a given workload

Before you start

On this page

An isolation level decides how much a transaction may see of other transactions that run at the same time. Weaker levels let more read anomalies through: a dirty read sees data that may never be committed, a non-repeatable read gets two answers to one question, a phantom finds new rows on a second search, a lost update overwrites a change it never saw, and write skew breaks a rule that two transactions each checked. The SQL standard names four isolation levels: READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ and SERIALIZABLE. PostgreSQL starts every transaction at Read Committed and MySQL’s InnoDB at Repeatable Read. SQLite has no level to choose: it admits a single writer at a time, and that alone makes its transactions serializable. Choose the level by the anomalies your code cannot live with, and set it for the transactions that need it.

Five read anomalies, one sentence each

  • Dirty read: a transaction reads a change that another transaction has not committed and may still roll back.
  • Non-repeatable read: reading one row twice gives two committed values, because another transaction changed it in between.
  • Phantom: running one search twice returns a different set of rows, because another transaction inserted or deleted a match.
  • Lost update: two transactions read a value and each writes a new value computed from it, so one write silently replaces the other.
  • Write skew: two transactions check a rule that spans several rows, then change different rows, and together break the rule that each one checked.

The standard defines its levels by the first three. Lost updates and write skew start with a read that goes stale before the write, which is why they count among the read anomalies here, but the standard’s table does not mention them, and each engine handles them its own way.

The four levels, and what each engine does

What the SQL standard allows at each level
Criterion Dirty readNon-repeatable readPhantom
READ UNCOMMITTED PossiblePossiblePossible
READ COMMITTED Not possiblePossiblePossible
REPEATABLE READ Not possibleNot possiblePossible
SERIALIZABLE Not possibleNot possibleNot possible

The standard sets the least each level must prevent, and engines may prevent more. The isolation levels share their names across engines, but not their machinery:

  • PostgreSQL runs Read Uncommitted as Read Committed, so it never shows a dirty read. Its Repeatable Read keeps one snapshot for the whole transaction, which also hides phantoms. Serializable adds checks and fails one transaction when the result could differ from every one-at-a-time order.
  • MySQL with InnoDB really allows dirty reads at Read Uncommitted. At its default, Repeatable Read, plain SELECTs read a snapshot taken at the transaction’s first read, while UPDATE, DELETE and locking reads work on the newest committed rows.
  • SQLite has nothing to set. Its transactions are serializable because writers take turns, and in WAL mode a reader keeps a stable snapshot while a writer commits.

You choose the level per transaction. In PostgreSQL, write BEGIN ISOLATION LEVEL REPEATABLE READ, or SET TRANSACTION ISOLATION LEVEL REPEATABLE READ as the first statement after BEGIN. In MySQL, SET TRANSACTION ISOLATION LEVEL READ COMMITTED comes before START TRANSACTION and applies to the next transaction only, while SET SESSION TRANSACTION ISOLATION LEVEL changes it for the rest of the connection.

A non-repeatable read on a real database

Timeline: A reads qty 10, B changes it to 7 and commits, A reads again and gets 7 at Read Committed or 10 at Repeatable Read.Session Areads twicestock rowqtySession BupdatesBEGIN; reads qty: 10UPDATE qty = 7; COMMITreads qty againRead Committed: 7Repeatable Read: 10

The same second read, two isolation levels

Text description of the diagram

The figure is a timeline with three columns: session A, which reads the stock row twice; the row's qty column; and session B, which changes it. Time runs downwards.

  1. A begins a transaction and reads qty: 10.
  2. B sets qty to 7 and commits.
  3. A reads qty again, in the same transaction.
  4. At Read Committed the second read returns 7, the newly committed value: a non-repeatable read. At Repeatable Read it returns 10 again, because the whole transaction reads from one snapshot.

The browser runs one session at a time, so this program opens two connections to one SQLite file in WAL mode and runs them step by step. The reader reads the stock of a kettle twice, once with each read in a transaction of its own and once with both reads inside one transaction:

Two reads, two ways, while another connection commits Python · snapshots.py
"""When does a read see another session's commit? Two connections to one SQLite file.

SQLite in WAL mode gives each read transaction a snapshot. A read outside BEGIN is a
transaction of its own; reads inside BEGIN ... COMMIT share one snapshot.
"""
import os
import sqlite3

DB = "shop.db"
for suffix in ("", "-wal", "-shm"):
    if os.path.exists(DB + suffix):
        os.remove(DB + suffix)


def connect():
    return sqlite3.connect(DB, autocommit=True, timeout=0)


setup = connect()
setup.execute("PRAGMA journal_mode = WAL")
setup.execute("CREATE TABLE stock (id INTEGER PRIMARY KEY, item TEXT NOT NULL, qty INTEGER NOT NULL)")
setup.execute("INSERT INTO stock VALUES (1, 'kettle', 10)")

reader, writer = connect(), connect()
QTY = "SELECT qty FROM stock WHERE id = 1"


def read(label):
    print(f"   reader sees {reader.execute(QTY).fetchone()[0]}{label}")


print("1. Two reads, each in a transaction of its own")
read("")
writer.execute("UPDATE stock SET qty = 7 WHERE id = 1")
print("   writer sets qty to 7 and commits")
read("  <- a non-repeatable read")

print("2. Two reads inside one transaction")
reader.execute("BEGIN")
read("")
writer.execute("UPDATE stock SET qty = 4 WHERE id = 1")
print("   writer sets qty to 4 and commits")
read("  <- the same snapshot as before")
reader.execute("COMMIT")
read(" after its own COMMIT")

Output

1. Two reads, each in a transaction of its own
   reader sees 10
   writer sets qty to 7 and commits
   reader sees 7  <- a non-repeatable read
2. Two reads inside one transaction
   reader sees 7
   writer sets qty to 4 and commits
   reader sees 7  <- the same snapshot as before
   reader sees 4 after its own COMMIT

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 snapshots.py

In the first run, each read is its own transaction and sees the latest commit, so the reader gets 10 and then 7. That is exactly how every statement behaves at PostgreSQL’s Read Committed: each one takes a fresh snapshot. In the second run, both reads share one snapshot and return 7, although the writer committed 4 in between, which is the promise of Repeatable Read. Only after its own COMMIT does the reader see 4.

Check yourself

Check yourself

4 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 4 A transaction counts the bookings for room 101 twice and gets 2, then 3, because another transaction inserted a booking and committed in between. Which anomaly is this?

    Choose one answer.

    Show the answer to question 1

    Answer: A phantom

    The second search returned a different set of rows: a phantom. A non-repeatable read is the same thing for one row whose value changes; a dirty read would need the insert to be still uncommitted.

  2. Question 2 of 4 Which isolation levels do PostgreSQL 18 and MySQL 8.4 with InnoDB use by default?

    Choose one answer.

    Show the answer to question 2

    Answer: PostgreSQL Read Committed, MySQL Repeatable Read

    PostgreSQL's default_transaction_isolation is read committed, and InnoDB's default is REPEATABLE READ. SQLite has no levels to choose: one writer at a time makes its transactions serializable.

  3. Question 3 of 4 A PostgreSQL transaction at READ UNCOMMITTED reads a row that another transaction has changed but not committed. What does it see?

    Choose one answer.

    Show the answer to question 3

    Answer: The last committed value: PostgreSQL runs Read Uncommitted as Read Committed

    PostgreSQL accepts the level's name but never shows uncommitted data, and readers do not wait for writers. MySQL at READ UNCOMMITTED would return the uncommitted value.

  4. Question 4 of 4 In the SQLite example, the reader runs BEGIN, reads 7, the writer commits 4, and the reader reads again before its COMMIT. What does the second read return, and why?

    Choose one answer.

    Show the answer to question 4

    Answer: 7, because both reads use the snapshot the transaction started with

    Inside one transaction, SQLite in WAL mode keeps the reader's snapshot, the behaviour Repeatable Read promises. Reads outside a transaction would each take a new snapshot, as statements do at Read Committed.

Every anomaly on PostgreSQL, MySQL and SQLite, replayed Pro: opens with a Pro, Premium or Ultimate pass

The standard’s table says what a level must prevent; this part shows what each engine actually does. It replays five two-session timelines at every level of PostgreSQL 18 and MySQL 8.4, explains each outcome, and runs the hardest case, write skew, on SQLite.

In this part:

  • Five timelines on PostgreSQL 18
  • The same timelines on MySQL 8.4
  • SQLite: the write skew attempt refused, reproduced

1 diagram · 1 runnable example (Python) · about 4 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Choosing and setting a level, with worked cases Pro: opens with a Pro, Premium or Ultimate pass

A level is a trade between anomalies and failures: stronger levels refuse more interleavings, so more transactions wait or fail and must be retried. This part sets levels without the common traps, then works through five workloads and the cost of Serializable in retries.

In this part:

  • Setting the level without the three common traps
  • Five workloads, worked
  • What Serializable costs, in retries
  • A level for any transaction, in four questions

about 4 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Interview questions

  1. Fresher to mid · Warm-up

    Which isolation level do PostgreSQL, MySQL and SQLite use by default, and which read anomalies can each show at it?

    PostgreSQL uses Read Committed: every statement sees the data committed when it started, so there are no dirty reads, but two reads in one transaction can disagree (a non-repeatable read or a phantom), and a read followed by a write can lose an update. MySQL with InnoDB uses Repeatable Read: plain SELECTs read one snapshot for the whole transaction, but UPDATE, DELETE and locking reads work on the newest rows, so lost updates and write skew are still possible. SQLite lets one writer in at a time, so its transactions are serializable, and in WAL mode a reader keeps its snapshot while a writer commits.

  2. Mid to senior

    A month-end report on PostgreSQL runs 40 queries over 15 minutes at the default level while transfers keep running, and its assets and liabilities no longer balance. Why, and what do you change without blocking the transfers?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. Mid to senior

    A coupon may be redeemed 3 times. The code counts the coupon's redemptions and inserts a new one when the count is below 3, at Repeatable Read on PostgreSQL. During a sale it was redeemed 5 times. Explain, and fix it on PostgreSQL and on MySQL.

    Model answer, Pro. First step and follow-ups

  4. Senior to staff

    On MySQL 8.4 at the default level, a transaction reads qty 10 with a plain SELECT, then UPDATE stock SET qty = qty - 1 WHERE id = 1 AND qty > 0 changes 0 rows, and a second plain SELECT still shows 10. Explain each step and what the code should do.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • An isolation level decides which read anomalies a transaction may meet: dirty reads, non-repeatable reads, phantoms, lost updates and write skew.
  • Defaults differ: PostgreSQL Read Committed, MySQL Repeatable Read, SQLite serializable with one writer at a time.
  • Each statement at Read Committed sees the latest commits; a Repeatable Read transaction keeps one snapshot.
  • Set the level per transaction, at its start.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.