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
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:
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.
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.
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
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.
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.
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.