创建数据库
wrangler d1 create my-db # 输出: # database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" # 把下面这段 copy 到 wrangler.toml # wrangler.toml: # [[d1_databases]] # binding = "DB" # database_name = "my-db" # database_id = "xxxxxxxx-..."
迁移与 schema
用 D1 自带的 migrations 目录:
wrangler d1 migrations create my-db init # 生成 migrations/0001_init.sql,编辑它
-- migrations/0001_init.sql CREATE TABLE users ( id INTEGER PRIMARY KEY, email TEXT UNIQUE NOT NULL, name TEXT NOT NULL, created_at INTEGER NOT NULL DEFAULT (unixepoch()) ); CREATE INDEX idx_users_email ON users(email);
# 应用到本地 miniflare wrangler d1 migrations apply my-db --local # 应用到生产 wrangler d1 migrations apply my-db --remote
在 Worker 里查询
type Bindings = { DB: D1Database; }; app.get('/users/:id', async (c) => { const id = c.req.param('id'); const user = await c.env.DB .prepare('SELECT * FROM users WHERE id = ?') .bind(id) .first(); if (!user) return c.text('not found', 404); return c.json(user); });
D1 原生 API 方法:
| 方法 | 返回 | 适合 |
|---|---|---|
.first() | 单行 or null | 按 id 查一条 |
.all() | {results: [...]} | 列表查询 |
.run() | {success, meta} | INSERT / UPDATE / DELETE |
.raw() | 二维数组 | 大结果流式处理 |
批处理与事务
D1 的事务通过 batch——一次原子提交多条语句:
const [r1, r2] = await c.env.DB.batch([ c.env.DB.prepare('INSERT INTO users (email, name) VALUES (?, ?)').bind(email, name), c.env.DB.prepare('INSERT INTO audit (action) VALUES (?)').bind('user.create'), ]);
D1 没有传统 BEGIN/COMMIT
在同一个请求内交错多次查询和插入,中间不是事务——它们是独立的 RPC。想要原子性,把所有语句凑成一个
在同一个请求内交错多次查询和插入,中间不是事务——它们是独立的 RPC。想要原子性,把所有语句凑成一个
batch() 调用。
用 Drizzle ORM
原生 prepare 太裸。Drizzle 在 Workers 上有原生 D1 adapter,类型安全、迁移、studio 全齐:
npm i drizzle-orm npm i -D drizzle-kit
// src/schema.ts import { sqliteTable, integer, text } from 'drizzle-orm/sqlite-core'; export const users = sqliteTable('users', { id: integer('id').primaryKey({ autoIncrement: true }), email: text('email').notNull().unique(), name: text('name').notNull(), createdAt: integer('created_at').notNull(), });
// src/index.ts import { drizzle } from 'drizzle-orm/d1'; import { eq } from 'drizzle-orm'; import { users } from './schema'; app.get('/users/:id', async (c) => { const db = drizzle(c.env.DB); const row = await db.select().from(users) .where(eq(users.id, Number(c.req.param('id')))) .get(); return row ? c.json(row) : c.text('nope', 404); });
只读副本:Sessions API
D1 2025 加入了读副本,默认关闭,开启后 Cloudflare 在多区域自动保留副本。使用时需要 Sessions API 来管理"读自己写"一致性:
const session = c.env.DB.withSession('first-primary'); // 写会走主区域 await session.prepare('INSERT INTO users ...').run(); // 后续读在同一个 session 内能看到这次写 const r = await session.prepare('SELECT * FROM users').all(); // 把 bookmark 序列化传给下个请求,维持读自己写 const bookmark = session.getBookmark(); return c.json({ data: r.results, bookmark });
典型玩法:把 bookmark 塞进响应 header,前端下次带回来,后端 withSession(bookmark)——用户自己的写一定能看到,其他用户的写异步传播。
命令行:本地 dev 与手工查询
# 直接跑 SQL wrangler d1 execute my-db --local --command="SELECT COUNT(*) FROM users" # 导入 SQL 文件 wrangler d1 execute my-db --remote --file=./seed.sql # 导出整个 db wrangler d1 export my-db --output=backup.sql
容量与限制
| 维度 | Free | Paid |
|---|---|---|
| 单库大小 | 500MB | 10GB |
| 库数量 | 10 | 50000+ |
| 行读/天 | 500 万 | 250 亿 |
| 行写/天 | 10 万 | 5000 万 |
| 单次查询返回 | 最多 100MB | |
何时不要用 D1
高写并发
D1 写集中在主区域,几千 QPS 写入会瓶颈。考虑拆分多库(per-tenant 一库,D1 支持几万库),或者用 Postgres + Hyperdrive。
需要复杂分析查询
SQLite 没窗口函数外的一些分析能力,单库上限 10GB。数据仓库用 Analytics Engine 或外部 BigQuery。
强一致跨区
多副本最终一致,不保证每个 PoP 立刻看到最新写。要强一致跨区域,用 Durable Objects。
本章小结
- D1 = 边缘 SQLite,libSQL 底座,主-副本架构
- wrangler d1 create / migrations / execute 管整个生命周期
- prepare + bind 原生 API,或用 Drizzle 得类型安全
- 事务用 batch(),不要期待 BEGIN/COMMIT
- Sessions API + bookmark 解决读副本的"读自己写"
- D1 适合多读少写、单库 < 10GB 的业务