中階閱讀時間 2 分鐘

DynamoDB Key Condition Expression

一個 key condition expression 是你傳給 QueryKeyConditionExpression — 請求中 DynamoDB 唯一用來 找出 item 的部分。其他 一切(過濾、投影)都在讀取已被計量之後才執行。

DynamoDB 中的 Key Condition Expression 是什麼?

Key condition expression 是 Query 上的 KeyConditionExpression,用來告訴 DynamoDB 要讀取哪些項目。 必須是相等比較(PK = :v); 則接受一個範圍運算子 — =<<=>>=BETWEENbegins_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 > :vbegins_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 ChannelRefCH#{channelId}
  • sort key PostedAt — 一個 ISO-8601 時間戳,MSG#2026-06-23T14:05:00Z

MSG# 前綴讓訊息列保持可排序,且與你可能在同一個頻道下 共置的任何其他列類型(釘選的設定、成員資格)區分開來。

載入一個頻道最新的訊息。 只用 partition key,最新在前:

KeyConditionExpression:  ChannelRef = :ch
ExpressionAttributeValues:  { ":ch": "CH#general" }
ScanIndexForward:  false

ScanIndexForward: 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_withBETWEEN 語法弄對很方便。

在 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 LSIsingle-table design

陷阱與下一步

  • partition key 永遠是 = 永遠沒有範圍。如果你需要一個跨 partition 的範圍,你已經長出了單一 Query 之外的需求。
  • 每個查詢一個 sort-key 條件。 你無法 AND 兩個 sort-key 述詞; 挑 BETWEEN begins_with,而非兩者。
  • 保留字需要別名。 一個叫 TimestampName 的 key 必須用 ExpressionAttributeNames#ts),否則查詢出錯。(AWS:reserved words
  • BETWEEN 含端點。 兩個端點都會被比對 — 據此設計你的 邊界。

expression builder 中草擬你的 key condition,然後 試試 DynoTable 對你自己的表執行它們,並看著它們實際 消耗的容量。

已更新