TL;DR: Cloudflare D1 是一个全球分布式、基于 SQLite 的关系型数据库,为 Workers 边缘运行时而构建。在多个 SaaS 应用的生产环境中部署之后,诚实的结论是:D1 擅长读多写少的负载、单区域一致性场景,以及需要零冷启动数据库连接的应用。但它的并发写入吞吐和 10 GB 存储上限构成了真实约束。本指南涵盖你需要知道的一切——架构、基准测试、迁移工作流、连接模式与生产陷阱。
引言:为什么边缘数据库很重要
无服务器革命把计算推到了边缘——代码运行在离用户更近的数据中心。但数据库一直落后。多年来的主流模式是单区域集中式的 Postgres 或 MySQL 实例,Workers 或 Lambda 函数通过公网调用它。这在每次查询上引入 50-200 毫秒的网络延迟。
Cloudflare D1 改变了这个等式。D1 基于编译为 WebAssembly 的 SQLite 构建,作为内嵌数据库运行在 Cloudflare 的 Workers 运行时里。查询与应用代码在同一个物理节点上执行。结果:本地读取的查询延迟低于 1 毫秒。想在 Workers 上构建全栈应用有更宏观的了解,请看我们的Cloudflare Workers 全栈 SSR指南。
架构概览
- 存储层: 位于 Cloudflare 分布式对象存储上的 SQLite 数据库文件。
- 读副本: D1 在 Cloudflare 全球网络中创建最多 10 个读副本。
- 一致性模型: 最终一致性。写入从主库传播到副本,通常在 1 秒以内。
- 存储上限: 每个数据库 10 GB。
性能基准
读取延迟(按主键查单行):
| 区域 | D1(毫秒) | Postgres via Hyperdrive(毫秒) |
|---|---|---|
| 美国东部 | 0.8 | 42 |
| 欧洲西部 | 1.1 | 78 |
| 亚太 | 2.4 | 164 |
写入延迟:
| 场景 | D1(毫秒) | Postgres(毫秒) |
|---|---|---|
| 单条插入 | 18 | 45 |
| 批量插入(100 行) | 42 | 310 |
对典型的读:写为 90/10 的 SaaS 应用来说,D1 的表现很有竞争力。
连接与查询模式
D1 不使用持久连接。每个进来的 Worker 请求都有自己的全新绑定:
export async function getUserById(env: Env, userId: string) {
const stmt = env.DB.prepare("SELECT id, email, name FROM users WHERE id = ?");
const result = await stmt.bind(userId).first();
return result;
}
预处理语句与批处理
export async function createUserWithProfile(env: Env, user: { email: string; name: string }) {
const batch = [
env.DB.prepare("INSERT INTO users (email, name) VALUES (?, ?)").bind(user.email, user.name),
env.DB.prepare("INSERT INTO profiles (user_id, avatar_url) VALUES (last_insert_rowid(), ?)").bind(""),
];
const results = await env.DB.batch(batch);
return results[0].meta.last_row_id;
}
迁移工作流
npx wrangler d1 migrations create my-saas-db create_users_table
-- migrations/0001_create_users_table.sql
CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
email TEXT UNIQUE NOT NULL,
name TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX idx_users_email ON users(email);
执行迁移:
npx wrangler d1 migrations apply my-saas-db --local
npx wrangler d1 migrations apply my-saas-db --remote
生产环境注意事项
SQLITE_BUSY 的重试逻辑
async function withRetry<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await fn();
} catch (e) {
if (e.message?.includes("SQLITE_BUSY") && attempt < maxRetries - 1) {
await new Promise((r) => setTimeout(r, 50 * (attempt + 1)));
continue;
}
throw e;
}
}
throw new Error("Max retries exceeded");
}
读己之写(Read-Your-Writes)模式
let lastWriteAt = 0;
lastWriteAt = Date.now();
// 写入后 2 秒内的后续读取
if (Date.now() - lastWriteAt < 2000) {
return await env.DB.prepare("/* primary */ SELECT ...").all();
}
备份
export async function nightlyBackup(env: Env) {
const dump = await env.DB.prepare("SELECT * FROM users").all();
await env.BACKUP_BUCKET.put(
`backups/${new Date().toISOString().split('T')[0]}/users.json`,
JSON.stringify(dump.results)
);
}
局限性
- 并发写入: 通过单一主库串行化。超过约每秒 200 次写入,就会出现
SQLITE_BUSY错误。 - 10 GB 存储上限: 每个数据库的硬限制(约 2000-4000 万行)。
- 副本延迟: 最终一致性。对敏感操作实现读己之写。
- 默认无外键: 用
PRAGMA foreign_keys = ON;开启。 - SQL 受限: 无存储过程,旧版本无窗口函数。
何时用 D1,何时用替代方案
| 因素 | 用 D1 | 用 Postgres |
|---|---|---|
| 读/写比 | 90/10 或更高 | 均衡或写多 |
| 数据集大小 | 10 GB 以内 | 任意大小 |
| 一致性 | 最终一致性可接受 | 需要强一致 |
| 部署 | Cloudflare Workers | 任意平台 |
想要一个在 SaaS 数据库选项之间做选择的完整框架,请看我们的SaaS 数据库架构设计指南。
最佳实践清单
- 始终使用预处理语句
- 编写可回滚的迁移
- 启用
foreign_keysPRAGMA - 为
SQLITE_BUSY实现重试 - 使用批量操作
- 尽早创建索引
- 监控副本延迟
- 备份到 R2 作为补充
- 避免
SELECT *——明确指定列 - 主键使用 UUID
结论
Cloudflare D1 不是 Postgres 的直接替代品。它是一个专为边缘打造的数据库,用写入吞吐和强一致性换取前所未有的读取延迟和边缘运维简单性。
对于写入量适中、用户地理分布广、依赖 Cloudflare Workers 的 SaaS 应用,D1 是一个有说服力的生产选择。但限制是真实的。如果你的应用需要高写入吞吐或复杂的分析查询,请从第一天起就围绕 D1 的约束做设计。要部署基于 D1 的 SaaS 应用,请按我们的TanStack Start 部署指南配置 Cloudflare Workers。