Chapter 23 Advanced 57 Questions

Practice Questions — Transactions and ACID Properties

← Back to Notes
8 Easy
11 Medium
13 Hard

Topic-Specific Questions

Question 1
Easy
What does the A in ACID stand for, and what does it guarantee?
Think all-or-nothing.
Atomicity. Every statement inside a transaction succeeds together or none of them do. A partial completion is impossible — on ROLLBACK or crash, the entire transaction is undone.
Question 2
Easy
Which statement begins a transaction in MySQL?
Two equivalent keywords.
START TRANSACTION; (preferred) or BEGIN;. Both are equivalent in MySQL. They implicitly turn off autocommit for the duration of the transaction.
Question 3
Easy
What is the final balance?
-- starts: 1000
START TRANSACTION;
UPDATE accounts SET balance = balance + 200 WHERE id = 1;
UPDATE accounts SET balance = balance - 50 WHERE id = 1;
ROLLBACK;
SELECT balance FROM accounts WHERE id = 1;
ROLLBACK undoes both UPDATEs.
1000
Question 4
Easy
What is MySQL's default isolation level?
Four words.
REPEATABLE READ. Unlike some databases (PostgreSQL defaults to READ COMMITTED), MySQL/InnoDB chose REPEATABLE READ as its default because MVCC makes it cheap and it prevents most common anomalies.
Question 5
Easy
What is a dirty read?
Think 'data that doesn't officially exist yet'.
Reading a row that has been modified by another transaction that has not yet committed. If the writer then ROLLBACKs, the reader has seen data that never officially existed. Only READ UNCOMMITTED permits dirty reads.
Question 6
Easy
What does COMMIT do?
Two guarantees: visibility and durability.
Makes all changes inside the transaction (a) visible to other transactions and (b) durably written to the redo log on disk, so they survive a crash. After COMMIT, ROLLBACK cannot undo them.
Question 7
Medium
Final balance?
START TRANSACTION;
UPDATE accounts SET balance = balance + 100 WHERE id = 1;  -- 1000 -> 1100
SAVEPOINT p1;
UPDATE accounts SET balance = balance + 200 WHERE id = 1;  -- 1100 -> 1300
ROLLBACK TO SAVEPOINT p1;
COMMIT;
Roll back to the savepoint, then commit.
1100
Question 8
Medium
Which isolation level prevents dirty reads but still allows non-repeatable reads?
Read only committed data, but another transaction can commit mid-way through yours.
READ COMMITTED.
Question 9
Medium
What does SELECT ... FOR UPDATE do?
Acquires an exclusive lock.
Acquires an exclusive (X) lock on the matching rows for the duration of the transaction. Other transactions that try to SELECT FOR UPDATE, LOCK IN SHARE MODE, or modify those rows block until this transaction commits or rolls back. Plain SELECTs still work (they read the MVCC snapshot).
Question 10
Medium
What is a deadlock, and how does InnoDB handle it?
Cycle of lock waits + detection.
A deadlock is a cycle in the wait-for graph: T1 waits for a lock T2 holds, T2 waits for a lock T1 holds. InnoDB's deadlock detector notices the cycle and aborts one of the transactions (the one that has done less work) with ERROR 1213 / SQLSTATE 40001. The aborted transaction must retry.
Question 11
Medium
Why does InnoDB use MVCC for REPEATABLE READ rather than taking read locks?
Concurrency.
MVCC lets readers and writers run concurrently without blocking each other. SELECT sees a snapshot of the database as of transaction start, regardless of concurrent writes. Taking read locks instead would force readers to queue behind writers and vice versa, crushing throughput.
Question 12
Medium
Is SERIALIZABLE always the right choice for safety?
Think cost.
No. SERIALIZABLE effectively upgrades every SELECT to LOCK IN SHARE MODE and blocks writers until readers commit. Under high concurrency, throughput plummets. Use SERIALIZABLE only for short, critical sections where the anomalies prevented are genuine problems.
Question 13
Hard
Explain why two concurrent withdrawals without FOR UPDATE can cause an overdraft even under REPEATABLE READ.
Think MVCC snapshots vs write locks.
Each transaction's SELECT reads the MVCC snapshot taken at transaction start — both see balance = 500. Each transaction's IF check decides 500 >= 300 is OK and issues an UPDATE. UPDATE takes a write lock, so the second one waits, but once it acquires it, it just executes balance = balance - 300 on the current (already-debited) value. Final balance: -100. REPEATABLE READ prevents the read from changing, not the arithmetic from overshooting. FOR UPDATE forces the SELECT itself to lock, serialising the whole critical section.
Question 14
Hard
Why must DDL statements be treated as permanent in MySQL transactions?
Implicit commits.
Statements like CREATE TABLE, ALTER TABLE, DROP TABLE, TRUNCATE, and RENAME cause an implicit COMMIT of the current transaction. A subsequent ROLLBACK cannot undo the DDL (the table is still created/dropped/altered). This is a MySQL-specific behaviour — some databases (PostgreSQL) have transactional DDL. Plan migrations accordingly.
Question 15
Hard
How does adding an index reduce lock contention?
Locks attach to what the engine scans.
InnoDB locks the index rows it reads to enforce isolation. Without an index on the WHERE column, a statement scans (and thus locks) far more rows than it needs — potentially the whole table. With an index, only the matching index entries are locked. Better indexes mean less locking, fewer deadlocks, and higher concurrency.
Question 16
Hard
Describe the 'transactional outbox' pattern and when to use it.
Reliable event emission from transactional systems.
Instead of calling an external system (email, message queue, webhook) from inside a DB transaction, INSERT a row into an outbox table as part of the transaction. A separate background worker reads committed outbox rows and dispatches them, marking them sent. If the transaction rolls back, the outbox row is rolled back too, so no message is emitted for a cancelled transaction. If the worker crashes, unprocessed rows remain and are retried.
Question 17
Hard
A long-running SELECT inside a transaction holds a snapshot for 10 minutes. What is the downside on a busy server?
Undo log growth.
InnoDB must keep old versions of every row modified during those 10 minutes, in case the long-running transaction reads them. The undo log (rollback segment) grows proportionally, filling disk and slowing every other transaction. This is the 'history list length' problem. Kill long-running read transactions with KILL or split them into smaller chunks.

