Batch jobs are multiple statements pretending to be one change — debit here, credit there; delete-then-reload — and without a transaction the pretense fails exactly when statements do: a mid-batch error leaves the halfway state published, which is how a reload failure becomes an empty table in production. BEGIN/COMMIT makes the batch a unit; errors → ROLLBACK → the world never saw the attempt.
DELETE FROM ledger; SELECT COUNT(*) AS rows FROM ledger;
BEGIN; DELETE FROM ledger; ROLLBACK; SELECT COUNT(*) AS rows FROM ledger;
The bare delete-then-reload published a half state — a DELETE outside a transaction commits immediately, so a failed reload leaves the table empty; wrapping the batch in BEGIN ... ROLLBACK unhappens the attempt and the table keeps its pre-batch rows.
Transaction around any multi-statement invariant; delete-and-reload always inside one (or build-aside-and-swap); idempotency as the second seatbelt for the retry.
DDL-in-transaction support varies; some warehouses auto-commit per statement — know yours.