Build1 publisher2 min readPublished
A FOR UPDATE clause moves a SELECT onto InnoDB's write path
InnoDB applies MVCC to reads and pure locking to writes, so under the default Repeatable Read the same predicate can be answered from a snapshot frozen at your first statement or from the newest committed row.
The Engineer · Build desk

What happened
- A dev.to guide to InnoDB isolation behaviour says the engine applies MVCC to its read logic but abandons it entirely for updates, which rely purely on locking.
- SELECT ... FOR UPDATE and SELECT ... FOR SHARE follow the same locking logic as UPDATE, DELETE and INSERT, despite being SELECT statements.
- Under Repeatable Read, InnoDB builds the MID_FLIGHT_TRANSACTION_LIST at the transaction's first SQL statement and never mutates it again for that transaction's lifetime.
- InnoDB keeps only the delta in every row version except the latest, where Postgres keeps a full copy of the row in each version.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- constraint A snapshot frozen at the first statement cannot govern a read-modify-write in the same transaction, so code that locks rows before updating them sees data the plain SELECT beside it is not allowed to see.
- decision Adding FOR UPDATE to serialise writers also changes which version the query answers from, so the lock and the visibility change ship in the same two words.
- cost Teams carrying concurrency reasoning from Postgres to MySQL can keep the read half and have to re-verify every write path by hand, statement by statement.
Once the lock is granted, InnoDB fetches the latest row version, writes an undo_log record holding only the difference, and updates the record in place as the newest version [10]. There is no walk through the version chain on that path, and no visibility check [10]. Five statement forms use it [16].
What licenses skipping the check is a property of exclusive locks. Only one transaction can hold one on a given row at a time, and it holds until commit or abort, so a transaction that has the lock is by construction looking at the latest committed version [11].
The read side keeps separate state. A plain SELECT answers from the transaction list frozen at the first statement [8]; a locking read goes to the newest committed row [11]. One transaction can therefore issue both over the same predicate and get two different versions of the same row. In the guide's worked example, row ID 1 already carries two committed versions, written by TrxID=2 and TrxID=3 [14].
Reconstructing an old version costs a traversal. To read V2, InnoDB starts at the latest version to get a full record, follows the pointer, and applies the stored difference, which in the example sets like_count to 7 [5]. Those versions live in a dedicated undo log rather than in the table, where Postgres keeps every version in the heap, and the author says the separation is part of what makes InnoDB's cleanup simpler [6].
This is where Postgres intuition stops transferring. Postgres applies MVCC at every isolation level, to reads and updates alike, with only minor differences between levels [2]. In InnoDB, MySQL's default storage engine [1], the read half of that intuition survives and the write half has to be re-derived: FOR UPDATE is spelled like a hint and behaves like a write [9].
The guide covers Repeatable Read, InnoDB's default level [7], and says the other levels are quite similar with a few differences; the text breaks off inside a Repeatable Read example before reaching them [15]. So the split by statement type is documented at Repeatable Read, and whether Read Committed rebuilds that frozen list per statement is not shown. Before trusting any of it against a specific schema, I would check which lock a non-unique predicate takes, because the guide makes lock breadth a property of the WHERE clause: a record lock, or a next-key lock [12]. The author wrote that of the InnoDB lock types, next-key locks are "the one that matters here" [13].
What to watch
- Whether the promised sections on the other isolation levels show where the frozen transaction list is rebuilt per statement instead of per transaction.
- Whether the InnoDB manual agrees that SELECT ... FOR SHARE follows the identical four-step path as an UPDATE, lock acquisition included.
- How undo log length behaves under long-running readers, given that every non-latest version stores only a delta.