--- description: When to open a DB transaction — only when multiple statements must commit atomically, not around a single already-atomic write. paths: - "**/*.ts" - "**/*.js" - "**/*.java" - "**/*.kt" - "**/*.go" - "**/*.py" --- # Database transaction scope Open an explicit DB transaction only when **two or more statements must commit or roll back as a unit**. Wrapping a single, already-atomic write in a transaction adds a `BEGIN`/`COMMIT` round-trip and holds a pooled connection longer with no correctness benefit. ## Flag as `Minor` only when ALL of these hold For the transaction block in the diff: - it contains exactly **one** write (one `INSERT`/`UPDATE`/`DELETE`, or one ORM `create`/`update`/`save`/`destroy`), and - that write is self-contained: no read whose result it depends on in the same block, no second write, no explicit lock (`SELECT … FOR UPDATE`, advisory lock), and no coupled side effect that must roll back with it (outbox row, audit log, emitted event). A single SQL statement is already atomic **even when it touches many rows** (`UPDATE … WHERE`, bulk `DELETE`) — that does not need a transaction. ## Do NOT flag — a transaction is justified when - two or more writes must all succeed or all fail (across tables or rows); - read-modify-write needing isolation (`SELECT … FOR UPDATE` then `UPDATE`, check-then-insert, balance updates); - a write plus a side effect that must be atomic with it (outbox/event/audit row); - acquiring an advisory or row lock for coordination; - batched backfills committing per batch. ## Caveats (avoid false positives) - One ORM call can emit **multiple** SQL statements (cascade inserts/updates, model hooks, `bulkCreate`, relations). If the diff does not make it clear the block performs a single statement, do **not** flag. - If surrounding code or a comment hints at an intentional consistency or locking requirement you cannot fully see, do **not** flag. - This is a cleanliness/perf note, never a blocker.