1942 字
10 分鐘

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 的資料筆數」,而是資料庫為了找出結果實際掃描的列數;沒有索引的條件查詢,可能只回傳一筆,卻掃過整張表。

列寫則包含 INSERTUPDATEDELETE。交易或批次會把實際寫入的列數累計進去。若寫入有索引的欄位,更新表格與索引都可能計入寫入量;索引通常能大幅降低讀取掃描,但不應在完全不觀測的情況下大量建立索引。

另外,從 Cloudflare Dashboard 或 Wrangler 執行的查詢也會算進用量。清查用量時不要只看正式 Worker;本機腳本、資料匯入和臨時的 SELECT * 都可能是來源。

先看 meta,再看帳號總量#

D1 每次查詢都會提供 meta.rows_readmeta.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_readrows_written、發生時間。不要把完整 prompt、Cookie 或個人資料一起寫入 log;D1 用量分析不需要那些內容。

列讀超標:先處理掃描範圍#

列讀超標通常不是某一筆結果太大,而是同一個條件被反覆掃描,或查詢沒有利用索引。可以按這個順序處理:

  1. 找出 rows_read 明顯高於回傳列數的查詢。
  2. 對照 WHEREJOINORDER BY 使用的欄位,確認是否缺少合適索引。
  3. 用 SQLite 的 EXPLAIN QUERY PLAN 檢查查詢是否仍在做 full table scan。
  4. 把不需要的欄位從 SELECT * 改成明確欄位,並限制一次工作的資料範圍。
  5. 檢查是否有前端重複請求、Worker retry 或排程重跑同一批資料。

例如,若應用程式經常用 author_idcreated_at 篩選,可以在確認讀取模式後建立對應的複合索引:

EXPLAIN QUERY PLAN
SELECT id, title
FROM posts
WHERE author_id = ?1
ORDER BY created_at DESC
LIMIT 20;
-- 請透過 migration 執行,不要在每次 request 中建立索引
CREATE INDEX idx_posts_author_created
ON posts (author_id, created_at DESC);

LIMIT 只限制回傳結果,不應被當成降低掃描量的保證;真正要確認的是 query plan 和 meta.rows_read 是否下降。索引也不是越多越好:它會增加儲存空間,寫入索引欄位時還可能增加列寫,因此要用實際查詢驗證。

列寫超標:先找重複與批次邊界#

Free 的每日 10 萬列寫對匯入、同步或事件收集流程很容易造成尖峰。先檢查:

  • 是否因網路 timeout 重試,卻沒有用唯一鍵或冪等條件避免重複 INSERT
  • 是否把每一筆事件拆成多個不必要的更新。
  • 是否把整批資料集中在同一分鐘執行,而不是分散到可接受的時間窗。
  • 是否把測試資料匯入正式 D1,或由 Dashboard/Wrangler 另外執行過大量 SQL。

可行的修正通常是讓寫入具備冪等鍵、合併可以合併的更新,並把大批匯入拆成可重試的小批次。小批次不是繞過帳號額度;它的價值是讓你能觀測每一批、在接近上限時停止,而不是讓同一批資料無限重送。

遇到限制時的恢復順序#

  1. 先保留完整錯誤訊息、資料庫名稱、查詢用途與當下時間。
  2. 暫停會持續重試的排程、佇列 consumer 或匯入工作,避免午夜重設後立刻再次撞滿。
  3. 到 D1 Metrics 對照 rows_readrows_written,找出尖峰來源。
  4. 修正索引、查詢、冪等處理或批次排程後,再用小量資料驗證。
  5. 若工作量在優化後仍超過 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 的每日限制,卻不會讓低效率查詢變快,也不會阻止用量造成額外費用。

參考資料:

D1 enforces free tier daily query limits

D1 Pricing

Debug D1

Cloudflare D1 超過每日限制怎麼辦?先分清列讀與列寫
https://laplusda.com/posts/cloudflare-d1-free-tier-query-limits/
作者
Zero
發佈於
2026-09-02
許可協議
CC BY-NC-SA 4.0
這篇文章有幫助嗎?

回報錯字、失效連結,或告訴我你想看的延伸主題。