# BEGIN TRANSACTION — all-or-nothing blocks

Turn a script into one **all-or-nothing** native transaction. The editor validates every rule at the keystroke — where DynamoDB would report an opaque cancellation after the run, the studio catches violations before the wire. Write blocks use a deliberate **two-step run** (first Run previews the plan, an unchanged second Run commits) and carry an idempotency token, so a retry is a no-op rather than a double-apply.

## Syntax

```sql
BEGIN TRANSACTION;
statement;            -- writes only, or reads only — never mixed
statement;            -- each write targets ONE item by its full key
…                     -- up to 100 statements, each item at most once
COMMIT;
```

Extra `WHERE` conditions compile to an atomic `ConditionExpression`; arithmetic `SET` compiles to a native `UpdateExpression`; anything the native form can't express identically is an **honest refusal** naming the fix, never a silent drop. Clock functions share one instant across the block. Guide: [Writes & transactions](https://www.kanject.com/docs/dynostudio-partiql-writes/).

## Use cases

### Move money with a balance guard

Debit, credit, and ledger row land together or not at all — and the `balance >= 100` predicate becomes a native condition, so an uncovered debit cancels the whole block.

```sql
BEGIN TRANSACTION;
UPDATE "stage.Accounts" SET balance = balance - 100
  WHERE accountId = 'A1' AND balance >= 100;
UPDATE "stage.Accounts" SET balance = balance + 100
  WHERE accountId = 'B2';
INSERT INTO "stage.Ledger" VALUE {'ledgerId': 'TX#124', 'amount': 100};
COMMIT;
```

_Executes as:_ TransactWriteItems · [ UpdateItem ×2 · PutItem ×1 ] · ClientRequestToken set

- A cancellation reports **per-statement reasons** instead of DynamoDB's opaque error; the status line reports the billed capacity (`· 3 WCU`).

_Quote this example:_ https://www.kanject.com/docs/dynostudio-transactions/#guarded-transfer

### Create related items together

A new order plus its first event — two entities of a single-table design that must never exist half-made.

```sql
BEGIN TRANSACTION;
INSERT INTO "stage.AppData" VALUE {'pk': 'ORDER#7', 'sk': 'META',
  'status': 'open', 'CreatedAt': CURRENT_TIMESTAMP};
INSERT INTO "stage.AppData" VALUE {'pk': 'ORDER#7', 'sk': 'EVT#0001',
  'type': 'created', 'At': CURRENT_TIMESTAMP};
COMMIT;
```

_Executes as:_ TransactWriteItems · PutItem ×2 — both stamped with the same instant

- Clock functions render **one shared instant** across the block — the two `CreatedAt`s can't drift.

_Quote this example:_ https://www.kanject.com/docs/dynostudio-transactions/#atomic-entity-create

### Read two accounts from one consistent snapshot

Comparing balances mid-transfer lies unless both reads see the same instant — a read transaction serves every item from a single snapshot, which no sequence of individual reads can guarantee.

```sql
BEGIN TRANSACTION;
SELECT * FROM "stage.Accounts" WHERE accountId = 'A1';
SELECT * FROM "stage.Accounts" WHERE accountId = 'B2';
COMMIT;
```

_Executes as:_ TransactGetItems · 2 exact-key reads · one consistent snapshot

- Reads skip the two-step ritual (nothing to double-apply) and run fine against a **read-only stage**. Each SELECT must be an exact key lookup — no extra predicates, `LIMIT`, `ORDER BY`, or aggregates.

_Quote this example:_ https://www.kanject.com/docs/dynostudio-transactions/#snapshot-read

## Try it

**Try it · Transaction**

```sql
BEGIN TRANSACTION;
UPDATE "stage.Accounts" SET balance = balance - 100
  WHERE accountId = 'A1' AND balance >= 100;
UPDATE "stage.Accounts" SET balance = balance + 100
  WHERE accountId = 'B2';
COMMIT;
```

_Executes as:_ TransactWriteItems · 2 writes — All-or-nothing: if A1 can't cover the debit, neither write lands. Two-step run: first Run validates and previews; an unchanged second Run commits.

_Result:_ ✓ Committed · 2 writes applied atomically · 2 WCU · idempotency token retained

_Run it live in DynoStudio:_ https://www.kanject.com/dynostudio/

> **The block rules, up front:** Writes only or reads only — never mixed (DynamoDB's rule, caught in the editor). Each `UPDATE` / `DELETE` targets exactly **one item by its full key**, each item appears **at most once**, and the block caps at **100 statements**. `RETURNING` isn't available inside a transaction. A sweep across many unknown rows is a [set-based write](https://www.kanject.com/docs/dynostudio-update/#archive-sweep) — per-item, different guarantee.

Related: [Writes & transactions](https://www.kanject.com/docs/dynostudio-partiql-writes/) · [UPDATE](https://www.kanject.com/docs/dynostudio-update/) · [INSERT & upserts](https://www.kanject.com/docs/dynostudio-insert/) · [keyword reference](https://www.kanject.com/docs/dynostudio-partiql-keywords/).

---
_Source: https://www.kanject.com/docs/dynostudio-transactions/ · Kanject Docs_
