{"post":{"slug":"savepoints-explained-2026","title":"Savepoints: Rolling Back Part of a Transaction","excerpt":"An error inside a transaction usually poisons the whole thing. A savepoint lets you undo just the failed part and continue, which is the only way to handle expected failures mid-transaction.","category":"code","date":"January 2, 2026","author":"ByteLedger Team","content":"<span class=\"dropcap\">Y</span>ou are inserting a batch of records inside one transaction. One violates a constraint. In several databases, that error puts the entire transaction into a failed state — every subsequent statement errors, and your only option is to roll back everything.\n\nThat is correct behaviour and unhelpful when the failure was expected and you wanted to skip that record and carry on.\n\n## What changed in 2026\n\n- **ORM savepoint usage became more visible.** Query logs showing savepoints developers did not write prompted better understanding of nested transaction handling.\n- **The overhead got documented.** Performance problems from savepoints created per iteration in loops became a recognised pattern.\n- **Bulk load patterns matured.** Batch-level rather than row-level savepoints became standard for large imports.\n- **Behaviour differences stayed engine-specific.** Whether an error aborts the whole transaction varies, which affects how necessary savepoints are.\n\n## What a savepoint does\n\nIt marks a point within a transaction that you can return to. Roll back to a savepoint and everything after it is undone; everything before it remains, and the transaction is still open and usable.\n\nThe pattern for handling an expected failure:\n\nSet a savepoint. Attempt the risky operation. If it succeeds, release the savepoint and continue. If it fails, roll back to the savepoint — which clears the error state — and continue with the next operation.\n\nWithout that, the first failure ends the transaction and you lose all the work that preceded it.\n\n| | Plain transaction | With savepoints |\n| --- | --- | --- |\n| Error effect | Whole transaction unusable | Roll back to the savepoint |\n| Partial progress kept | No | Yes |\n| Complexity | Low | Moderate |\n| Overhead | None | Per savepoint |\n\n## Why ORMs create them\n\nIf you have seen savepoints in query logs you did not write, this is why. Most databases do not support genuinely nested transactions — you cannot begin a transaction inside a transaction and commit the inner one independently.\n\nORMs simulate them with savepoints. An inner transaction block becomes a savepoint; committing it releases the savepoint; rolling it back rolls back to the savepoint. From the application's perspective it looks like nesting, and underneath it is one transaction with markers.\n\nThat is a reasonable abstraction with one important consequence: **the inner block is not durably committed**. If the outer transaction rolls back, the inner work is gone regardless of the inner commit. Code assuming an inner commit persisted the data is wrong, and the failure appears only when the outer transaction fails.\n\n## The overhead\n\nSavepoints are not free. Each one is tracked by the transaction, and creating a large number within one transaction consumes resources and can degrade performance meaningfully.\n\nThe failure mode is a loop that creates a savepoint per iteration — importing a million rows with a savepoint each. That is a million savepoints in one transaction, and it can slow the database in ways that look unrelated to the import.\n\nThe fix is granularity. Savepoint per *batch* rather than per row: process a thousand rows, and on failure roll back that batch and either retry it row by row or skip it. You lose the ability to skip exactly one bad row without extra work, and you avoid a pathological savepoint count.\n\nFor bulk loading, better still is to avoid savepoints entirely by validating before insert or loading into a staging table and moving valid rows across — see [COPY bulk loading](/blog/copy-bulk-loading-2026).\n\n## Common mistakes\n\n- **A savepoint per row in a large loop.** Serious overhead.\n- **Assuming an inner ORM transaction is durably committed.** It is not until the outer one commits.\n- **Using savepoints instead of validating first.** Cheaper to check than to fail and recover.\n- **Not releasing savepoints.** They accumulate for the transaction's life.\n- **Long transactions with many savepoints.** Compounds with the usual long-transaction problems, per [MVCC explained](/blog/mvcc-explained-2026).\n- **Assuming uniform engine behaviour.** Whether an error aborts everything differs.\n\n## FAQ\n\n**Do all databases abort the transaction on error?**\nNo — behaviour varies. Some allow you to continue after a statement error without a savepoint. Where the abort behaviour exists, savepoints are the only recovery mechanism.\n\n**How many savepoints are too many?**\nEngine-dependent, and the practical answer is that per-row savepoints in a large loop is too many. Batch granularity avoids the question.\n\n**Are nested transactions real anywhere?**\nGenuine independently-committable nested transactions are rare. What frameworks provide is savepoint emulation, with the durability caveat above.\n\n**Should I release savepoints I no longer need?**\nYes — it frees the associated resources. Frameworks generally handle this; hand-written savepoint code frequently does not.\n\n## Where to go next\n\nFor the transaction semantics underneath, read [ACID transactions explained](/blog/acid-transactions-explained-2026) and [database isolation levels](/blog/database-isolation-levels-2026). For bulk loading without savepoint overhead, [COPY bulk loading](/blog/copy-bulk-loading-2026).","hue":311,"metaTitle":"Savepoints: Rolling Back Part of a Transaction","metaDescription":"How savepoints let a transaction recover from an error without losing all its work, why ORMs use them for nested transactions, and their overhead.","keywords":["savepoints","nested transactions","partial rollback","transaction error recovery","subtransaction overhead","orm transaction handling","sql transactions","database patterns 2026"],"tldr":["**An error aborts the transaction in some engines** — subsequent statements fail until you roll back.","**A savepoint marks a point you can return to** — undo to there and continue.","**ORMs use them for nested transactions** — which is why they appear in queries you did not write.","**Each one has real overhead** — creating thousands in a loop is a performance problem.","**Skip:** savepoints for errors you can prevent by checking first. They are for genuinely expected failures."],"readTime":"4 min read","wordCount":805},"related":[{"slug":"distributed-transactions-microservices-2026","title":"Distributed Transactions in Microservices: 2026 Guide","excerpt":"Splitting a system into services means splitting its data too, so an atomic commit is no longer free. Here is how to choose between blocking coordination, compensation, and event-driven design.","category":"code","date":"July 31, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":304},{"slug":"transactions-explained-2026","title":"Database transactions explained in 2026: ACID, isolation, and beyond","excerpt":"Transactions are the safety net of every database write. This 2026 guide covers ACID, isolation levels, deadlocks, and the patterns that prevent data corruption.","category":"code","date":"April 21, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":310},{"slug":"what-is-a-database-transaction-2026","title":"What is a database transaction? The 2026 plain-English guide","excerpt":"A transaction groups several database operations into one unit that either all happen or none do. Here is the lifecycle, not the isolation-level deep dive.","category":"code","date":"July 17, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":290}],"sameCategory":[{"slug":"bazel-build-explained-2026","title":"Bazel Explained: What Hermetic Builds Actually Cost","excerpt":"Bazel delivers reproducible, precisely cached builds and demands that you describe everything. Here is what you get, what it costs, and who should say no.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":145},{"slug":"graphql-federation-2026","title":"GraphQL Federation: One Graph, Many Teams","excerpt":"Federation composes independently-owned subgraphs into a single API. Here is what it solves, the operational cost it adds, and when a simpler gateway is enough.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":300},{"slug":"hermetic-builds-explained-2026","title":"Hermetic Builds Explained: Same Inputs, Same Output","excerpt":"A hermetic build depends only on declared inputs. Here is what that property gives you, the common leaks that break it, and how to move toward it incrementally.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":130},{"slug":"monorepo-tooling-compared-2026","title":"Monorepo Tooling Compared: What You Actually Need","excerpt":"Monorepo tools solve task orchestration and caching, not repository layout. Here is what each tier provides and how to tell when you have outgrown the simple option.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":150}],"newer":{"slug":"rerankers-explained-2026","title":"Rerankers: Fixing the Order of What You Retrieved","excerpt":"Vector search returns roughly relevant results in roughly the right order. A reranker reads each candidate against the query properly and reorders them, which is a different and better operation.","category":"ai","date":"January 2, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":207},"older":{"slug":"spin-off-tax-treatment-2026","title":"Spin-Offs: What Happens to Your Basis When a Company Splits","excerpt":"A company you own spins off a division and shares in the new entity appear in your account. Usually no tax is due, and your original basis must be allocated between two holdings.","category":"finance","date":"January 2, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":287}}