CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
Cloudflare 实战入门/课时/D1 数据库
RawBlame
课时目录12 节

Cloudflare 实战入门

  1. 01边缘计算概览
  2. 02Wrangler 与本地开发
  3. 03Workers 核心
  4. 04Workers KV
  5. 05R2 对象存储
  6. 06D1 数据库
  7. 07Queues 消息队列
  8. 08Durable Objects
  9. 09Workers AI
  10. 10Stream 与媒体
  11. 11安全与防护
  12. 12运维与集成

D1 数据库

SQLite 边缘数据库。

30 分钟

D1 数据库

这一课你将能做到

  1. 创建 D1 数据库、设计表结构、执行迁移(Migration)
  2. 在 Worker 中用 SQL 完成增删改查,并处理关联查询
  3. 理解 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"

讲清楚要点

  1. D1 是 SQLite,不是 MySQL/PostgreSQL:支持标准 SQL,但功能子集较小。没有存储过程、触发器、视图(部分支持)、全文搜索(FTS5 有实验性支持)。
  2. 读走边缘,写走中心:查询会自动路由到最近的只读副本;写入会路由到中心主库。这意味着写延迟比读高(几十到几百毫秒)。
  3. 没有连接池概念:D1 是 Serverless 数据库,Worker 直接通过 binding 访问,不需要管理连接。底层由 Cloudflare 自动处理连接复用。
  4. 大小有限制:免费套餐单库 500MB,付费套餐单库 10GB(可申请更大)。单条查询返回结果最大 50MB。
  5. 迁移工具是 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

卡点急救

  1. 迁移时报 table already exists:本地和线上数据库结构不一致。先 wrangler d1 execute --local --command "DROP TABLE IF EXISTS users" 清理,再重新迁移。
  2. 查询结果为空但表里有数据:检查是否连对了数据库(本地 vs 线上);检查 bind 参数类型是否正确(SQLite 是动态类型,但 bind 时字符串和数字要匹配)。
  3. 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,在部署前自动执行迁移。

自测三问

  1. D1 的写入和读取分别路由到哪里?为什么这样设计?
  2. 如果要在 D1 里实现「用户点赞文章」功能,需要几张表?怎么设计?
  3. D1 的 batch() 和单条执行有什么区别?什么场景必须用 batch()?

小结一句话

D1 是边缘优先的 SQLite 数据库:读走边缘节点、写走中心主库,适合结构化数据、关联查询和「读多写少」的边缘应用。

延伸

  • D1 官方文档
  • D1 Pricing & Limits
  • 下一课:Queues 消息队列,学习异步任务与死信队列。
加载本节练习…
← 上一课R2 对象存储下一课 →Queues 消息队列