Mixed & Application Questions

Question 1
Easy
What does this return?
-- balance starts at 100
START TRANSACTION;
UPDATE t SET balance = 500 WHERE id = 1;
ROLLBACK;
SELECT balance FROM t WHERE id = 1;
Rollback undoes the update.
100
Question 2
Easy
Is SELECT * FROM t inside a transaction blocking under MySQL REPEATABLE READ?
MVCC.
No. Plain SELECT reads the MVCC snapshot without taking any row lock, so it does not block and is not blocked by other writers.
Question 3
Medium
What is the final value?
-- x starts at 10
START TRANSACTION;
UPDATE t SET x = x + 1 WHERE id = 1;  -- 11
SAVEPOINT a;
UPDATE t SET x = x + 1 WHERE id = 1;  -- 12
SAVEPOINT b;
UPDATE t SET x = x + 1 WHERE id = 1;  -- 13
ROLLBACK TO SAVEPOINT a;
COMMIT;
SELECT x FROM t;
Roll back to savepoint 'a' removes changes after it.
11
Question 4
Medium
Name three ways to cause a deadlock.
Cycle creation.
(1) Two transactions update the same rows in opposite orders (T1 locks 101 then 102; T2 locks 102 then 101). (2) Transactions taking locks from left to right on different indexes that overlap. (3) A transaction locking a gap (next-key lock) that another transaction tries to insert into. All three create cycles.
Question 5
Medium
Under REPEATABLE READ in InnoDB, can a phantom read occur on a plain SELECT?
InnoDB uses snapshots for reads.
No. MVCC snapshots make every SELECT inside the transaction see the same set of rows regardless of concurrent INSERTs that are committed after the transaction started. For plain reads, InnoDB REPEATABLE READ is effectively serializable.
Question 6
Medium
Two sessions run:
-- Session 1
START TRANSACTION;
UPDATE t SET x = 100 WHERE id = 1;
-- no commit yet

