DynamoDB 中的運算式屬性名與值
DynamoDB 運算式是模板:你寫預留位置,然後在兩個旁路對映裡提供真實的屬性名和值。#name 是一個名稱
預留位置;:value 是一個值預留位置。把這兩個搞混,DynamoDB 就拒絕整個呼叫。
DynamoDB 中 #name 和 :value 有什麼區別?
#name 是屬性名的預留位置,透過 ExpressionAttributeNames 提供;:value 是屬性值的預留位置,透過 ExpressionAttributeValues 提供。用 #name 來避開保留字、點或空格,用 :value 來表示每一個字面量——DynamoDB 從不把值內聯進運算式。它們不可互換;把兩者搞混會丟擲 ValidationException。
#name透過ExpressionAttributeNames替換一個屬性名——每當一個屬性與保留字衝突,或含有 點/空格時就用它。:value透過ExpressionAttributeValues替換一個值——DynamoDB 從不把字面量內聯進運算式文字, 所以每個值都是一個預留位置。- 它們不可互換。 把
#放到該放:的地方是一個ValidationException,而不是一個安靜的空操作。
從 SQL 過來,你兩者都內聯——WHERE status = 'published'。DynamoDB 兩者都不內聯。那個分離正是絆倒每個
新手的東西。
為什麼有這兩個對映
在 SQL 裡,查詢字串承載一切:列名、字面量、運算子。DynamoDB 刻意把運算式的形狀與它的資料分開。
值進它們自己的對映,於是 DynamoDB 能給每個值定型別(S、N、BOOL、…),而且解析器永遠不必猜一個
字串在哪裡結束——沒有引號或轉義可弄錯。參見 DynamoDB 中的資料型別
瞭解完整的型別標籤列表。
名稱得到同樣的待遇,但出於一個不同的原因:DynamoDB 有一長串保留字,任何匹配其中之一的屬性都不能 作為一個裸名出現在運算式裡。預留位置完全避開了這個保留。
保留字的坑
這是一張 CMS 文章表——分割區索引鍵 BLOG#<blog>,排序索引鍵 ARTICLE#<slug>——它的屬性讀起來很自然,卻碰巧與
保留字衝突:
| 屬性 | 保留? | 它持有什麼 |
|---|---|---|
status | 是 | draft / published |
name | 是 | 作者顯示名 |
size | 是 | 渲染後的位元組長度 |
ttl | 是 | 歸檔過期(紀元) |
slug | 否 | URL slug |
status、name、size 和 ttl 全在 AWS 的保留字列表上,所以這個過濾在第一個詞就失敗:
FilterExpression status = :s
DynamoDB 返回一個 ValidationException——"Attribute name is a reserved keyword; reserved
keyword: status"。修法是一個名稱預留位置,絕不是重新命名屬性:
FilterExpression #status = :s
ExpressionAttributeNames { "#status": "status" }
ExpressionAttributeValues { ":s": { "S": "published" } }
那個坑:slug 不保留,所以一個你針對 slug 測試過的查詢能用,於是你假設下一個也行。然後 status
破壞了它。完整列表在變動,所以別去背它——給每個名稱都加預留位置,你就永遠不會被咬。
永遠對映每個值
值是不容商量的:沒有內聯字面量的語法。哪怕一個普通數字也得一個預留位置。這次更新把一篇文章標記為已釋出、 蓋上它的大小,並設一個 30 天的歸檔 TTL:
UpdateExpression: SET #status = :s, #size = :sz, #ttl = :exp
ExpressionAttributeNames: { "#status": "status", "#size": "size", "#ttl": "ttl" }
ExpressionAttributeValues: {
":s": { "S": "published" },
":sz": { "N": "20480" },
":exp": { "N": "1719792000" }
}注意 :sz 和 :exp 作為 N 字串傳送——DynamoDB 的數字型別線上纜上被編碼為字串。值對映也是你跨子句
複用一個值的地方:定義 :s 一次,在一個 ConditionExpression 和一個 FilterExpression 裡都引用它。
手工構建這兩個對映正是打字錯誤藏身之處。Expression Builder 把運算式字串和兩個對映一起生成,型別標籤都已填好,所以預留位置無法彼此失同步。
下面這個構建器在 status——一個保留字——上做篩選,你可以看到它在 ExpressionAttributeNames 對映裡被自動別名為 #status:
用名稱處理巢狀和彆扭的路徑
# 預留位置的作用不止於避開保留字。文件路徑語法用點和方括號,所以一個字面包含一個點的屬性——比如一個
後設資料鍵 og.title——沒有預留位置就無法定址:
ProjectionExpression #og
ExpressionAttributeNames { "#og": "og.title" }
沒有它,DynamoDB 把 og.title 讀作"og 對映裡的 title 欄位"——一個完全不同的東西。帶空格或帶前導
數字的名稱是同樣的故事。對於巢狀,你給每一段都加預留位置:#meta.#author,並同時定義 #meta 和 #author。
名稱與值,並排對照
#name | :value | |
|---|---|---|
| 替換的是 | 一個屬性名 | 一個屬性值 |
| 對映 | ExpressionAttributeNames | ExpressionAttributeValues |
| 字首 | # | : |
| 何時需要 | 保留字、點、空格 | 始終——無內聯字面量 |
| 用錯了哪個會報錯 | ValidationException | ValidationException |
如果一個值被當成名稱來打型別,DynamoDB 會去找一個叫 published 的屬性,而你的條件永遠不會按你意指的
那樣匹配——所以 API 大聲失敗,而不是悄悄出錯。那種嚴格是一個特性:沒有安靜的錯誤答案。
陷阱與後續步驟
- 宣告一個你不用的預留位置——DynamoDB 拒絕任一對映裡未使用的條目。從運算式構建對映,而不是先於它。
- 編輯運算式後複用
:v——丟掉一個子句,它的值可能逗留,觸發未使用條目錯誤。構建器讓它們步調一致。 - 因為一個名稱用過一次就假設它安全——保留字衝突是按屬性來的。一律加預留位置,別再猜了。
這些對映出現在每條寫入路徑裡,所以它們自然地與 單表設計以及在你附加一個過濾之前懂得 何時 Query 與 Scan配對。
用 Expression Builder 生成運算式外加兩個對映,然後 試試 DynoTable 對你自己的表執行它們,看著預留位置被解析。