Update Expressions no DynamoDB
Uma update expression diz ao UpdateItem como mutar um único item: quais atributos
escrever, incrementar, deletar ou dobrar em um set. Não há UPDATE … SET … WHERE —
você nomeia o item pela sua chave e descreve a mudança com quatro palavras-chave de
cláusula.
Como funcionam as update expressions do DynamoDB?
Uma update expression do DynamoDB diz ao UpdateItem como mutar um item usando quatro cláusulas. SET escreve ou sobrescreve um atributo. ADD incrementa atomicamente um número ou faz a união em um set. REMOVE deleta um atributo ou um elemento de lista. DELETE remove membros específicos de um set. Uma única chamada pode conter as quatro ao mesmo tempo.
SETescreve ou sobrescreve um atributo — escalares, documentos, e os idiomas de funçãoif_not_existselist_append.ADDfaz um incremento numérico atômico ou uma união de set, em uma ida e volta, sem ler primeiro.REMOVEdeleta um atributo por completo (ou um único elemento de lista por índice).DELETEremove membros específicos de um set — e só de um set.
Vindo do SQL, a cilada é recorrer a SET para tudo. ADD e DELETE existem porque
read-modify-write em um contador ou um set é uma corrida que você vai perder sob
concorrência.
Escolha a cláusula pelo que você está mudando
Uma única chamada UpdateItem pode carregar todas as quatro cláusulas de uma vez, na
ordem SET … REMOVE … ADD … DELETE. Cada palavra-chave aparece no máximo uma vez e
recebe uma lista de ações separada por vírgulas.
| Cláusula | Funciona em | Use para |
|---|---|---|
SET | Qualquer atributo | Escrever/sobrescrever um valor ou campo de documento |
ADD | Só Number ou Set | Incrementar atomicamente, ou unir em um set |
REMOVE | Qualquer atributo ou elemento de lista | Deletar um atributo; remover um índice de lista |
DELETE | Só Set | Remover membros específicos de um set |
ADD em uma string e DELETE em um escalar são erros de validação, não no-ops — o
DynamoDB rejeita a chamada inteira. Conforme a
referência de update expression da AWS,
ADD é restrito a números e sets, e DELETE a sets.
O exemplo prático: um carrinho de compras
Um item por carrinho, chaveado por CartPK = "CART#c-9f21" e CartSK = "SUMMARY". Ele
acompanha um OrderTotal corrente, uma lista LineItems, um string set PromoCodes e
um ItemCount.
SET — escreva os escalares e documentos
SET sobrescreve o que estava lá. Adicione um item de linha à lista e aumente o total
na mesma chamada:
SET OrderTotal = :total,
LineItems = list_append(LineItems, :newItem),
UpdatedAt = :now
list_append(LineItems, :newItem) anexa ao final; inverta os argumentos —
list_append(:newItem, LineItems) — para prefixar. A ordem dos argumentos é a ordem da
concatenação, nada mais.
Há uma cilada naquela primeira chamada: se o carrinho é novinho, LineItems ainda não
existe, e list_append em um atributo ausente falha. Proteja com if_not_exists:
SET LineItems = list_append(if_not_exists(LineItems, :empty), :newItem)
if_not_exists(LineItems, :empty) retorna a lista atual se presente, senão o fallback
:empty (uma lista vazia []). Isso faz a primeira adição e toda adição posterior
usarem a mesma expressão — uma razão real para esses idiomas existirem.
ADD — incremente a contagem, atomicamente
Para aumentar ItemCount, não leia-o, some um no seu código, e faça SET de volta.
Isso é uma corrida de lost-update: duas adições concorrentes ambas leem 3, ambas
escrevem 4, e você perdeu uma. ADD faz a aritmética no servidor:
ADD ItemCount :one
Com :one = 1, isso é um contador atômico. Chamadas concorrentes serializam no item,
então duas adições caem como +2. Passe um número negativo para decrementar. Se
ItemCount estiver ausente, ADD o trata como 0 primeiro — então você nunca precisa
semear o contador.
Você pode construir esta expressão exata — nomes, valores tipados, e a requisição
marshalled — no
DynamoDB expression builder sem escapar à mão um
único placeholder #name ou :value.
REMOVE — remova um atributo ou um item de linha
REMOVE é como você deleta um atributo por completo (não há "defina como null" — isso só
escreve um tipo NULL). Limpe um desconto aplicado e remova o terceiro item de linha em
uma chamada:
REMOVE AppliedDiscount, LineItems[2]
LineItems[2] remove o elemento no índice 2 e desloca tudo depois dele para baixo —
o índice 3 vira 2, e assim por diante. Se você fizer REMOVE de dois índices em uma
expressão, ambos são avaliados contra a lista original, então remover [2] e [3]
juntos remove o terceiro e o quarto elementos como você esperaria.
DELETE — remova membros de set
PromoCodes é um string set, então um cliente puxando um código usa DELETE, não
REMOVE. REMOVE PromoCodes arrasaria o set inteiro; DELETE subtrai os membros
nomeados:
DELETE PromoCodes :pulled
Com :pulled = o set {"SAVE10"}, só aquele membro vai. Duas regras mordem aqui: um set
nunca pode ser vazio, então deletar o último membro remove o atributo PromoCodes por
completo; e o valor deve ser de um tipo set que corresponda ao atributo — uma string
solta é um erro de tipo.
Junte tudo
Um update "adicione item, aplique um promo, aumente a contagem" é uma chamada por três cláusulas:
SET LineItems = list_append(if_not_exists(LineItems, :empty), :newItem),
OrderTotal = OrderTotal + :price
ADD ItemCount :one
DELETE PromoCodes :expiredCode
Note OrderTotal = OrderTotal + :price — aritmética dentro de SET funciona sobre o
valor existente. Não é atômica do jeito que ADD é para segurança contra corridas, mas
lê o total atual no servidor em vez de fazer ida e volta dele pelo seu código.
Armadilhas a evitar
- Fazer
SETem um contador que você leu primeiro. UseADD— read-modify-write perde updates sob concorrência. Esse é o bug de carrinho/estoque mais comum. list_appendem uma lista ausente. Envolva o alvo emif_not_existsou a primeira escrita falha.- Confundir
REMOVEeDELETE.REMOVEremove o atributo;DELETEsubtrai membros de um set. Misturá-los deleta mais do que você pretendia. - Esquecer que
UpdateItemé um upsert. Se a chave não existe, ele cria o item. Use umaConditionExpression(attribute_exists(CartPK)) quando você quer dizer "atualize só".
Para modelar as chaves contra as quais essas expressões rodam, veja single-table design; para decidir como você vai ler o carrinho de volta, veja query vs scan.
Construa e copie qualquer uma dessas no expression builder, depois experimente o DynoTable para rodá-las contra suas próprias tabelas e ver o item mudar ao vivo.