-- Session 2 (READ COMMITTED)
START TRANSACTION;
SELECT x FROM t WHERE id = 1;
What does Session 2's SELECT return (assuming original x = 5)?
READ COMMITTED reads the latest COMMITTED value.
5
Question 7
Medium
What does LOCK IN SHARE MODE guarantee?
Shared lock semantics.
Takes a shared (S) lock on the read rows for the duration of the transaction. Other transactions can also take S locks, so concurrent readers coexist. But no one can take an X lock (no UPDATE, no DELETE, no FOR UPDATE) on those rows until this transaction commits. Use it for 'read now, I will need the value to stay consistent for the rest of my transaction' scenarios.
Question 8
Hard
A payment service sees 20 deadlocks per minute. What are the first three things to check?
Ordering, indexes, transaction length.
(1) Are transactions locking rows in a consistent order? Sort by id before updating multiple rows. (2) Are the WHERE clauses indexed? A missing index locks far more rows than necessary, amplifying contention. (3) How long are the transactions? External API calls, large scans, or slow queries inside transactions expand the lock window; shorten them.
Question 9
Hard
What happens?
-- Session 1 (autocommit OFF)
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- forgets to COMMIT and moves on

-- Session 2
SELECT balance FROM accounts WHERE id = 1;  -- under REPEATABLE READ
Think about what Session 2 sees and whether Session 2 blocks.
Session 2's plain SELECT returns the old, pre-deduction balance and does not block. Session 1's uncommitted UPDATE is invisible under MVCC. But Session 1's lock is still held indefinitely, so any write to row id = 1 by another session will block until Session 1 commits, rolls back, or is killed.
Question 10
Hard
Why should you never call an external API inside a transaction?
Locks and latency.
Locks are held from the first write statement until COMMIT/ROLLBACK. An external call can take seconds and has unpredictable latency. Every row your transaction has touched is locked for the duration. Under load, a 3-second API call turns into a 3-second outage for anyone else touching those rows. Perform external calls before START TRANSACTION (to get inputs) or after COMMIT (to emit events via the outbox pattern).
Question 11
Hard
Final balances after:
-- accounts: 101:1000, 102:1000
SET autocommit = 0;

UPDATE accounts SET balance = balance - 300 WHERE id = 101;
-- connection drops here without COMMIT or ROLLBACK
What happens to uncommitted work when the connection closes?
101:1000, 102:1000. When the client disconnects without COMMIT, MySQL rolls back any uncommitted work automatically. Aarav's balance is restored.
Question 12
Hard
Explain write skew and whether REPEATABLE READ prevents it.
Two transactions each read a consistent snapshot and write different rows.
Write skew: two transactions each read a consistent view of the data, then each writes to a different row. Individually, each write is valid — but the combined effect breaks an invariant. Example: two on-call engineers. Rule: at least one must be on call. Each transaction checks 'I am the only one off-call, but there is still one other' and then both go off-call simultaneously. REPEATABLE READ does not prevent this because neither transaction modified a row the other read. Only SERIALIZABLE or explicit FOR UPDATE on the invariant rows prevents it.
Question 13
Hard
What is innodb_lock_wait_timeout and when does it matter?
Default 50 seconds.
The number of seconds a transaction will wait for a row lock before giving up with error 1205 (Lock wait timeout exceeded). Default is 50 seconds. For OLTP workloads, 50 seconds is far too long — it amounts to an outage from the user's perspective. Setting it to 3-5 seconds surfaces contention problems quickly and keeps p99 latency in check.
Question 14
Hard
Would setting isolation level to READ COMMITTED fix a deadlock caused by next-key locks on a range UPDATE?
READ COMMITTED uses only row locks, no gap locks.
Often yes. Under READ COMMITTED, InnoDB takes only row locks (not next-key / gap locks), so range UPDATEs lock fewer slots and deadlocks caused by concurrent inserts into the gap disappear. The trade-off: you give up phantom-read prevention, which can matter for some reports. Measure before flipping the switch.
Question 15
Hard
Two ways to recover from a stuck long-running transaction.
Kill or wait.
(1) SHOW PROCESSLIST to find the offending thread, then KILL <id>; to terminate it and roll it back. (2) Ride out the storm and alert on lock wait timeouts; the transaction eventually commits, rolls back, or the client disconnects. Option 1 is usually the right call in production.

Multiple Choice Questions

MCQ 1
What does ROLLBACK do?
  • A. Commits the transaction
  • B. Undoes all changes since START TRANSACTION
  • C. Restarts the server
  • D. Disables autocommit
Answer: B
B is correct. ROLLBACK returns the database to the state it was in when the transaction started.
MCQ 2
Which letter in ACID stands for 'committed data survives crashes'?
  • A. A
  • B. C
  • C. I
  • D. D
