D1 读副本和 Sessions API 怎么用?
开了 D1 读副本之后,最容易出的问题是:用户刚写完数据,下一次读落到一个滞后的副本,看不到自己的写入。
解法是 Sessions API:用 withSession() 发起读,并把 bookmark 在请求之间往返传递,就能得到“读到自己的写入”。 而且官方写明,要使用读副本必须走 Sessions API,否则查询仍留在主库。
下面讲读副本是什么、withSession() 的参数怎么选、代码长什么样,以及哪些地方本地看不出来。
读副本是什么?
D1 默认所有查询走主库,读副本需要显式开启,官方列出的方式是控制台设置或 REST API。开启后,D1 会在多个区域创建只读副本,通过 Sessions API 发起的读可就近服务,写仍然转发主库。
副本异步更新,所以会滞后。
Sessions API 怎么写?
export default {
async fetch(request: Request, env: { DB: D1Database }) {
const bookmark = request.headers.get("x-d1-bookmark") ?? "first-unconstrained";
const session = env.DB.withSession(bookmark);
const { results } = await session
.prepare("SELECT id, title FROM posts ORDER BY id DESC LIMIT 20")
.all();
const res = Response.json(results);
res.headers.set("x-d1-bookmark", session.getBookmark() ?? "");
return res;
},
};withSession() 的参数控制 session 第一次查询的去向:
first-unconstrained:默认,可去主库或任一副本;first-primary:强制主库,拿最新数据;- 上一次返回的 bookmark:保证至少和该版本一样新。
官方给出的保证是 session 内的顺序一致性:读不会回退,写按序,自己的写入后续可见。把 bookmark 通过 header 或 cookie 往返,就能把这个保证延伸到跨请求。
getBookmark() 返回什么?
按 string | null 处理最稳妥,上面的 ?? "" 就是这个原因。官方页面没有明确写出返回类型,而且官方示例代码自身有不一致(变量名、未定义的 response 等),以官方为准。
我在本地看到:session 还没有执行任何查询时返回 null,执行过查询后返回字符串。
本地能验证什么?
不多。本地的 served_by_primary 恒为 true,官方也说明 wrangler dev 下副本路由字段不具代表性。本地还会接受随便写的 bookmark 字符串,不会校验。
所以副本延迟、实际路由,以及 bookmark 的跨请求保证,本地都看不到,只能引官方的说法。
开启之前和之后要注意什么?
- 开启之前,先把读路径改成
withSession,并想清楚 bookmark 怎么在请求间传递; - Sessions API 目前只在 Worker 绑定里可用,REST API 不可用;
- 关闭读副本最长可能需要 24 小时才完全停止,Sessions 代码在关闭后仍可运行;
- 读副本没有额外费用,计费仍按 rows read / rows written;
- 读副本只改善读延迟和读吞吐,不加速写,写多的应用开了它收益有限。
如果写入才是你的瓶颈,读 什么时候该离开 D1。写入的事务怎么做,见 Cloudflare D1 怎么做事务?。
本文的 getBookmark() 在未执行查询时返回 null、本地副本路由字段恒为 primary、任意 bookmark 不被校验,是我在本地 wrangler/miniflare 上看到的。副本延迟、实际路由、bookmark 的跨请求保证、REST API 不可用、关闭时长和计费,都来自官方文档,我没有在线上验证。行为会变化,以 2026-10 的官方文档为准。