1 of 25

Isolation Isn’t All Bad

(For Your Database)

March 2026

Sean McNealy

2 of 25

Sean McNealy

Introduction

sean@mcnealysoftware.com

https://github.com/seanmcnealy

3 of 25

JDBC sets isolation

What does this do?

�connection.setTransactionIsolation(TRANSACTION_SERIALIZABLE);

Why?

Do you have to?

4 of 25

ACID Transactions[1][2]

  • Atomic
    • One transaction, not parts
  • Consistent
    • Valid state
  • Isolated
    • Non-interference
  • Durable
    • Writes are saved

5 of 25

SQL Isolation Levels[3]

  • Read Uncommitted
  • Read Committed
  • Repeatable Read
  • Serializable

6 of 25

The problem (maybe)

BEGIN TRANSACTION;

UPDATE table SET amount = amount - 1 where id = 1;

UPDATE table SET amount = amount + 1 where id = 2;

COMMIT TRANSACTION;

We’ve moved 1 “amount” from one record to another.

?

!

7 of 25

The problem #2

SELECT id, name FROM customers LIMIT 20;

SELECT COUNT(*) FROM customers;

You could get a different number of rows in the first select than the aggregation call without repeatable reads.

?

!

8 of 25

SQL Integrity Levels

  • Read Uncommitted
  • Read Committed
  • Repeatable Read
  • Serializable

9 of 25

Follow along with code

github.com/seanmcnealy/sql_examples

10 of 25

Isolation Implementations

  • Locking
  • Multiversioning aka MVCC

11 of 25

Why choose levels?

Performance / Concurrency

Throughput

Latency and Backpressure

Transactions are configurable

Transactions can hint they are read only:

connection.setReadOnly(true);

12 of 25

Database Support

Everyone is different!

13 of 25

Database Support:

MySQL

Default is READ_COMMITTED

REPEATABLE_READ stops a phantom read

14 of 25

Database Support:

PostgreSQL

Default is READ_COMMITTED

REPEATABLE_READ stops a phantom read

READ_UNCOMMITTED is just READ_COMMITTED

15 of 25

Database Support:

Microsoft SQL Server

Default is READ_COMMITTED

Has its own extra level

TRANSACTION_SNAPSHOT, which is REPEATABLE_READ but hints how to do that read

16 of 25

Database Support:

SQLite

Default is SERIALIZABLE

*If caching is OFF then READ_UNCOMMITTED is equal to READ_COMMITTED

17 of 25

Database Support:

Oracle

Default is READ_COMMITTED

Supports SERIALIZABLE

Does not support READ_UNCOMMITTED or REPEATABLE_READ

18 of 25

Database Support:

DB2

Default is READ_COMMITTED

19 of 25

Database Support:

Snowflake

Only READ_COMMITTED

20 of 25

Database Support:

Hive, Cassandra, Druid…

Don’t set any level, it won’t work

These are just designed differently

21 of 25

Database Support:

H2

Legacy Mode:

Default is READ_COMMITTED

Supports other levels

MVCC Mode:

Only READ_COMMITTED

22 of 25

What Next

Application-level locking

Partitioning

By data

By time

Streaming

23 of 25

Code

@Transactional(readOnly = true, isolation = Isolation.READ_UNCOMMITTED)

When using SERIALIZABLE get write locks early

@Lock(LockModeType.PESSIMISTIC_WRITE)

connection.setAutoCommit(false);

connection.setReadOnly(true);

connection.setTransactionIsolation( Connection.TRANSACTION_READ_COMMITTED);

When using SERIALIZABLE get write locks early

SELECT ... FOR UPDATE

JDBC

Spring Annotations

24 of 25

Sean McNealy

Questions?

Thank you

McNealy Software Inc

sean@mcnealysoftware.com

25 of 25

Citations

1. Haerder, T.; Reuter, A. (1983). "Principles of transaction-oriented database recovery". ACM Computing Surveys.

2. ANSI X3.135-1986. https://nvlpubs.nist.gov/nistpubs/Legacy/FIPS/fipspub127.pdf

3. ISO/IEC 9075:1992. https://www.contrib.andrew.cmu.edu/~shadow/sql/sql1992.txt