Answer: D
D is correct. D = Durability. Backed by the write-ahead redo log being flushed to disk at COMMIT time.
MCQ 3
Which is MySQL's default isolation level?
  • A. READ UNCOMMITTED
  • B. READ COMMITTED
  • C. REPEATABLE READ
  • D. SERIALIZABLE
Answer: C
C is correct. MySQL/InnoDB defaults to REPEATABLE READ, unlike PostgreSQL which defaults to READ COMMITTED.
MCQ 4
Which statement takes an exclusive lock on the matched rows?
  • A. SELECT * FROM t WHERE id = 1
  • B. SELECT * FROM t WHERE id = 1 LOCK IN SHARE MODE
  • C. SELECT * FROM t WHERE id = 1 FOR UPDATE
  • D. SELECT * FROM t WHERE id = 1 NO LOCK
Answer: C
C is correct. FOR UPDATE takes an X lock, blocking any other write or locking read until the transaction ends.
MCQ 5
What is autocommit = 1?
  • A. Every statement runs in its own transaction that commits immediately
  • B. Transactions never commit until the server restarts
  • C. The server rejects all writes
  • D. COMMIT is replaced by ROLLBACK
Answer: A
A is correct. autocommit = 1 is MySQL's default. Each standalone statement is its own mini-transaction. START TRANSACTION suspends autocommit until COMMIT or ROLLBACK.
MCQ 6
Which anomaly can still occur under READ COMMITTED?
  • A. Dirty read
  • B. Non-repeatable read
  • C. Both dirty and non-repeatable
  • D. None
Answer: B
B is correct. READ COMMITTED blocks dirty reads but still allows non-repeatable reads (a committed UPDATE from another transaction is visible on your next SELECT).
MCQ 7
What does SAVEPOINT enable?
  • A. Bypassing the redo log
  • B. Rolling back only part of a transaction
  • C. Committing part of a transaction
  • D. Pausing replication
Answer: B
B is correct. ROLLBACK TO SAVEPOINT name undoes changes since that savepoint without ending the transaction.
MCQ 8
What happens when InnoDB detects a deadlock?
  • A. The server restarts
  • B. One transaction is aborted with error 1213; the other proceeds
  • C. Both transactions are aborted
  • D. Deadlocks cannot occur in InnoDB
Answer: B
B is correct. The deadlock detector kills the transaction that has done less work. The winner finishes, the loser must retry.
MCQ 9
Which is NOT a way to reduce deadlocks?
  • A. Access rows in a consistent order
  • B. Keep transactions short
  • C. Add indexes to the WHERE columns
  • D. Remove all PRIMARY KEYs
Answer: D
D is correct. Removing primary keys hurts both performance and correctness. The other three are standard deadlock mitigations.
MCQ 10
Under MySQL/InnoDB REPEATABLE READ, can phantom reads occur on plain SELECTs?
  • A. Yes, always
  • B. No — MVCC snapshots prevent them
  • C. Only on temporary tables
  • D. Only if autocommit is off
Answer: B
B is correct. Plain SELECTs read the transaction-start snapshot, so a concurrent committed INSERT does not appear.
MCQ 11
What does DDL do to an open transaction in MySQL?
  • A. Nothing
  • B. Causes an implicit COMMIT — the transaction cannot roll back prior DDL
  • C. Pauses the transaction
  • D. Converts it to SERIALIZABLE
Answer: B
B is correct. CREATE, ALTER, DROP, TRUNCATE, and RENAME all cause implicit COMMIT. ROLLBACK after DDL cannot undo the DDL.
MCQ 12
Which lock does a plain UPDATE take on the rows it modifies?
  • A. Shared (S)
  • B. Exclusive (X)
  • C. No lock
  • D. Intention only
Answer: B
B is correct. UPDATE and DELETE always take X locks on the affected index rows.
MCQ 13
Under REPEATABLE READ, two concurrent withdrawals without FOR UPDATE can both see balance = 500 and both succeed. Why?
  • A. REPEATABLE READ is broken
  • B. MVCC makes reads lock-free, so both see the snapshot; UPDATE applies arithmetic on the current value, causing a lost update / overdraft
  • C. InnoDB automatically serialises UPDATEs to the same row
  • D. The balance column is cached in memory
