23.2 BEGIN, COMMIT, and ROLLBACK: The Transaction Lifecycle
Right, let’s talk transactions. You don’t need me to tell you they’re the bedrock of any sane database system. Think of them as the “Undo” and “Redo” buttons for your database, but with far higher stakes and, thankfully, no paperclip assistant. At their core, transactions are about bundling a set of operations into a single, all-or-nothing unit of work. You either want all of it to happen, or none of it. There is no in-between. This is what we nerds call atomicity, and it’s the first letter in the hallowed ACID acronym. It’s the difference between transferring money successfully and having it vanish from your account only to never appear in mine—a situation we both want to avoid.
The lifecycle of a transaction is brutally simple. It’s born (BEGIN), it lives (does stuff), and then it either gets a glorious memorial erected in its honor (COMMIT) or it’s violently erased from history as if it never happened (ROLLBACK). No feelings, no take-backs.
The Sacred Trinity: BEGIN, COMMIT, ROLLBACK
Let’s make this concrete. Most databases operate in autocommit mode by default, where every single statement is its own tiny, implicit transaction. It’s like microwaving every ingredient of a meal individually instead of cooking the whole thing together. To actually bundle statements, you must explicitly start a transaction.
BEGIN; -- The starting pistol. Everything after this is tentative.
UPDATE accounts SET balance = balance - 100.00 WHERE user_id = 'alice';
UPDATE accounts SET balance = balance + 100.00 WHERE user_id = 'bob';
-- Now, pause. Look at the data. Is everything correct?
-- Alice's money is gone, Bob's has appeared. But it's not permanent yet.
-- This change is only visible inside our transaction. Bob, if he queries in another session, still sees his old, sad balance.
COMMIT; -- The point of no return. The changes are now permanent and visible to everyone.
The COMMIT is you saying, “Yes, I’ve checked my work, burn it into the stone tablets.” But what if you spotted a mistake right after the UPDATE? What if you subtracted 100 from Alice but then discovered Bob’s account is frozen? You don’t want half of that operation sticking around.
BEGIN;
UPDATE accounts SET balance = balance - 100.00 WHERE user_id = 'alice';
-- ...oops, application logic checks Bob's status and finds it's 'frozen'
ROLLBACK; -- The giant eraser. The update on Alice is completely undone. It's like this transaction never started.
The ROLLBACK is your emergency eject button. It’s why you can experiment fearlessly inside a transaction. This is the superpower. You can write complex, multi-step data changes and if anything goes wrong—a constraint violation, a logical error, your cat walks across the keyboard—you just ROLLBACK and the database pretends you never did a thing.
Autocommit: The Silent Default
Here’s the first “gotcha”: many clients have autocommit on by default. This means if you just start firing off UPDATE statements without a BEGIN, each one is being instantly committed. There’s no going back. This is fine for simple, single operations, but a terrifying way to live for anything complex.
You can usually turn it off in your session (SET autocommit = OFF;), which essentially implicitly starts a transaction for you after every COMMIT or ROLLBACK. I prefer explicit BEGIN statements. It makes the code’s intent clear and stops me from accidentally building a monstrously long transaction because I forgot to commit.
The Visibility Rule: What Can You See Inside a Transaction?
This is crucial and often misunderstood. Once you BEGIN a transaction, you are creating a snapshot of the database world. You can see all changes committed before your transaction started, plus all the changes you’ve made inside your own transaction.
But you cannot see changes made by other transactions that are still in progress or that were committed after your transaction started. This is called isolation (the ‘I’ in ACID), and it prevents you from seeing a partial, inconsistent state of the database from someone else’s half-finished work. Your transaction’s view of the data is frozen in time at the moment it began, plus your own doodles in the margin.
The Pit of Despair: Long-Lived Transactions
Just because you can hold a transaction open for hours while you pop out for lunch and ponder a tricky WHERE clause doesn’t mean you should. This is a fantastic way to anger your database and everyone else using it.
A long-running transaction:
- Prevents Vacuuming: In PostgreSQL, for instance, it blocks the
VACUUMprocess from cleaning up old row versions that your transaction might still need to see for a consistent view. This can lead to catastrophic table bloat. - Locks Resources: You might be holding locks on rows or tables, preventing other transactions from making progress. You become the database equivalent of that person who blocks the aisle in the grocery store.
- Increases Risk: The longer it’s open, the higher the chance your application crashes, the network drops, or something else happens, leaving a transaction hanging and requiring a DBA to come kill it manually.
The best practice is brutally simple: get in, do your work, and get out (commit or rollback) as quickly as possible. Do your thinking before you issue the BEGIN, not after.