DynamoDB TransactWriteItems in Python (boto3)

Transaktionen sind eine der Stellen, an denen boto3s zwei APIs am härtesten auseinandergehen: transact_write_items gibt es nur auf dem Low-Level-Client, der Komfort nativer Python-Typen von Table fällt hier also weg. Und wenn die Transaktion scheitert, steht das, was du brauchst, in einer Ecke der Exception, in die der meiste boto3-Code nie schaut. (Was dir eine Transaktion bringt ist in jedem SDK gleich.)

Code

import boto3

client = boto3.client("dynamodb")

# Move one award between two songs — atomically. If the first song has no
# award to give, NEITHER update happens.
try:
    client.transact_write_items(
        TransactItems=[
            {
                "Update": {
                    "TableName": "Music",
                    "Key": {"Artist": {"S": "Arturo Sandoval"}, "SongTitle": {"S": "Cubano Chant"}},
                    "UpdateExpression": "SET #upd0 = #upd0 - :one",
                    "ConditionExpression": "#upd0 >= :one",
                    "ExpressionAttributeNames": {"#upd0": "Awards"},
                    "ExpressionAttributeValues": {":one": {"N": "1"}},
                }
            },
            {
                "Update": {
                    "TableName": "Music",
                    "Key": {"Artist": {"S": "Arturo Sandoval"}, "SongTitle": {"S": "A Mis Abuelos"}},
                    "UpdateExpression": "SET #upd0 = if_not_exists(#upd0, :zero) + :one",
                    "ExpressionAttributeNames": {"#upd0": "Awards"},
                    "ExpressionAttributeValues": {":one": {"N": "1"}, ":zero": {"N": "0"}},
                }
            },
        ]
    )
    print("Transaction committed")
except client.exceptions.TransactionCanceledException as e:
    # One reason per action, in TransactItems order. Code "None" means that
    # action was fine — some OTHER action sank the transaction.
    codes = [reason["Code"] for reason in e.response["CancellationReasons"]]
    print(f"Transaction canceled: {codes}")  # e.g. ['ConditionalCheckFailed', 'None']

Erklärung

  • TransactItems — eine Liste aus Put-, Update-, Delete- und ConditionCheck-Dicts, jeder Wert in DynamoDB JSON, ohne Ausnahme. Das ist der eine boto3-Aufruf, bei dem die typisierte Form nicht optional ist, und deshalb gibt es den Abschnitt am Ende dieser Seite. Die Obergrenzen stehen auf der CLI-Seite.
  • CancellationReasons steht nicht innerhalb von Error. botocore hebt modellierte Fehlerfelder an den Anfang des Response-Dicts, die gefangene Exception trägt also ein e.response mit den Keys CancellationReasons, Error, Message und ResponseMetadata nebeneinander. Wer es unter e.response["Error"] sucht, findet nichts, und e.response["Error"] enthält nur Code und Meldung der Zusammenfassung.
  • Kein "Message" auf den None-Einträgen — der Grund einer erfolgreichen Aktion ist das Ein-Key-Dict {"Code": "None"}, das naheliegende [r["Message"] for r in reasons] wirft also genau bei den Aktionen, die funktioniert haben, einen KeyError: 'Message'. Nimm r.get("Message").
  • Eine generierte Exception-Klasse — botocore baut client.exceptions.TransactionCanceledException zur Laufzeit aus dem Servicemodell, deshalb hängt sie an der Client-Instanz und deshalb kannst du sie nicht per from botocore.exceptions import ... holen. In einer Hilfsfunktion, die den Client nicht im Scope hat, fange botocore.exceptions.ClientError und verzweige über e.response["Error"]["Code"]; die generierte Klasse ist eine Unterklasse davon.
  • Strukturelle Fehler kommen nicht als Stornierungen an, die except-Klausel im Snippet sieht sie also nie. Zwei Aktionen auf dasselbe Item lösen einen nackten ClientError mit dem Code ValidationException aus, dessen e.response keinen CancellationReasons-Key hat, weil die Transaktion abgelehnt wurde, bevor irgendeine Aktion lief. Fange ClientError an der äußeren Kante, wenn du diese Fälle mit demselben Kontext protokollieren willst.
  • ReturnValuesOnConditionCheckFailure: "ALL_OLD" an einer Aktion legt das unterlegene Item unter einem Item-Key in den Grund dieser Aktion, in DynamoDB JSON — das spart dir das nachgelagerte get_item, nachdem du das Rennen bereits verloren hast.
  • boto3 füllt ClientRequestToken für dich. Auf der Leitung mitgeschnitten, verließen zwei identische transact_write_items-Aufrufe die Maschine mit zwei verschiedenen UUIDs — der Token deckt also einen einzelnen Aufruf ab und nicht deine eigene Catch-and-Retry-Schleife. Übergib selbst einen stabilen Token, wenn das Retry den Prozess überleben kann.
  • Wiederhole bei TransactionConflict, nie bei ConditionalCheckFailed — der erste sagt, dass jemand anders das Item einen Moment lang hielt; der zweite sagt, dass deine Vorbedingung falsch ist und es beim nächsten Mal auch sein wird. Das sind die einzigen zwei Codes, die die meisten Handler auseinanderhalten müssen, und der vollständige Satz ist auf der Seite zur TransactionCanceledException entschlüsselt.
  • Kosten — ein transaktionaler Write kostet etwa doppelt so viel wie derselbe Write außerhalb einer Transaktion, auf der CLI-Seite gemessen. Wenn du Atomarität nur auf einem einzelnen Item brauchst, bekommst du sie mit einem bedingten Write zum halben Preis.

Eine Resource-API-Variante davon gibt es nicht

boto3.resource("dynamodb").Table(...) hat kein transact_write_items-Attribut; nur resource.meta.client hat es. Eine Codebasis, die sich auf Table und native Python-Typen festgelegt hat, muss für ihre Transaktionen also auf typisiertes DynamoDB JSON zurückfallen oder von Hand mit boto3.dynamodb.types.TypeSerializer serialisieren:

from boto3.dynamodb.types import TypeSerializer

serialize = TypeSerializer().serialize
values = {k: serialize(v) for k, v in {":one": 1, ":zero": 0}.items()}

TypeSerializer wendet dieselben Regeln an wie die Resource-API, lehnt also float ab und erwartet für alles Gebrochene decimal.Decimal. Der DynamoDB-JSON-Konverter macht dieselbe Umwandlung im Browser, wenn du nur ein Literal in ein Skript einfügen willst. Um die Items, die eine Transaktion anfasst, zu bearbeiten, ohne eine der beiden Formen von Hand zu schreiben, lade DynoTable herunter.

Verwandte Beispiele

Referenzen

Zuletzt verifiziert am 2026-07-28 gegen die oben verlinkte offizielle AWS-Dokumentation.

Mit DynamoDB ohne die Console arbeiten

Ein schneller DynamoDB-Desktop-Client, der das echte SQL ausführt, das DynamoDB nicht kann — JOINs, GROUP BY, Aggregationen — mit visueller Bearbeitung und einem KI-Agenten auf deinen eigenen Bedrock-Schlüsseln.

30 Tage kostenlos testen, keine Kreditkarte — danach der Kostenlos-Tarif ohne Zeitlimit.