MariaDB’s Snapshot Isolation: A Fix That Breaks More Than It Fixes

April 17, 2026
Author
Vadim Tkachenko
Share this Post:

Jepsen’s analysis of MySQL 8.0.34 walked through a set of concurrency and isolation anomalies in InnoDB. MariaDB, which inherits the same codebase, took the report seriously and shipped a response: a new server variable called innodb_snapshot_isolation, turned on by default starting in 11.8. The announcement claims that with the flag enabled, Repeatable Read in MariaDB now satisfies snapshot isolation.

It’s a good intention. The problem is what actually ships.

Two things fall apart once you start looking. First, the fix isn’t complete — the anomalies Jepsen flagged can still be reproduced under concurrent load.

Second, it introduces incompatibilities with MySQL (in default enabled mode) – the moment the SNAPSHOT ISOLATION does fire as intended, it introduces ERROR 1020: Record has changed since last read into transactions that used to complete silently. That error now shows up in multiple applications, requiring to make changes either on code level or disabling innodb_snapshot_isolation

A quick refresher on what snapshot isolation promises

Snapshot isolation is supposed to let a transaction see a consistent view of the database taken at the moment it started.

Key behaviors of Snapshot Isolation:

  • Consistency: A transaction sees data as it was at its start time, ignoring updates from concurrent transactions.
  • Non-Blocking Reads: Readers do not block writers, and writers do not block readers, reducing contention.
  • Conflict Detection: A transaction only commits if its updates do not conflict with concurrent updates made since the snapshot was taken.

Two anomalies are specifically should not be present with Snapshot Isolation:

Lost Update Anomaly: Two transactions read the same value, both modify it, and one overwrites the other. Two users increment a counter from 10. Both read 10, both write 11. The correct answer is 12.

Non-Repeatable Read Anomaly: A transaction reads a row, someone else commits a change, and the first transaction reads the same row again and sees something different. Product price was $100, then it’s $120 — all inside one transaction.

What MySQL does today

Plain Repeatable Read handles the simple case (two reads) fine:

# Session A Session B A sees
1 SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; START TRANSACTION;
2 SELECT name FROM test_nrr WHERE id=0; 'Alice'
3 UPDATE test_nrr SET name='Bob' WHERE id=0; (autocommit)
4 SELECT name FROM test_nrr WHERE id=0; 'Alice'RR holds
5 COMMIT;

Add a write on Session A between the two reads, though, and RR does not hold (we get Non-Repeatable Read Anomaly):

# Session A Session B A sees
1 SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; START TRANSACTION WITH CONSISTENT SNAPSHOT;
2 SELECT name FROM test_nrr WHERE id=0; 'Alice'
3 UPDATE test_nrr SET name='Bob' WHERE id=0;
4 UPDATE test_nrr SET gender=99 WHERE id=0;
5 SELECT name FROM test_nrr WHERE id=0; 'Bob'Non-Repeatable Read

And here’s Lost Update under plain Repeatable Read in MySQL:

Time Session A Session B
t1 BEGIN;
t2 BEGIN;
t3 SELECT counter FROM t WHERE id=1; → 10
t4 SELECT counter FROM t WHERE id=1; → 10
t5 UPDATE t SET counter=10+1 WHERE id=1; COMMIT; (counter = 11)
t6 UPDATE t SET counter=10+1 WHERE id=1; COMMIT; (still 11)

Expected 12. Got 11. Session A’s increment is gone.

For the full picture of what every isolation level actually guarantees across engines, Martin Kleppmann’s Hermitage suite is the good reference: github.com/ept/hermitage.

What MariaDB is supposed to do

With innodb_snapshot_isolation=ON, the Non-Repeatable Read scenario should stop at step 4 with:

# Session A Session B A sees
1 SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; START TRANSACTION WITH CONSISTENT SNAPSHOT;
2 SELECT name FROM test_nrr WHERE id=0; 'Alice'
3 UPDATE test_nrr SET name='Bob' WHERE id=0;
4 UPDATE test_nrr SET gender=99 WHERE id=0;
ERROR 1020: Record has changed since last read in table ‘test_nrr
5 TRANSACTION ROLLBACK

 

And the Lost Update case should force whichever transaction tries to commit the stale write to roll back instead. That’s the guarantee:

 

Time Session A Session B
t1 BEGIN;
t2 BEGIN;
t3 SELECT counter FROM t WHERE id=1; → 10
t4 SELECT counter FROM t WHERE id=1; → 10
t5 UPDATE t SET counter=10+1 WHERE id=1; COMMIT; (counter = 11)
t6 UPDATE t SET counter=10+1 WHERE id=1; -> ERROR 1020: Record has changed since last read in table 'test_nrr'

TRANSACTION ROLLBACK

That is in both cases MariaDB introduces

ERROR 1020: Record has changed since last read in table

What actually happens

Run it under concurrent load and both anomalies still turn up:

ERROR 1020 fires most of the time, but  not every time. A snapshot-isolation guarantee that only holds “usually” isn’t a guarantee. The whole reason you pick an isolation level is for the bound it gives you.

The another problem: compatibility

Even when the error does fire when it should, MariaDB, by default, introduced a new failure mode into every client connected to the database. Almost nothing in the MySQL ecosystem catches ERROR 1020 mid-transaction and retries. It sees an unexpected error, it bails.

Issues already filed against applications running on MariaDB 11.8:

These are apps that work on MySQL, work on earlier MariaDB, and now fail on 11.8. The clean workaround is turning the flag off — which defeats the point of shipping it on by default.

Where that leaves us

Jepsen pointed at real transactional anomalies and MariaDB tried to answer them.  But a partial fix that silently breaks working applications isn’t what “drop-in MySQL replacement” is supposed to mean. If the goal was to make migration easier, 11.8 went the other direction.

Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments

Far
Enough.

Said no pioneer ever.
MySQL, PostgreSQL, InnoDB, MariaDB, MongoDB and Kubernetes are trademarks for their respective owners.
© 2026 Percona All Rights Reserved