Cloudflare D1 超過每日限制怎麼辦?先分清列讀與列寫
如果 Workers Free 上的 D1 突然開始回傳「超過 daily row read limit」或「daily row write limit」,先不要重建資料庫,也不要一直重試同一支 Worker。從 2026 年 9 月 1 日起,Cloudflare 會在帳號超過 D1 Free 的每日列讀或列寫限制後,直接拒絕後續查詢;Workers Binding API 與 REST API 都受影響,直到每日限制在 00:00 UTC 重設。已儲存的資料不會因此被刪除。
這次變更真正需要調整的是用量觀測:你要先知道是讀取掃太多列,還是批次寫入太密集,再決定要改查詢、加索引、排程,或升級 Workers Paid。把所有錯誤都當成「D1 壞了」只會讓排錯更慢。
先用錯誤訊息判斷是哪一種上限
| 現象 | 代表什麼 | 先做什麼 |
|---|---|---|
free tier daily row read limit | 帳號當天掃描的列數已達 Free 上限 | 找出全表掃描、缺少索引或被重複呼叫的讀取查詢 |
free tier daily row write limit | 帳號當天寫入的列數已達 Free 上限 | 檢查匯入、重試、批次大小與重複寫入 |
| 沒有上述訊息,但查詢仍失敗 | 可能是 SQL、網路、資料庫大小或其他 D1 錯誤 | 記錄完整 error.message,不要只看 HTTP status |
Cloudflare 的 D1 錯誤清單已列出這兩個 Free tier 錯誤。超過限制後,合理的恢復方式是等到午夜 UTC,或升級到 Workers Paid;不需要因為查詢被拒絕就搬走或清空資料。
Free 方案目前怎麼計算列讀與列寫
目前 D1 pricing 頁列出的 Workers Free 額度是每日 500 萬列讀、每日 10 萬列寫,以及所有 D1 資料庫合計 5 GB 儲存空間。列讀不是「最後回傳給 Worker 的資料筆數」,而是資料庫為了找出結果實際掃描的列數;沒有索引的條件查詢,可能只回傳一筆,卻掃過整張表。
列寫則包含 INSERT、UPDATE 與 DELETE。交易或批次會把實際寫入的列數累計進去。若寫入有索引的欄位,更新表格與索引都可能計入寫入量;索引通常能大幅降低讀取掃描,但不應在完全不觀測的情況下大量建立索引。
另外,從 Cloudflare Dashboard 或 Wrangler 執行的查詢也會算進用量。清查用量時不要只看正式 Worker;本機腳本、資料匯入和臨時的 SELECT * 都可能是來源。
先看 meta,再看帳號總量
D1 每次查詢都會提供 meta.rows_read 與 meta.rows_written。如果使用 Workers Binding API,可以先把這些欄位記錄到不含敏感資料的結構化 log:
const result = await env.DB .prepare('SELECT id, title FROM posts WHERE author_id = ?1 ORDER BY created_at DESC LIMIT 20') .bind(authorId) .all()
console.log({ query: 'posts-by-author', rowsRead: result.meta?.rows_read, rowsWritten: result.meta?.rows_written,})這段是觀測範例,不會自動替你找出慢查詢。先把查詢以穩定名稱分類,再到 D1 Dashboard 的 Metrics > Row Metrics 對照資料庫與時間範圍。需要跨資料庫或長期報表時,也可以使用 D1 的 GraphQL Analytics API。
排查時至少保留三個欄位:查詢用途、rows_read/rows_written、發生時間。不要把完整 prompt、Cookie 或個人資料一起寫入 log;D1 用量分析不需要那些內容。
列讀超標:先處理掃描範圍
列讀超標通常不是某一筆結果太大,而是同一個條件被反覆掃描,或查詢沒有利用索引。可以按這個順序處理:
- 找出
rows_read明顯高於回傳列數的查詢。 - 對照
WHERE、JOIN、ORDER BY使用的欄位,確認是否缺少合適索引。 - 用 SQLite 的
EXPLAIN QUERY PLAN檢查查詢是否仍在做 full table scan。 - 把不需要的欄位從
SELECT *改成明確欄位,並限制一次工作的資料範圍。 - 檢查是否有前端重複請求、Worker retry 或排程重跑同一批資料。
例如,若應用程式經常用 author_id 和 created_at 篩選,可以在確認讀取模式後建立對應的複合索引:
EXPLAIN QUERY PLANSELECT id, titleFROM postsWHERE author_id = ?1ORDER BY created_at DESCLIMIT 20;
-- 請透過 migration 執行,不要在每次 request 中建立索引CREATE INDEX idx_posts_author_createdON posts (author_id, created_at DESC);LIMIT 只限制回傳結果,不應被當成降低掃描量的保證;真正要確認的是 query plan 和 meta.rows_read 是否下降。索引也不是越多越好:它會增加儲存空間,寫入索引欄位時還可能增加列寫,因此要用實際查詢驗證。
列寫超標:先找重複與批次邊界
Free 的每日 10 萬列寫對匯入、同步或事件收集流程很容易造成尖峰。先檢查:
- 是否因網路 timeout 重試,卻沒有用唯一鍵或冪等條件避免重複
INSERT。 - 是否把每一筆事件拆成多個不必要的更新。
- 是否把整批資料集中在同一分鐘執行,而不是分散到可接受的時間窗。
- 是否把測試資料匯入正式 D1,或由 Dashboard/Wrangler 另外執行過大量 SQL。
可行的修正通常是讓寫入具備冪等鍵、合併可以合併的更新,並把大批匯入拆成可重試的小批次。小批次不是繞過帳號額度;它的價值是讓你能觀測每一批、在接近上限時停止,而不是讓同一批資料無限重送。
遇到限制時的恢復順序
- 先保留完整錯誤訊息、資料庫名稱、查詢用途與當下時間。
- 暫停會持續重試的排程、佇列 consumer 或匯入工作,避免午夜重設後立刻再次撞滿。
- 到 D1 Metrics 對照
rows_read/rows_written,找出尖峰來源。 - 修正索引、查詢、冪等處理或批次排程後,再用小量資料驗證。
- 若工作量在優化後仍超過 Free 的產品需求,再評估 Workers Paid;不要把升級當成查詢未優化的替代品。
Free 額度是以 UTC 午夜重設,不是台灣時間每天 00:00。台灣時間的重設時間通常是早上 8 點,但若 Cloudflare 調整規則,仍應以錯誤訊息與官方文件為準。需要先確認 D1 binding、環境或本機資料庫邊界時,可以搭配 Workers Local Explorer 的檢查方式;它解決的是環境辨識,不會改變 D1 的帳號額度。
結論:先量出是哪種列,再決定要等、改或升級
D1 Free 超過每日限制後,失敗的是後續查詢,不是資料本身。把錯誤分成列讀與列寫,使用 meta 和 Row Metrics 找到真正的來源,再分別處理索引、掃描、冪等寫入與批次節奏。只有在這些調整仍不符合工作量時,升級 Workers Paid 才是有根據的容量決策。
常見問題
Q: 超過 D1 Free 每日列讀上限會刪除資料嗎?
A: 不會。Cloudflare 的公告說明,查詢會在限制期間回傳錯誤,但已儲存的資料不受影響。先停止重試並等待 UTC 重設,或升級到 Paid。
Q: 查詢只回傳一列,為什麼還會用掉很多列讀?
A: D1 依資料庫掃描的列數計算列讀,不是只算最後回傳的列數。未索引的篩選條件可能掃過整張表才找出一列;請用 meta.rows_read 和 query plan 驗證。
Q: 升級 Paid 後一定要修改查詢嗎?
A: 不一定要為了恢復服務立刻修改,但仍應修正高掃描或重複寫入的查詢。Paid 會移除 Free 的每日限制,卻不會讓低效率查詢變快,也不會阻止用量造成額外費用。
參考資料:
回報錯字、失效連結,或告訴我你想看的延伸主題。