Answer: B
B is correct. The classic 'lost update' pattern. FOR UPDATE is required to serialise the critical section.
MCQ 14
Error 1213 (40001) means:
  • A. Syntax error
  • B. Deadlock — transaction was chosen as the victim; retry
  • C. Connection lost
  • D. Disk full
Answer: B
B is correct. Applications must catch 1213 and retry. Exponential backoff reduces thundering-herd retry storms.
MCQ 15
Which isolation level is effectively achieved for plain reads by InnoDB REPEATABLE READ + MVCC?
  • A. READ UNCOMMITTED
  • B. READ COMMITTED
  • C. SERIALIZABLE-like (stable snapshot for the whole transaction)
  • D. None
Answer: C
C is correct. Plain reads under REPEATABLE READ see a consistent snapshot for the transaction, like SERIALIZABLE, but without the locking cost.
MCQ 16
Which is TRUE about innodb_lock_wait_timeout?
  • A. It triggers the deadlock detector
  • B. It controls how long a transaction waits for a single lock before giving up with error 1205
  • C. It forces COMMIT at the timeout
  • D. It is a read-only system variable
Answer: B
B is correct. Default 50 seconds; most OLTP shops reduce it to 3-5 seconds so contention surfaces quickly.
MCQ 17
Why call external APIs outside transactions?
  • A. MySQL forbids it
  • B. External calls inside transactions hold locks for the network round-trip duration, collapsing concurrency
  • C. It improves CPU cache performance
  • D. It is required for foreign keys
Answer: B
B is correct. Call external services before START TRANSACTION or after COMMIT (using an outbox if you need reliability).

Coding Challenges

Challenge 1: Atomic Transfer

Easy
Write a transaction that moves Rs. 1500 from account 101 to account 102. Ensure both UPDATEs succeed or neither does.
Sample Input
accounts: (101,'Aarav',5000), (102,'Priya',2000)
Sample Output
(101,3500), (102,3500)
Use START TRANSACTION and COMMIT.
START TRANSACTION;
    UPDATE accounts SET balance = balance - 1500 WHERE id = 101;
    UPDATE accounts SET balance = balance + 1500 WHERE id = 102;
COMMIT;

Challenge 2: Rollback on Insufficient Funds

Easy
Transfer Rs. X from account A to account B only if A has enough balance. Use FOR UPDATE and ROLLBACK if balance is insufficient.
Sample Input
accounts: (101,500), (102,1000). Transfer 700 from 101 to 102.
Sample Output
Transaction rolled back. Balances unchanged.
Use FOR UPDATE. Check balance. Use IF / ROLLBACK.
START TRANSACTION;
    SELECT balance INTO @bal FROM accounts WHERE id = 101 FOR UPDATE;
    IF @bal < 700 THEN
        ROLLBACK;
    ELSE
        UPDATE accounts SET balance = balance - 700 WHERE id = 101;
        UPDATE accounts SET balance = balance + 700 WHERE id = 102;
        COMMIT;
    END IF;

Challenge 3: Savepoint Retry

Easy
Insert an order, then insert items. If any item insert fails, roll back to the savepoint after the order insert and try a different item. Finally COMMIT.
Sample Input
Order total 1500, first try item_id=42 (fails), then item_id=99 (works).
Sample Output
orders has 1 row. order_items has 1 row with product_id=99.
Use SAVEPOINT / ROLLBACK TO SAVEPOINT.
START TRANSACTION;
    INSERT INTO orders(customer_id, total) VALUES (7, 1500);
    SET @oid = LAST_INSERT_ID();
    SAVEPOINT after_order;
    -- pretend the next insert fails; handle in app, then:
    ROLLBACK TO SAVEPOINT after_order;
    INSERT INTO order_items(order_id, product_id, qty) VALUES (@oid, 99, 1);
COMMIT;

Challenge 4: Prevent Double-Booking of a Seat

Medium
Two users try to book the same seat concurrently. Write the SQL (for one session) that correctly books the seat only if it is currently NULL.
Sample Input
seats: (1, 'AI-101', NULL). User 1001 tries to book.
Sample Output
(1, 'AI-101', 1001) after the transaction; a concurrent session sees the seat already booked and does not overwrite.
Use SELECT ... FOR UPDATE. Check NULL. UPDATE only if NULL.
START TRANSACTION;
    SELECT booked_by INTO @who FROM seats WHERE id = 1 FOR UPDATE;
    IF @who IS NULL THEN
        UPDATE seats SET booked_by = 1001 WHERE id = 1;
    END IF;
