大家好,我是程序员柏风,AI 最全的 Vibecoding 学习地图 http://coderoadmap.cn 作者。
D1 数据库
这一课你将能做到
- 创建 D1 数据库、设计表结构、执行迁移(Migration)
- 在 Worker 中用 SQL 完成增删改查,并处理关联查询
- 理解 D1 的「边缘读、中心写」架构,并能判断它和 KV、传统数据库的边界
为什么要学
KV 只能存字符串,没有关联查询;R2 是文件存储,没有结构化查询。如果你的应用需要「用户表」「订单表」「文章表」,需要 JOIN、WHERE、INDEX,那就需要一个真正的数据库。D1 就是 Cloudflare 给出的答案:边缘优先的 SQLite。
人话:D1 是把 SQLite 放到了 Cloudflare 的边缘网络里。你的 Worker 在北京运行,D1 会自动把数据库的只读副本放到北京节点,查询延迟 < 5ms;写入时会路由到中心节点(比如美国东部),保证强一致。
先建立图像
写入: [Worker] ── SQL INSERT ──> [D1 中心主库] (美东)
│
异步复制到只读副本
│
┌─────────────────────────────┼─────────────────────────────┐
▼ ▼ ▼
[北京只读副本] [纽约只读副本] [伦敦只读副本]
│ │ │
查询: 3ms 查询: 4ms 查询: 3ms
这就是 D1 的「读写分离」:读走边缘,写走中心。
跟着做
步骤 1:创建数据库
npx wrangler d1 create app-db
得到 database_id 后写入 wrangler.toml:
[[d1_databases]]
binding = "DB"
database_name = "app-db"
database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
步骤 2:创建表(迁移)
创建 migrations/0001_init.sql:
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
created_at INTEGER NOT NULL DEFAULT (unixepoch())
);
CREATE TABLE posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
title TEXT NOT NULL,
content TEXT NOT NULL,
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
FOREIGN KEY (user_id) REFERENCES users(id)
);
CREATE INDEX idx_posts_user_id ON posts(user_id);
执行迁移:
npx wrangler d1 migrations apply app-db --local # 本地
npx wrangler d1 migrations apply app-db --remote # 线上
步骤 3:Worker 中查询
export interface Env {
DB: D1Database;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
if (url.pathname === '/users' && request.method === 'GET') {
const { results } = await env.DB.prepare('SELECT * FROM users ORDER BY created_at DESC LIMIT 20').all();
return Response.json(results);
}
if (url.pathname === '/users' && request.method === 'POST') {
const { email, name } = await request.json();
const { success, meta } = await env.DB.prepare('INSERT INTO users (email, name) VALUES (?, ?)')
.bind(email, name)
.run();
if (!success) return new Response('Insert failed', { status: 500 });
return Response.json({ id: meta.last_row_id }, { status: 201 });
}
if (url.pathname.startsWith('/users/')) {
const id = url.pathname.split('/')[2];
const user = await env.DB.prepare('SELECT * FROM users WHERE id = ?').bind(id).first();
if (!user) return new Response('Not Found', { status: 404 });
const { results: posts } = await env.DB.prepare('SELECT * FROM posts WHERE user_id = ?').bind(id).all();
return Response.json({ user, posts });
}
return new Response('Not Found', { status: 404 });
},
} satisfies ExportedHandler<Env>;
步骤 4:事务(Batch)
D1 支持 batch() 执行多条语句,要么全部成功,要么全部失败:
const results = await env.DB.batch([
env.DB.prepare('INSERT INTO users (email, name) VALUES (?, ?)').bind('a@b.com', 'Alice'),
env.DB.prepare('INSERT INTO posts (user_id, title, content) VALUES (?, ?, ?)').bind(1, 'Hello', 'World'),
]);
步骤 5:本地开发
npx wrangler dev
本地 D1 会自动用 SQLite 文件模拟(.wrangler/state/v3/d1/)。你可以用 wrangler d1 execute 直接操作本地数据库:
npx wrangler d1 execute app-db --local --command "SELECT * FROM users"
讲清楚要点
- D1 是 SQLite,不是 MySQL/PostgreSQL:支持标准 SQL,但功能子集较小。没有存储过程、触发器、视图(部分支持)、全文搜索(FTS5 有实验性支持)。
- 读走边缘,写走中心:查询会自动路由到最近的只读副本;写入会路由到中心主库。这意味着写延迟比读高(几十到几百毫秒)。
- 没有连接池概念:D1 是 Serverless 数据库,Worker 直接通过 binding 访问,不需要管理连接。底层由 Cloudflare 自动处理连接复用。
- 大小有限制:免费套餐单库 500MB,付费套餐单库 10GB(可申请更大)。单条查询返回结果最大 50MB。
- 迁移工具是 wrangler d1 migrations:用 SQL 文件管理 schema 变更,按序号执行。线上和本地分开执行,确保环境一致。
你可能会懵的地方
-
D1 和 KV 怎么选? KV 适合「键值对、读多写少、全球缓存」;D1 适合「结构化数据、关联查询、事务」。用户配置用 KV,用户订单用 D1。
-
D1 支持 JOIN 吗? 支持。SQLite 的 JOIN、子查询、聚合函数都能用。
-
D1 有连接数限制吗? 没有传统意义上的连接数限制,但有并发查询数限制(免费套餐约 50 并发)。超过会排队或报错。
-
怎么备份 D1?
wrangler d1 export app-db --output backup.sql导出为 SQL 文件。也可以开启 Time Travel(付费功能)实现时间点恢复。 -
D1 能替代 MySQL 吗? 小规模、读多写少、边缘场景可以。大规模、高并发写、复杂事务、存储过程,还是 MySQL/PostgreSQL 更成熟。
错法对照
| 错法 | 问题 | 正解 |
|---|---|---|
在循环里单条 INSERT | 每条都产生一次写请求,延迟高、费用高 | 用 batch() 或 INSERT INTO ... VALUES (...), (...) |
用字符串拼接 SQL WHERE id = ${id} | SQL 注入风险 | 用 bind(id) 参数化查询 |
不建索引直接 WHERE user_id = ? | 全表扫描,慢 | CREATE INDEX idx ON posts(user_id) |
| 在 D1 里存大 JSON 或文件 | 单条 50MB 限制,性能差 | 大文件用 R2,D1 只存元数据 |
| 期望写入后立刻在边缘读到 | D1 是「写中心、读边缘」,有复制延迟 | 对强一致要求高的场景,用 Durable Objects |
卡点急救
- 迁移时报
table already exists:本地和线上数据库结构不一致。先wrangler d1 execute --local --command "DROP TABLE IF EXISTS users"清理,再重新迁移。 - 查询结果为空但表里有数据:检查是否连对了数据库(本地 vs 线上);检查
bind参数类型是否正确(SQLite 是动态类型,但bind时字符串和数字要匹配)。 env.DB is undefined:检查wrangler.toml的binding名;重启wrangler dev。
面试常问
-
D1 的架构特点是什么? 读写分离:写入路由到中心主库,读取路由到最近的边缘只读副本。这种设计牺牲写延迟换取读延迟,适合「读多写少」的边缘应用。
-
D1 和 PlanetScale / Neon 怎么比? D1 深度集成 Cloudflare Workers,边缘读延迟最低;PlanetScale/Neon 是传统 Serverless 数据库,功能更全(MySQL/PostgreSQL 兼容),但边缘读延迟高。如果已经在 Cloudflare 生态,D1 是首选;如果需要完整 SQL 功能,选 PlanetScale/Neon。
-
D1 的迁移工具怎么设计?
wrangler d1 migrations用序号 SQL 文件管理 schema 变更,支持本地和远程分开执行。生产环境建议配合 CI/CD,在部署前自动执行迁移。
自测三问
- D1 的写入和读取分别路由到哪里?为什么这样设计?
- 如果要在 D1 里实现「用户点赞文章」功能,需要几张表?怎么设计?
- D1 的
batch()和单条执行有什么区别?什么场景必须用batch()?
小结一句话
D1 是边缘优先的 SQLite 数据库:读走边缘节点、写走中心主库,适合结构化数据、关联查询和「读多写少」的边缘应用。
延伸
- D1 官方文档
- D1 Pricing & Limits
- 下一课:Queues 消息队列,学习异步任务与死信队列。