DynamoDB Transactions: TransactWriteItems & TransactGetItems
ACID transactions in DynamoDB — TransactWriteItems and TransactGetItems, condition checks, idempotency, costs and limits, with practical examples.
On this page
DynamoDB transactions give you all-or-nothing writes and consistent multi-item reads across one or more tables. They turn DynamoDB from a key-value store into something you can build real money-moving, invariant-enforcing logic on — with genuine ACID guarantees.
This guide covers the two transactional APIs, how condition checks and idempotency work, and the limits and costs you need to plan around.
The two transactional APIs
DynamoDB exposes exactly two transaction operations:
TransactWriteItems— a set of write actions that succeed or fail together. Each action is one ofPut,Update,Delete, orConditionCheck.TransactGetItems— a set ofGetactions that return a consistent snapshot, isolated from concurrent writes.
Both are atomic and isolated. If any single action in a TransactWriteItems fails (a condition isn’t met, a conflict occurs, capacity is exhausted), none of the writes are applied.
TransactWriteItems
Each call can include up to 100 action items totaling up to 4 MB, spanning multiple tables in the same account and Region. The same item cannot appear twice in one transaction.
{
"TransactItems": [
{
"Update": {
"TableName": "Accounts",
"Key": { "PK": { "S": "ACCT#alice" } },
"UpdateExpression": "SET balance = balance - :amt",
"ConditionExpression": "balance >= :amt",
"ExpressionAttributeValues": { ":amt": { "N": "100" } }
}
},
{
"Update": {
"TableName": "Accounts",
"Key": { "PK": { "S": "ACCT#bob" } },
"UpdateExpression": "SET balance = balance + :amt",
"ExpressionAttributeValues": { ":amt": { "N": "100" } }
}
}
]
}
This is the canonical money transfer. The ConditionExpression on Alice’s account guarantees the debit never overdraws; if it fails, Bob’s credit never happens either. See expressions for the full condition syntax.
ConditionCheck — guard without writing
A ConditionCheck action asserts something about an item you are not modifying. It’s how you enforce a cross-item invariant in the same atomic unit:
{
"ConditionCheck": {
"TableName": "Users",
"Key": { "PK": { "S": "USER#alice" } },
"ConditionExpression": "attribute_exists(PK) AND #status = :active",
"ExpressionAttributeNames": { "#status": "status" },
"ExpressionAttributeValues": { ":active": { "S": "ACTIVE" } }
}
}
Pair this with writes to other items — e.g. “only create this order if the user is still active.” If the check fails, the whole transaction aborts.
TransactGetItems
TransactGetItems reads up to 100 items (up to 4 MB) as an isolated snapshot. Unlike a BatchGetItem, it guarantees that no concurrent transactional write is partially reflected in your results — you see a point-in-time consistent view.
{
"TransactItems": [
{ "Get": { "TableName": "Accounts", "Key": { "PK": { "S": "ACCT#alice" } } } },
{ "Get": { "TableName": "Accounts", "Key": { "PK": { "S": "ACCT#bob" } } } }
]
}
Use it when you need to read several related items and must not observe a half-applied transaction. For independent reads where a torn view is acceptable, BatchGetItem is cheaper.
Idempotency with client request tokens
Network failures make retries unavoidable, and a blind retry of a transfer could double-charge. TransactWriteItems accepts a ClientRequestToken — an idempotency key. If you retry with the same token within roughly 10 minutes, DynamoDB recognizes the duplicate and returns the original result instead of re-applying the writes.
client.transact_write_items(
ClientRequestToken="transfer-7f3a-2026-06-16",
TransactItems=[...],
)
Generate the token once per logical operation (not per retry) and reuse it across attempts. This is the single most important safety feature for at-least-once delivery paths like SQS consumers or API retries.
How conflicts and isolation actually work
DynamoDB transactions are serializable with respect to other transactions, and use optimistic concurrency — there are no locks.
- A transactional write conflicts with another transaction touching the same item, raising
TransactionConflict. Retry with backoff. - A transactional operation is not isolated from a concurrent non-transactional write (a plain
PutItem/UpdateItem). The non-transactional write can land between the transaction’s read and commit phases. If isolation matters, route all writers through transactions. - Standard reads (
GetItem,Query) are not blocked by in-flight transactions and may briefly observe pre-commit state, since they follow normal consistency rules.
Limits and costs
| Aspect | Limit / cost |
|---|---|
Items per TransactWriteItems | 100 |
Items per TransactGetItems | 100 |
| Payload size | 4 MB |
| Same item twice in one transaction | Not allowed |
| Write capacity | 2× WCU vs a normal write |
| Read capacity | 2× RCU vs a normal read |
| Idempotency window | ~10 minutes |
The 2× cost is the price of the two-phase prepare/commit protocol DynamoDB runs under the hood. Budget for it: a transaction over 25 items costs as much as 50 individual writes.
Error handling
A failed TransactWriteItems returns a TransactionCanceledException whose CancellationReasons array tells you, per action, why it failed — ConditionalCheckFailed, TransactionConflict, ProvisionedThroughputExceeded, ThrottlingError, or ItemCollectionSizeLimitExceeded. Inspect the array to decide whether to retry (conflicts, throttling) or surface a business error (condition failures).
CancellationReasons: [
{ Code: "ConditionalCheckFailed", Message: "balance >= :amt" },
{ Code: "None" }
]
The first action’s condition failed; the second was never attempted.
When to use transactions
Reach for transactions only when you genuinely need atomicity or cross-item invariants:
- Transferring value between two items (the balance example).
- Enforcing uniqueness — write the entity and a
Putwithattribute_not_existson a unique-key item in one transaction. - Maintaining a denormalized counter or aggregate alongside the source item.
For everything else, prefer single-item conditional writes or batch operations, which are cheaper and have no conflict overhead. Transactions are a precision tool, not a default.
Inspecting transactional state
When you’re debugging a transaction that keeps failing, the bottleneck is usually seeing the live item state your condition expressions evaluate against. A GUI like Tablyne lets you read the actual attribute values and test condition expressions against real items before you ship the transaction, which is faster than guessing from CancellationReasons strings alone.
Transactions are powerful, but every one you add doubles capacity cost and introduces conflict-retry logic. Model your access patterns so that most writes stay single-item, and reserve transactions for the invariants that truly need them.
Frequently asked questions
Do DynamoDB transactions lock items?
No. DynamoDB uses optimistic concurrency, not locks. If two transactions touch the same item concurrently, one fails with a TransactionConflict and you retry — there's no held lock that can deadlock or block other readers.
How much do DynamoDB transactions cost?
Transactional reads and writes cost twice the capacity of their non-transactional equivalents. A transactional write of a 1 KB item consumes 2 WCUs instead of 1; a transactional strongly consistent read of a 4 KB item consumes 2 RCUs instead of 1.
Can a TransactWriteItems span multiple tables?
Yes. A single TransactWriteItems can target up to 100 items across one or more tables in the same AWS account and Region, all committed atomically.