Fortgeschritten5 Min. Lesezeit

DynamoDB Update Expressions

Eine Update Expression sagt UpdateItem, wie ein einzelnes Item zu mutieren ist: welche Attribute zu schreiben, zu inkrementieren, zu löschen oder in ein Set zu falten sind. Es gibt kein UPDATE … SET … WHERE — du benennst das Item über seinen Key und beschreibst die Änderung mit vier Klausel-Schlüsselwörtern.

Wie funktionieren DynamoDB Update Expressions?

Eine DynamoDB Update Expression sagt UpdateItem, wie ein Item mit vier Klauseln mutiert werden soll. SET schreibt oder überschreibt ein Attribut. ADD inkrementiert atomar eine Zahl oder vereinigt in ein Set. REMOVE löscht ein Attribut oder ein Listenelement. DELETE entfernt bestimmte Mitglieder aus einem Set. Ein Aufruf kann alle vier auf einmal tragen.

  • SET schreibt oder überschreibt ein Attribut — Skalare, Dokumente und die Funktions-Idiome if_not_exists und list_append.
  • ADD macht eine atomare Zahlen-Inkrementierung oder eine Set-Vereinigung, in einem Round Trip, ohne vorheriges Lesen.
  • REMOVE löscht ein Attribut komplett (oder ein einzelnes Listenelement per Index).
  • DELETE entfernt bestimmte Mitglieder aus einem Set — und nur aus einem Set.

Aus SQL kommend ist die Falle, für alles zu SET zu greifen. ADD und DELETE existieren, weil Read-modify-write auf einem Zähler oder einem Set ein Race ist, das du unter Nebenläufigkeit verlierst.

Wähle die Klausel nach dem, was du änderst

Ein UpdateItem-Aufruf kann alle vier Klauseln auf einmal tragen, in der Reihenfolge SET … REMOVE … ADD … DELETE. Jedes Schlüsselwort erscheint höchstens einmal und nimmt eine kommagetrennte Liste von Actions.

KlauselWirkt aufVerwende es, um
SETJedes AttributEinen Wert oder ein Dokumentfeld schreiben/überschreiben
ADDNur Zahl oder SetAtomar inkrementieren oder in ein Set vereinigen
REMOVEJedes Attribut oder ListenelementEin Attribut löschen; einen Listenindex droppen
DELETENur SetBestimmte Mitglieder aus einem Set entfernen

ADD auf einem String und DELETE auf einem Skalar sind Validierungsfehler, keine No-ops — DynamoDB lehnt den ganzen Aufruf ab. Laut der AWS-Update-Expression-Referenz ist ADD auf Zahlen und Sets beschränkt und DELETE auf Sets.

Das durchgespielte Beispiel: ein Warenkorb

Ein Item pro Warenkorb, gekeyt mit CartPK = "CART#c-9f21" und CartSK = "SUMMARY". Es verfolgt eine laufende OrderTotal, eine LineItems-Liste, ein PromoCodes-String-Set und einen ItemCount.

SET — schreib die Skalare und Dokumente

SET überschreibt, was immer da war. Füge ein Line-Item zur Liste hinzu und erhöhe die Summe im selben Aufruf:

SET OrderTotal = :total,
LineItems = list_append(LineItems, :newItem),
UpdatedAt = :now

list_append(LineItems, :newItem) hängt ans Ende an; dreh die Argumente um — list_append(:newItem, LineItems) — um voranzustellen. Die Argumentreihenfolge ist die Verkettungsreihenfolge, nicht mehr.

In diesem ersten Aufruf steckt eine Falle: wenn der Warenkorb brandneu ist, existiert LineItems noch nicht, und list_append auf einem fehlenden Attribut scheitert. Sichere es mit if_not_exists:

SET LineItems = list_append(if_not_exists(LineItems, :empty), :newItem)

if_not_exists(LineItems, :empty) gibt die aktuelle Liste zurück, falls vorhanden, sonst den Fallback :empty (eine leere Liste []). Das lässt das erste Hinzufügen und jedes spätere dieselbe Expression verwenden — ein echter Grund, warum diese Idiome existieren.

ADD — den Count atomar inkrementieren

