DynamoDB Key Condition Expression
一個 key condition expression 是你傳給 Query 的
KeyConditionExpression — 請求中 DynamoDB 唯一用來 找出 item 的部分。其他
一切(過濾、投影)都在讀取已被計量之後才執行。
DynamoDB 中的 Key Condition Expression 是什麼?
Key condition expression 是 Query 上的 KeyConditionExpression,用來告訴 DynamoDB 要讀取哪些項目。 必須是相等比較(PK = :v); 則接受一個範圍運算子 — =、<、<=、>、>=、BETWEEN 或 begins_with。它決定什麼被讀取並計費,與篩選條件不同。
- partition key 必須是相等。
PK = :v,別無其他 — 沒有 範圍、沒有begins_with、沒有IN。DynamoDB 對它雜湊以定位一個 partition。 - sort key 接受一個範圍運算子。
=、<、<=、>、>=、BETWEEN, 或begins_with— 這是你切一個 item collection 的地方。 - 它不是過濾器。 key condition 決定什麼被 讀取 並計費;一個
FilterExpression只在你已為讀取付費後修剪結果。 - sort key 是位元組順序的。 範圍運算子做字典序比較,所以 你如何格式化 sort key 字串 就是 你的查詢能力。
為什麼 partition key 被鎖定為相等
DynamoDB 透過對 partition key 雜湊到一個實體 partition 來儲存 item。一個 雜湊給你一個位置,而非一個範圍 — 所以沒有東西可以 跨 掃描。
那就是為什麼 PK > :v 或 begins_with(PK, :v) 被直接拒絕。引擎
無法在不讀整張表的情況下回答「所有 key 以 X 開頭的 partition」,而那
正是它生來要避開的 Scan。
從 SQL 過來,這感覺很反:WHERE id LIKE 'order%' 在
Postgres 裡很瑣碎。在 DynamoDB 中,partition key 是一個位址,而非一個可
搜尋的欄位。
sort key 才是能力所在之處
在一個 partition 內,item 依 sort key 排序 儲存。那個排序 正是範圍運算子所利用的 — DynamoDB seek 到一個位置並向前讀。
| 運算子 | 讀取 | 用它來 |
|---|---|---|
SK = :v | 一個精確的 item | 依 key 取得一個特定子項 |
SK < / <= / > / >= :v | 一個開放端的切片 | 「這個點之後的一切」 |
SK BETWEEN :a AND :b | 一個封閉範圍(含端點) | 一個有邊界的窗口 — 一個日期範圍 |
begins_with(SK, :p) | 一個前綴切片 | PK 底下一個類型或階層 |
key 上沒有 LIKE、沒有 CONTAINS、沒有 ENDS_WITH。子字串與
後綴比對不是位元組順序的,所以它們會強制一次全讀 — 依設計,
API 不讓你這麼做。那些住在 FilterExpression 裡,你已經
付費的地方。(AWS:Key condition expressions)
演練範例:聊天 app 中的訊息
假設你在打造以頻道為基礎的聊天。一張表,依頻道分割, 依訊息時間排序。原始 key schema:
- partition key
ChannelRef—CH#{channelId} - sort key
PostedAt— 一個 ISO-8601 時間戳,MSG#2026-06-23T14:05:00Z
MSG# 前綴讓訊息列保持可排序,且與你可能在同一個頻道下
共置的任何其他列類型(釘選的設定、成員資格)區分開來。
載入一個頻道最新的訊息。 只用 partition key,最新在前:
KeyConditionExpression: ChannelRef = :ch
ExpressionAttributeValues: { ":ch": "CH#general" }
ScanIndexForward: falseScanIndexForward: false 反向走過排序好的 collection — 不在客戶端排序
就取得「最近的在前」的便宜方式。
用 begins_with 取某一天。 因為時間戳是 sort key 且
它以文字儲存,一個日期前綴就是一個乾淨的切片:
KeyConditionExpression ChannelRef = :ch AND begins_with(PostedAt, :day)
:ch "CH#general"
:day "MSG#2026-06-23"
那會讀取 2026-06-23 當天每一則訊息,別無其他 — DynamoDB seek 到 那個前綴,並在它掉出末端時停下。這之所以可行,僅因為那個前綴 是一個位元組排序字串的真正左錨點。
用 BETWEEN 取一個精確窗口。 對於「14:00 那個小時內的
訊息」,一個含端點的範圍勝過一個前綴:
KeyConditionExpression ChannelRef = :ch AND PostedAt BETWEEN :lo AND :hi
:ch "CH#general"
:lo "MSG#2026-06-23T14:00:00Z"
:hi "MSG#2026-06-23T14:59:59Z"
BETWEEN 在兩個邊界上都含端點,所以刻意挑你的端點 — 這裡的
off-by-one 會悄悄丟掉或重複一則邊緣訊息。
你可以在 DynamoDB expression builder 中組裝並複製這些
expression 之中的任何一個,並替你填好 ExpressionAttributeValues
map — 對第一次就把 begins_with 與 BETWEEN 語法弄對很方便。
在 DynoTable 中看它
對一個真實的頻道 partition 跑同一個 key condition,並即時看著 消耗的容量更新,這樣你就能確認你讀的是一個切片 — 而非 整個 collection。
陷阱:把 key condition 跟過濾搞混
那個昂貴的錯誤,是伸手去拿 FilterExpression 來做 key 的工作。
KeyConditionExpression: ChannelRef = :ch
FilterExpression: begins_with(PostedAt, :day)這 看起來 等同上面的 begins_with key condition,也回傳
相同的列 — 但它先讀 整個 頻道 partition,然後丟掉
那天以外的一切。你就整個讀取被計費。
過濾從不減少讀取成本。它們在 DynamoDB 計量了那些 item 之後才執行,
跟一個過濾的 Scan 是同一個地雷。如果一個述詞
能進 key condition,它就屬於那裡。
修正在上游:如果一個存取模式無法表達成一個 PK 相等 加一個 sort-key 範圍,那是一個建模訊號。要嘛重塑 sort key,要嘛 加一個為那個模式建 key 的 index — 如何擺放 key,請見 GSI vs LSI 與 single-table design。
陷阱與下一步
- partition key 永遠是
=。 永遠沒有範圍。如果你需要一個跨 partition 的範圍,你已經長出了單一Query之外的需求。 - 每個查詢一個 sort-key 條件。 你無法
AND兩個 sort-key 述詞; 挑BETWEEN或begins_with,而非兩者。 - 保留字需要別名。 一個叫
Timestamp或Name的 key 必須用ExpressionAttributeNames(#ts),否則查詢出錯。(AWS:reserved words) BETWEEN含端點。 兩個端點都會被比對 — 據此設計你的 邊界。
在 expression builder 中草擬你的 key condition,然後 試試 DynoTable 對你自己的表執行它們,並看著它們實際 消耗的容量。