D1 数据库
SQLite 边缘数据库。
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>;