Cloudflare D1 是 Cloudflare 推出的 Serverless SQLite 数据库服务:它运行在 Cloudflare 全球网络上,与 Workers 和 Pages Functions 原生集成,开发者通过简单的绑定即可在边缘函数中执行 SQL,写入发往主库、读取就近分发,且无需管理任何数据库服务器。
本文面向想在 Cloudflare 上构建全栈应用的开发者,覆盖 D1 的定位选型、快速上手、SQL 实战、Pages Functions 集成、性能优化与生产运维。对 Pages 平台本身不熟悉的读者,建议先阅读 Cloudflare Pages 完全指南。
D1 的定位:适合什么,不适合什么
D1 本质上是托管的 SQLite,这决定了它的能力边界。
适合的场景
- SaaS 应用的配置与元数据:租户信息、订阅状态、功能开关
- 用户系统:账号、会话、个人资料(配合 Better Auth 等认证方案)
- 轻量业务库:博客文章、留言、短链接映射、表单提交
- 读多写少的场景:内容型产品、仪表盘、内部工具
- 按租户分库的多租户架构:D1 支持创建上万个数据库,每个租户一个库的成本极低
不适合的场景
- 高频写入:大量并发写会成为瓶颈(SQLite 单主库写入模型)
- 海量数据:单库建议控制在 GB 级,不适合 TB 级数据仓库
- 复杂分析查询:OLAP 请用 ClickHouse 或 D1 + 数据导出到分析系统
快速上手:从建库到本地开发
创建数据库
# 创建数据库
npx wrangler d1 create my-guestbook
# 输出示例:
# ✅ Successfully created DB 'my-guestbook' in region APAC
# [[d1_databases]]
# binding = "DB"
# database_name = "my-guestbook"
# database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
绑定到 wrangler.toml
把输出中的绑定配置写入项目根目录的 wrangler.toml:
name = "my-guestbook-app"
compatibility_date = "2025-10-01"
pages_build_output_dir = "./public"
[[d1_databases]]
binding = "DB"
database_name = "my-guestbook"
database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
binding = "DB" 决定了你在代码中通过 context.env.DB 访问数据库,这个名字可以自定义(比如 DATABASE)。
本地开发:–local vs –remote
# 本地模式:读写本地 .wrangler/state 中的 SQLite 文件,速度快、离线可用
npx wrangler pages dev public --local
# 远程模式:直接操作云端真实数据库(小心污染线上数据!)
npx wrangler pages dev public --remote
日常开发用 --local;调试点对点的线上问题时才用 --remote。
Migration 管理
D1 使用文件化的迁移工作流。在项目下创建 migrations/ 目录:
migrations/
├── 0001_create_messages.sql
└── 0002_add_email_column.sql
第一个迁移文件 0001_create_messages.sql:
CREATE TABLE IF NOT EXISTS messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
content TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
应用迁移:
# 应用到本地开发库
npx wrangler d1 migrations apply my-guestbook --local
# 应用到远程生产库
npx wrangler d1 migrations apply my-guestbook --remote
# 查看迁移状态
npx wrangler d1 migrations list my-guestbook
SQL 实战:Prepared Statement 与批量操作
Pages Functions 中的 D1 客户端 API 围绕 prepared statement 设计。
参数绑定:永远不要拼接 SQL
// ❌ 错误:SQL 注入风险
const sql = `SELECT * FROM messages WHERE name = '${name}'`;
// ✅ 正确:参数绑定
const stmt = context.env.DB.prepare(
'SELECT * FROM messages WHERE name = ?'
).bind(name);
const { results } = await stmt.all();
常用方法一览:
.all():返回所有行({ results, meta }).first():返回第一行或 null.run():执行写入语句,返回{ meta: { changes, last_row_id } }.raw():返回原始数组格式的行
批量操作 batch()
插入多条数据时,用 batch() 一次往返完成:
const stmts = items.map((item) =>
context.env.DB.prepare(
'INSERT INTO messages (name, content) VALUES (?, ?)'
).bind(item.name, item.content)
);
await context.env.DB.batch(stmts);
batch() 保证这些语句在原子批内执行——这正是 D1 对「事务」的替代方案:D1 不支持跨多次往返的交互式事务,但 batch() 内的语句具备事务性。如果你的流程必须先读后写(例如扣库存),把读校验和写入放在同一批中,或在应用层做幂等设计。
Pages Functions 集成:目录结构与绑定访问
Pages Functions 本质上是 Workers 的轻量封装(Workers 完整能力见 Cloudflare Workers 入门实战)。
Pages Functions 通过约定式目录结构工作:
my-app/
├── public/ # 静态资源
│ └── index.html
├── functions/ # 后端函数
│ └── api/
│ └── messages.js
└── wrangler.toml
URL 路由直接映射文件路径:functions/api/messages.js 对应 /api/messages。函数文件使用现代格式导出按 HTTP 方法命名的处理器:
export async function onRequestGet(context) { /* GET 逻辑 */ }
export async function onRequestPost(context) { /* POST 逻辑 */ }
通过 context.env.DB 访问绑定的 D1 数据库。
完整示例:留言板 CRUD
后端 API:functions/api/messages.js
// GET /api/messages — 拉取最新 50 条留言
export async function onRequestGet(context) {
try {
const { results } = await context.env.DB.prepare(
'SELECT id, name, content, created_at FROM messages ORDER BY id DESC LIMIT 50'
).all();
return Response.json({ ok: true, data: results });
} catch (err) {
return Response.json({ ok: false, error: err.message }, { status: 500 });
}
}
// POST /api/messages — 提交新留言
export async function onRequestPost(context) {
let body;
try {
body = await context.request.json();
} catch {
return Response.json({ ok: false, error: '无效的 JSON' }, { status: 400 });
}
const name = String(body.name ?? '').trim().slice(0, 50);
const content = String(body.content ?? '').trim().slice(0, 1000);
if (!name || !content) {
return Response.json({ ok: false, error: '昵称和留言不能为空' }, { status: 400 });
}
const { meta } = await context.env.DB.prepare(
'INSERT INTO messages (name, content) VALUES (?, ?)'
).bind(name, content).run();
return Response.json({ ok: true, id: meta.last_row_id }, { status: 201 });
}
前端页面:public/index.html 核心片段
<form id="guestbook">
<input name="name" placeholder="昵称" required maxlength="50" />
<textarea name="content" placeholder="说点什么..." required></textarea>
<button type="submit">提交</button>
</form>
<ul id="messages"></ul>
<script>
async function loadMessages() {
const res = await fetch('/api/messages');
const { data } = await res.json();
document.getElementById('messages').innerHTML = data
.map((m) => `<li><b>${m.name}</b>:${m.content} <small>${m.created_at}</small></li>`)
.join('');
}
document.getElementById('guestbook').addEventListener('submit', async (e) => {
e.preventDefault();
const form = e.target;
await fetch('/api/messages', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: form.name.value,
content: form.content.value,
}),
});
form.reset();
loadMessages();
});
loadMessages();
</script>
端到端流程就此打通:浏览器 fetch → Pages Functions 路由 → D1 prepared statement → SQLite 执行 → JSON 响应。部署只需 npx wrangler pages deploy public。
查询性能与最佳实践
索引设计
SQLite 索引语法与常规数据库一致,D1 场景下尤其要为「每页必查」的条件建索引:
CREATE INDEX IF NOT EXISTS idx_messages_created_at ON messages (created_at DESC);
-- 多租户场景:复合索引
CREATE INDEX IF NOT EXISTS idx_tenant_status ON orders (tenant_id, status);
避免 N+1
循环中逐条查询是边缘函数的大忌——每次查询都是一次往返。用 IN 或 JOIN 合并:
// ❌ N+1:逐条查
for (const id of ids) {
await DB.prepare('SELECT * FROM items WHERE id = ?').bind(id).first();
}
// ✅ 一次查询
const placeholders = ids.map(() => '?').join(',');
const { results } = await DB.prepare(
`SELECT * FROM items WHERE id IN (${placeholders})`
).bind(...ids).all();
读写延迟特性
D1 的架构是「单主写入 + 边缘读副本」:
- 写入:无论函数在哪个边缘节点执行,写请求都要路由到主库区域,延迟取决于用户到主库的距离
- 读取:开启读副本(Read Replication)后,只读查询可以命中就近副本,全球读取延迟显著下降
因此 D1 应用的设计准则是:把写路径做短(单次写入、批量写入),把读路径交给副本放大。
备份与导出
wrangler d1 export
# 导出整库为 SQL 文件
npx wrangler d1 export my-guestbook --remote --output=backup.sql
# 只导出 schema 或数据
npx wrangler d1 export my-guestbook --remote --no-data --output=schema.sql
建议将导出加入 CI 定时任务,保存到 R2 或外部存储。
Time Travel 时间点恢复
D1 内置 Time Travel,可以将数据库恢复到过去任意时间点(免费版保留 7 天,付费版更长):
# 恢复到指定时间点(Unix 时间戳或 ISO 格式)
npx wrangler d1 time-travel restore my-guestbook --timestamp=2026-07-29T02:00:00Z
# 查询可恢复的时间范围
npx wrangler d1 time-travel info my-guestbook
误删数据、错误迁移都可以用 Time Travel 一键回滚,这是 D1 相对自建 SQLite 的核心运维优势。
生态工具:ORM 接入
手写 SQL 适合小项目,业务复杂后建议引入 ORM。
Drizzle ORM(推荐)
Drizzle 对 D1 有一等支持,类型安全、零运行时开销:
npm install drizzle-orm
import { drizzle } from 'drizzle-orm/d1';
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
import { desc } from 'drizzle-orm';
const messages = sqliteTable('messages', {
id: integer('id').primaryKey({ autoIncrement: true }),
name: text('name').notNull(),
content: text('content').notNull(),
});
export async function onRequestGet(context) {
const db = drizzle(context.env.DB);
const rows = await db.select().from(messages).orderBy(desc(messages.id)).limit(50);
return Response.json({ ok: true, data: rows });
}
配合 drizzle-kit 可以从 schema 自动生成迁移文件。生产级的 React + Hono + Drizzle + D1 完整架构可参考 React + Hono + Better Auth 全栈技术方案。
Prisma
Prisma 通过 driver adapter(@prisma/adapter-d1)支持 D1,适合已有 Prisma 技术栈的团队,但冷启动和包体积代价高于 Drizzle,新项目更推荐 Drizzle。
常见问题(FAQ)
D1 的免费额度是多少?
免费套餐每日包含 500 万次读、10 万次写,存储上限 5 GB。Workers Paid 套餐读 250 亿次/月起步,写入另有配额,超出按量计费。绝大多数中小项目在免费额度内即可运行。
D1 支持事务吗?
不支持跨多次往返的交互式事务(如 BEGIN ... COMMIT 分段执行),但 batch() 批内的多条语句是原子执行的。涉及先读后写的资金类逻辑,需要把校验与写入放进同一批,或在应用层实现幂等与对账。
D1 和 KV 该怎么选?
KV 是全球最终一致的键值存储,读极快但不支持 SQL、无事务、无关系查询;D1 是带强一致读的 SQLite,支持复杂查询。简单配置、Session、缓存选 KV;有结构化数据、需要 WHERE/JOIN/聚合选 D1。很多生产架构两者并用。
D1 能当生产数据库吗?
可以,但要看负载画像。读多写少的 SaaS、内容站、内部工具完全没问题,官方也提供 Time Travel、读副本等生产特性。高频写、跨库复杂事务、海量分析查询则不适合,这类场景应选传统托管数据库或专用 OLAP 系统。
D1 的数据存在哪里?
D1 数据库有一个主区域(创建时可选择就近落点),写入发生在主库;开启读副本后,数据会异步复制到边缘节点供就近读取。创建时通过 --location 参数指定主库位置,满足数据驻留合规需求。
相关阅读
- Cloudflare 详解:从 CDN 到全球边缘计算平台
- Cloudflare Pages 完全指南
- React + Hono + Better Auth 全栈技术方案:构建 Cloudflare 原生 SaaS 应用
- Better Auth 完整接入指南
- JAMstack 的 SaaS 设计架构与多租户模型
- Cloudflare 专题导航
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。