Topic 30

The Two-Seat Problem

Safety

Anna, customer 214 in the Marquee's database, wants two tickets for Friday's 8 p.m. showing of Night Bus: seats G7 and G8, side by side. It's a date night, and the whole point is the pair. One seat is not half a success; half a date night is worse than none.

In the database, that wish is two INSERTs into bookings — one row for G7, one for G8. And between those two statements sits a gap that nothing so far in this book protects. If the power dies in that gap, G7 is booked, G8 never happens, and no record anywhere says the two belonged together. The fix is called a transaction: a way to draw a bracket around several changes and make the bracket, not the statement, the unit that succeeds or fails.

The Gap Between Two Statements

Here is Anna's booking the way Chapter 5 would write it — two honest INSERTs, one after the other:

Two seats, two separate statements — the hazard version
INSERT INTO bookings (customer_id, screening_id, seat)
VALUES (214, 88, 'G7');

INSERT INTO bookings (customer_id, screening_id, seat)
VALUES (214, 88, 'G8');

Each statement on its own is protected. Chapter 5 made that promise: a single INSERT either fully happens or leaves the table untouched, and there is no such thing as a half-inserted row. That promise still holds. The problem is that it holds per statement, and Anna's intention spans two.

Walk the failure in slow motion. The first INSERT lands: G7 is Anna's. Then, before the second statement arrives, the power dies. When the database comes back, every rule still holds and every saved row is intact. It is perfectly consistent — and perfectly wrong: G7 booked, G8 free, and Anna's plan torn down the middle. The database never knew the two rows were one decision, because nothing told it.

BEGIN and COMMIT: Drawing the Bracket

Telling it is exactly what a transaction does. You open the bracket with BEGIN;, put the statements inside, and close it with COMMIT; — three ordinary words, and the gap is gone:

The same two seats, wrapped in one transaction
BEGIN;

INSERT INTO bookings (customer_id, screening_id, seat)
VALUES (214, 88, 'G7');

INSERT INTO bookings (customer_id, screening_id, seat)
VALUES (214, 88, 'G8');

COMMIT;

Inside the bracket, changes are provisional. The database performs them, but it promises nothing until COMMIT. If a crash lands between the two INSERTs now, the database notices the unfinished bracket when it restarts and un-does the partial work: G7's booking un-happens along with everything else since BEGIN. Anna gets both seats, or she gets a clean "sorry" and picks another night. The torn middle state has been removed from the list of possible outcomes.

Two bare INSERTs vs one transaction
Two bare INSERTs
G7 lands · power dies · G8 never happens — the pair is torn and nothing says it was a pair
One transaction
Both seats become real at COMMIT, or the whole bracket rolls back — the half-booked middle state cannot exist

You already use this pattern every time you shop online. The basket is BEGIN: you can fill it, change it, or abandon it, and nothing has happened yet. The confirm-order button is COMMIT: one press, and everything in the basket ships together — nobody is ever charged for half a basket. That is the whole idea mapped; from here on we'll use the real words.

ROLLBACK: the Escape Hatch

COMMIT is one way out of the bracket. The other is ROLLBACK;, which abandons it: everything since BEGIN un-happens, deliberately. And that turns the bracket into a seatbelt for your own mistakes. Remember Chapter 5's horror story — the UPDATE that forgot its WHERE and changed every row in the table? Inside a transaction, that tragedy becomes a near-miss:

The Chapter 5 horror story, this time wearing a seatbelt
BEGIN;

UPDATE bookings SET seat = 'H7';
-- no WHERE: every booking in the table just changed

SELECT COUNT(*) FROM bookings WHERE seat = 'H7';
-- the count is horrifying. Good thing the bracket is still open.

ROLLBACK;
-- everything since BEGIN un-happens; the table is exactly as it was

Read what happened there: the mistake ran, a quick SELECT revealed the damage, and ROLLBACK erased it as if it had never been typed. This is a professional habit worth copying from day one — wrap a risky change in a bracket, run it, look at the result, and only then choose COMMIT or ROLLBACK. It costs two extra lines and removes the sweaty part of changing data.

One boundary to be clear about, because it matters for the rest of the chapter: ROLLBACK only works while the bracket is open. The moment COMMIT runs, the changes are real, and no command un-happens them. What saves you after COMMIT is a different tool entirely, and it gets the last topic of this chapter to itself.

Autocommit, Honestly

One confession, so your mental model is complete. Every bare statement you have run since Chapter 3 was secretly a transaction too. Databases run in autocommit mode by default: each statement that arrives on its own gets an invisible BEGIN before it and an invisible COMMIT after it. That is why a single INSERT could never half-happen — you have been protected all along, one statement at a time.

So BEGIN doesn't switch on new machinery; the machinery was always on. What the bracket gives you is the choice of size: instead of a stream of tiny one-statement transactions, you decide which statements form one decision. Anna's pair of seats is one decision. Now the database knows it too.

Common Confusions
  • "Each statement already succeeds or fails on its own — isn't that enough?" True per statement, useless across statements. Anna's problem lives in the gap between the two INSERTs, and only the bracket covers the gap.
  • "ROLLBACK is an undo for anything, anytime." Only inside an open bracket. After COMMIT the changes are real, and the recovery story is a backup (Topic 33), not a command.
  • "Transactions are an advanced feature I'll opt into someday." Every banking app, shop, and booking site runs on them today. The bracket is the ordinary shape of real work, not an expert trick.
  • "BEGIN makes my statements behave differently." The statements are unchanged; only their grouping changes. The same INSERT runs the same way — it just waits for COMMIT to become permanent.
Why It Matters
  • "Together or not at all" is the shape of money transfers, orders, and sign-ups alike; half-done is the one outcome a business cannot explain to a customer.
  • ROLLBACK turns Chapter 5's horror stories into near-misses: run the risky change, look at what it did, and only then decide whether it really happened.
  • Knowing about autocommit explains why your single statements were always safe — and exactly where that safety ends.

Knowledge Check

Anna's two INSERTs are wrapped in BEGIN … COMMIT. What does the bracket guarantee?

  • Both bookings become real together, or neither does
  • Both INSERTs run faster because they are sent to the database as one batch
  • Seats G7 and G8 are permanently reserved for Anna the moment BEGIN runs
  • The database double-checks the seat numbers more carefully before inserting

When can ROLLBACK still save you?

  • Any time in the same session, even shortly after COMMIT has run
  • Between BEGIN and COMMIT, while the bracket is open
  • Only if the transaction contains nothing but INSERT statements
  • During the first few minutes after COMMIT, before the change is written to disk

What was autocommit doing all through the earlier chapters?

  • Saving the database file to disk after every keystroke you typed into a query
  • Nothing; transactions only exist after you type BEGIN for the first time
  • Wrapping each bare statement in its own invisible one-statement transaction
  • Collecting all your statements into one big transaction per session

The power dies after the first INSERT inside an open transaction. What does the database do when it restarts?

  • Keeps G7's booking and waits for someone to re-send the INSERT for G8
  • Keeps both changes, because each INSERT succeeded on its own before the crash
  • Asks the next user who logs in whether to keep the half-finished work
  • Un-does the partial work, so G7's booking disappears too

You got correct