COMMIT;

Challenge 5: Simulate a Dirty Read

Medium
Using two MySQL sessions, demonstrate that READ UNCOMMITTED allows a dirty read. Provide the exact SQL for both sessions as comments.
Sample Input
accounts: (101, 5000). Session 2 UPDATEs without committing; Session 1 SELECTs.
Sample Output
Session 1 sees the uncommitted value; Session 2 rolls back; Session 1 acted on phantom data.
Set SESSION ISOLATION LEVEL READ UNCOMMITTED in Session 1.
-- Session 1
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
START TRANSACTION;
SELECT balance FROM accounts WHERE id = 101;  -- 5000
-- <wait>
SELECT balance FROM accounts WHERE id = 101;  -- 9999 (dirty)
COMMIT;

-- Session 2
START TRANSACTION;
UPDATE accounts SET balance = 9999 WHERE id = 101;
-- do NOT commit
ROLLBACK;
-- Session 1's second SELECT had seen 9999, a value that never officially existed.

Challenge 6: Isolation Level Proof

Medium
Show two sessions where Session 1 under REPEATABLE READ does NOT see Session 2's committed UPDATE. Provide the SQL.
Sample Input
accounts: (101, 5000).
Sample Output
Session 1's second SELECT still returns 5000 even though Session 2 committed an UPDATE to 6000.
Session 1 must START TRANSACTION under REPEATABLE READ before Session 2 updates.
-- Session 1
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;
SELECT balance FROM accounts WHERE id = 101;  -- 5000 (snapshot taken)
-- <wait for Session 2 to commit>
SELECT balance FROM accounts WHERE id = 101;  -- still 5000
COMMIT;

-- Session 2
START TRANSACTION;
UPDATE accounts SET balance = 6000 WHERE id = 101;
COMMIT;
-- Session 2's new value is invisible to Session 1 until Session 1 commits and starts a fresh transaction.

Challenge 7: Retry on Deadlock

Hard
Write pseudocode (or Python) that runs a transaction with up to 3 retries on error 1213. Use exponential backoff: 10ms, 20ms, 40ms.
Sample Input
Under contention, occasionally the transaction errors with 1213.
Sample Output
Transaction succeeds on a later attempt; on 3 failures, a RuntimeError is raised.
Must ROLLBACK on deadlock. Must sleep between retries.
import time
from pymysql.err import OperationalError  # or the equivalent in your driver

def run_with_retry(conn, body_fn, max_attempts=3):
    for attempt in range(max_attempts):
        try:
            conn.begin()
            body_fn(conn)
            conn.commit()
            return
        except OperationalError as e:
            if e.args and e.args[0] == 1213:
                conn.rollback()
                time.sleep(0.01 * (2 ** attempt))
                continue
            raise
    raise RuntimeError('Gave up after %d deadlock retries' % max_attempts)

Challenge 8: Consistent Lock Ordering

Hard
Rewrite this two-account transfer so that both transactions always lock the rows in the same order (lowest id first), preventing deadlocks.
Sample Input
-- Current naive version START TRANSACTION; UPDATE accounts SET balance = balance - amt WHERE id = from_id; UPDATE accounts SET balance = balance + amt WHERE id = to_id; COMMIT;
Sample Output
Both transactions always lock the smaller id first, preventing a deadlock cycle.
Use LEAST / GREATEST or an IF. Still debit/credit correctly.
-- Always SELECT ... FOR UPDATE both rows in (smaller id, larger id) order,
-- then apply the debit/credit based on which id is the source.
START TRANSACTION;
    SELECT id INTO @lo FROM accounts WHERE id = LEAST(:from_id, :to_id) FOR UPDATE;
    SELECT id INTO @hi FROM accounts WHERE id = GREATEST(:from_id, :to_id) FOR UPDATE;

    UPDATE accounts SET balance = balance - :amt WHERE id = :from_id;
    UPDATE accounts SET balance = balance + :amt WHERE id = :to_id;
COMMIT;

Need to Review the Concepts?

Go back to the detailed notes for this chapter.

Read Chapter Notes

Want to master SQL and databases with a mentor?

Explore our MySQL Masterclass