---
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.
