Transactions in InnoDB: REPEATABLE READ, implicit commits and gap locks
InnoDB's default isolation level, snapshot versus locking reads, the statements that commit for you, why an error does not roll back a transaction, and the locks behind deadlocks.
After this lesson you can
- State InnoDB's default isolation level and what a consistent read sees
- Name statements that implicitly commit an open transaction
- Explain why an error inside a transaction must be followed by ROLLBACK
- Describe record, gap and next-key locks and how two transactions deadlock
The SQL course defined transactions and isolation anomalies; this lesson is how InnoDB implements them. Each run here gets a single connection, so behaviour that needs two concurrent sessions (snapshots, gap locks, deadlocks) is described, not run: the three sections on reads, locks and deadlocks are definitions, and only the examples are observed output.
The defaults
CREATE TABLE jobs (id INT PRIMARY KEY, status VARCHAR(10) NOT NULL);INSERT INTO jobs VALUES (1, 'done'), (2, 'queued'), (3, 'queued');START TRANSACTION;SELECT id INTO @claimedFROM jobsWHERE status = 'queued'ORDER BY idLIMIT 1FOR UPDATE SKIP LOCKED;COMMIT; SELECT @@autocommit AS autocommit, @@transaction_isolation AS isolation, @@innodb_lock_wait_timeout AS lock_wait_timeout_s, @@innodb_deadlock_detect AS deadlock_detect, @claimed AS claimed_job;autocommitis on. Every statement outsideSTART TRANSACTIONis its own transaction and commits immediately.- The isolation level is
REPEATABLE READ(PostgreSQL and SQL Server default toREAD COMMITTED). Change it per session withSET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED. - A lock wait gives up after
innodb_lock_wait_timeoutseconds (50), and deadlock detection (innodb_deadlock_detect) is on.
The example also ran FOR UPDATE SKIP LOCKED, the job-queue pattern in
which each worker takes the first row nobody else has locked.
Two kinds of read
Under REPEATABLE READ, a plain SELECT is a consistent read: it
reads from a snapshot taken at the transaction's first read, using undo
records (MVCC), and takes no locks. Other sessions' later commits are
invisible to it until your transaction ends; your own changes are visible.
A locking read (SELECT ... FOR UPDATE, FOR SHARE), UPDATE and
DELETE read the latest committed row and lock it, so within one
transaction they can see a value the plain did not. Read a value
you are about to change with .
CREATE TABLE log (id INT PRIMARY KEY, note VARCHAR(40));START TRANSACTION;INSERT INTO log VALUES (1, 'before CREATE TABLE');CREATE TABLE scratch (x INT); -- DDL commits the open transactionROLLBACK; START TRANSACTION;INSERT INTO log VALUES (2, 'before a second START TRANSACTION');START TRANSACTION; -- commits the first oneROLLBACK; START TRANSACTION;INSERT INTO log VALUES (3, 'plain rollback');ROLLBACK; START TRANSACTION;INSERT INTO log VALUES (4, 'before the savepoint');SAVEPOINT sp;INSERT INTO log VALUES (5, 'after the savepoint');ROLLBACK TO SAVEPOINT sp;COMMIT; SELECT id, note FROM log ORDER BY id;Rows 1 and 2 survived a ROLLBACK because something committed them before
it ran. Row 5 shows SAVEPOINT: rolling back to it undoes only what came
after.
An error does not roll back the transaction
When a statement inside a transaction fails (a constraint, a bad value, a lock wait timeout), InnoDB undoes that statement, not the transaction; a deadlock is the exception described above. If the code carries on and commits, whatever succeeded before the error is committed.
CREATE TABLE accounts ( id INT PRIMARY KEY, owner VARCHAR(10) NOT NULL, balance INT NOT NULL, CHECK (balance >= 0));INSERT INTO accounts VALUES (1, 'Nino', 300), (2, 'Ana', 50), (3, 'Luka', 300), (4, 'Eka', 50);-- Ignores errors and commits whatever happened.CREATE PROCEDURE careless_transfer(IN p_from INT, IN p_to INT, IN p_amount INT)BEGIN DECLARE CONTINUE HANDLER FOR SQLEXCEPTION BEGIN END; START TRANSACTION; UPDATE accounts SET balance = balance + p_amount WHERE id = p_to; UPDATE accounts SET balance = balance - p_amount WHERE id = p_from; COMMIT;END;-- Rolls the whole transaction back on any error.CREATE PROCEDURE careful_transfer(IN p_from INT, IN p_to INT, IN p_amount INT)BEGIN DECLARE EXIT HANDLER FOR SQLEXCEPTION ROLLBACK; START TRANSACTION; UPDATE accounts SET balance = balance + p_amount WHERE id = p_to; UPDATE accounts SET balance = balance - p_amount WHERE id = p_from; COMMIT;END;CALL careless_transfer(1, 2, 1000);CALL careful_transfer(3, 4, 1000); SELECT id, owner, balance FROM accounts ORDER BY id;The careless procedure credited Ana, failed to debit Nino because of the
CHECK constraint, ignored the error and committed: 1,000 created from
nothing. The careful one has an EXIT HANDLER that rolls back, so nothing
changed.
Common mistakes
- Running DDL inside a transaction and expecting
ROLLBACKto undo the rows before it. The DDL already committed them. - Committing after a failed statement. InnoDB rolled back only the failing statement; roll back the transaction yourself.
- Reading with a plain
SELECT, then updating based on it under concurrency. UseSELECT ... FOR UPDATEor a single conditionalUPDATE. - Treating error 1213 as fatal. It means "your transaction was rolled back, try again".
What interviewers ask
- What is InnoDB's default isolation level, and does it prevent
phantoms?
REPEATABLE READ; snapshots hide new rows from plain reads, and next-key locks block inserts into ranges read with locking reads. - What is a gap lock? A lock on the space between index entries that prevents inserts there; part of a next-key lock.
- Does an error inside a transaction roll it back? Only the failing
statement; the application must issue
ROLLBACK.
Exercise · Pro
Which rows survive the session?
Exercises are free in the first 3 lessons of every course. From here on they are part of Pro and Sprint; the lesson and its runnable examples stay open.
Upgrade