Um ItemCount zu erhöhen, lies ihn nicht, addiere in deinem Code eins und SET ihn zurück. Das ist ein Lost-Update-Race: zwei gleichzeitige Adds lesen beide 3, schreiben beide 4, und du hast einen verloren. ADD macht die Arithmetik serverseitig:

ADD ItemCount :one

Mit :one = 1 ist das ein atomarer Zähler. Gleichzeitige Aufrufe serialisieren auf dem Item, sodass zwei Adds als +2 landen. Übergib eine negative Zahl zum Dekrementieren. Wenn ItemCount abwesend ist, behandelt ADD es zuerst als 0 — sodass du den Zähler nie seeden musst.

Du kannst diese exakte Expression — Namen, getypte Werte und die marshalled Request — im DynamoDB Expression Builder bauen, ohne einen einzigen #name- oder :value-Platzhalter von Hand zu escapen.

REMOVE — ein Attribut oder ein Line-Item droppen

REMOVE ist, wie du ein Attribut komplett löschst (es gibt kein "setz es auf null" — das schreibt nur einen NULL-Typ). Lösch einen angewandten Rabatt und drop das dritte Line-Item in einem Aufruf:

REMOVE AppliedDiscount, LineItems[2]

LineItems[2] entfernt das Element an Index 2 und schiebt alles danach nach unten — Index 3 wird 2, und so weiter. Wenn du zwei Indizes in einer Expression REMOVEst, werden beide gegen die ursprüngliche Liste ausgewertet, sodass das Entfernen von [2] und [3] zusammen das dritte und vierte Element droppt, wie du es erwarten würdest.

DELETE — Set-Mitglieder entfernen

PromoCodes ist ein String-Set, also verwendet ein Kunde, der einen Code zieht, DELETE, nicht REMOVE. REMOVE PromoCodes würde das ganze Set zerlegen; DELETE subtrahiert die genannten Mitglieder:

DELETE PromoCodes :pulled

Mit :pulled = dem Set {"SAVE10"} geht nur dieses Mitglied. Zwei Regeln beißen hier: ein Set kann nie leer sein, also entfernt das Löschen des letzten Mitglieds das PromoCodes-Attribut komplett; und der Wert muss ein Set-Typ sein, der dem Attribut entspricht — ein bloßer String ist ein Typfehler.

Setz es zusammen

Ein "Item hinzufügen, Promo anwenden, Count erhöhen"-Update ist ein Aufruf über drei Klauseln:

SET LineItems = list_append(if_not_exists(LineItems, :empty), :newItem),
OrderTotal = OrderTotal + :price
ADD ItemCount :one
DELETE PromoCodes :expiredCode

Beachte OrderTotal = OrderTotal + :price — Arithmetik innerhalb von SET wirkt auf dem bestehenden Wert. Sie ist nicht race-sicher atomar wie ADD, aber sie liest die aktuelle Summe serverseitig, statt sie durch deinen Code zu round-trippen.

Zu vermeidende Fallstricke

  • Einen Zähler SETen, den du zuerst liest. Verwende ADD — Read-modify-write verliert Updates unter Nebenläufigkeit. Das ist der häufigste Warenkorb-/Inventar-Bug.
  • list_append auf einer fehlenden Liste. Wickle das Ziel in if_not_exists, sonst scheitert der erste Write.
  • REMOVE und DELETE verwechseln. REMOVE droppt das Attribut; DELETE subtrahiert Mitglieder aus einem Set. Sie zu mischen löscht mehr, als du meintest.
  • Vergessen, dass UpdateItem ein Upsert ist. Wenn der Key nicht existiert, legt es das Item an. Verwende eine ConditionExpression (attribute_exists(CartPK)), wenn du "nur aktualisieren" meinst.

Zum Modellieren der Keys, gegen die diese Expressions laufen, siehe Single-Table Design; um zu entscheiden, wie du den Warenkorb zurückliest, siehe Query vs. Scan.

Baue und kopiere jede davon im Expression Builder, dann probier DynoTable, um sie gegen deine eigenen Tabellen auszuführen und zuzusehen, wie sich das Item live ändert.

Aktualisiert