DynamoDB 中的 Query 與 Scan
Query 依 partition key 讀取單一的項目集合(可選擇性地以 sort key 縮小範圍);Scan 讀取整個表格並在之後篩選。它們在 API 中看起來相似,但計費 — 以及擴展方式 — 完全不同。
在 DynamoDB 中什麼時候應該使用 Query 而非 Scan?
只要你能指定所需的 partition,就使用 Query——它只讀取一個項目集合,且僅對相符的項目計費。只有在一次性匯出或極小的表格時才使用 Scan;它會讀取每一個項目,並在任何 FilterExpression 執行之前對整個表格計費。面對真實資料,Query 是更好的選擇。
- Query 是針對性的:你只為相符 partition 中的項目付費。
- Scan 是窮舉性的:你為讀取每個項目付費,然後用一個在讀取被計費之後才執行的
FilterExpression把大部分丟掉。
在任何具一定規模的表格上,帶 filter 的 Scan 正是那個經典的「為什麼我的帳單爆表、延遲還比 RDS 差」的陷阱。
並排比較
| Query | Scan | |
|---|---|---|
| 讀取 | 一個 partition(依 PK) | 表格中的每一個項目 |
| 計費容量 | partition 中相符的項目 | 整個表格,在篩選之前 |
FilterExpression | 在讀取後套用 — 仍為讀取計費 | 相同 — 篩選永遠不會降低成本 |
| 延遲 | 隨表格成長維持平穩 | 隨表格大小成長 |
| 分頁 | 1 MB/頁 → LastEvaluatedKey | 1 MB/頁;可平行化 |
| 適用於 | 已知的存取模式 | 一次性匯出、極小的設定表格 |
關鍵陷阱:在這兩種操作上,FilterExpression 都在 DynamoDB 計費讀取之後才執行。一個「傳回 10 列」的 Scan 可能為讀取一百萬列計費 — 篩選是一種便利,絕不是成本控制。
使用 Query
Query PK = "USER#42" AND SK begins_with "ORDER#"
如果你發現自己為了回答一個常見的存取模式而動手用 Scan,那是個建模訊號:新增一個 Global Secondary Index,讓該模式變成一個 Query。
抉擇歸結到一個問題 — 你能說出你需要哪個 partition 嗎?
鍵已知就 Query;若否,加個 GSI 讓它變成已知,只有在沒有任何鍵適用時才退而用 Scan。
Scan 何時沒問題
一次性匯出、極小的設定表格,以及刻意分頁掃過整個表格的背景工作。當你真的必須讀取所有內容時,使用 Segment/TotalSegments 將一個 Scan 分散到多個 worker(一次平行 scan)。
對 DynamoDB 反射性地下 SELECT * FROM table 是同一種反模式換上 PartiQL 的外衣 — 它會編譯成一個 Scan。當你真的需要跨項目的分析(一個 GROUP BY、一個 JOIN、一個聚合)時,DynoTable 的 SQL Workbench 會在一個有界的結果集上於用戶端執行它們,而非猛敲表格。
用容量計算機估算任一模式的讀取單位成本,並試用 DynoTable,對你自己的表格執行並檢查這些查詢。