六维教程

Cloudflare Workers D1 数据库

KV 适合简单的键值存储,但遇到需要多字段关联查询、条件过滤、聚合统计的业务,就要用关系型数据库。这一篇学习 D1,基于 SQLite 的边缘数据库,并掌握 KV 加 D1 的缓存旁路模式。

D1 是什么

D1 是 Cloudflare 提供的关系型数据库,底层基于 SQLite,支持标准 SQL。它和 Worker 绑定在一起,代码里直接执行 SQL 语句,不需要单独部署数据库服务器。

KV 与 D1 对比

先弄清两者的定位差异

对比项 KV D1
数据结构 键值对 表、行、列
查询方式 按键名精确查找 SQL,支持条件、排序、聚合
一致性 最终一致 强一致(单主)
读取速度 极快,就近节点直接返回 相对慢,需要查数据库
适用场景 配置、缓存、简单数据 业务数据、复杂查询
单条限制 值最大 25MB 行数据视表结构而定

结论是,KV 当缓存和配置用,D1 当正式数据库用。

创建数据库

wrangler d1 create my-db

命令输出包含 database_id,形如

✅ Successfully created DB 'my-db'
┌───────────────┬──────────────────────────────────┐
│ database_id   │ a1b2c3d4e5f6a7b8c9d0e1f2         │
└───────────────┴──────────────────────────────────┘

绑定配置

在 wrangler.toml 中添加 D1 绑定

name = "my-worker"
main = "src/index.js"
compatibility_date = "2026-01-01"

[[d1_databases]]
binding = "DB"
database_name = "my-db"
database_id = "a1b2c3d4e5f6a7b8c9d0e1f2"

绑定后代码里通过 env.DB 操作数据库。

建表

先写一个 schema.sql 文件

CREATE TABLE IF NOT EXISTS users (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  name TEXT NOT NULL,
  age INTEGER
);

分别应用到本地和线上数据库

# 应用到本地(开发环境)
wrangler d1 execute my-db --local --file=./schema.sql

# 应用到线上
wrangler d1 execute my-db --remote --file=./schema.sql

之后每次改表结构都要同步执行这两条命令,保持本地和线上结构一致。

增删改查

D1 的 API 核心是 prepare 加 bind 加执行方法

export default {
  async fetch(request, env, ctx) {
    const url = new URL(request.url);
    const parts = url.pathname.split("/").filter(Boolean);
    const method = request.method;

    if (parts[0] === "api" && parts[1] === "users") {
      // 查询列表
      if (method === "GET" && !parts[2]) {
        const { results } = await env.DB.prepare(
          "SELECT * FROM users"
        ).all();
        return Response.json(results);
      }

      // 查询单个
      if (method === "GET" && parts[2]) {
        const user = await env.DB.prepare(
          "SELECT * FROM users WHERE id = ?"
        ).bind(parts[2]).first();
        if (!user) return Response.json({ error: "用户不存在" }, { status: 404 });
        return Response.json(user);
      }

      // 新增
      if (method === "POST") {
        const body = await request.json();
        const { meta } = await env.DB.prepare(
          "INSERT INTO users (name, age) VALUES (?, ?)"
        ).bind(body.name, body.age).run();
        return Response.json({ id: meta.last_row_id }, { status: 201 });
      }

      // 删除
      if (method === "DELETE" && parts[2]) {
        await env.DB.prepare(
          "DELETE FROM users WHERE id = ?"
        ).bind(parts[2]).run();
        return Response.json({ ok: true });
      }
    }

    return new Response("页面不存在", { status: 404 });
  },
};

三个执行方法的分工

方法 用途 典型语句
.all() 查询多行 SELECT
.first() 查询第一行 SELECT … LIMIT
.run() 执行写操作 INSERT、UPDATE、DELETE

查询结果的结构也要记住,.all() 返回 { results: [...] }.first() 直接返回一行对象,.run() 返回 { meta: { last_row_id } }last_row_id 是自增主键的新值。

参数化查询防注入

上面所有 SQL 都用了 ? 占位符加 .bind() 传参,这是必须养成的安全习惯。

// 正确,参数化
const user = await env.DB.prepare(
  "SELECT * FROM users WHERE id = ?"
).bind(id).first();

// 错误,拼接字符串有注入风险
const user = await env.DB.prepare(
  `SELECT * FROM users WHERE id = ${id}`
).first();

参数化查询由平台负责转义,能彻底防住 SQL 注入,永远不要用字符串拼接方式构造 SQL。

KV 加 D1 缓存旁路模式

D1 查询比 KV 慢,频繁查询的接口可以先用 KV 缓存结果,这就是缓存旁路(Cache Aside)模式,三条规则

时机 操作
读数据 先查 KV,命中直接返回,未命中查 D1 并写回 KV
写数据 只写 D1,同时删除对应 KV 缓存
缓存时间 给 KV 设置 TTL,防止数据长期不更新
const CACHE_TTL = 300; // 5 分钟

// 查询用户列表,带缓存
async function getUsers(env) {
  // 1. 先查 KV
  const cached = await env.MY_KV.get("cache:users");
  if (cached) {
    return { results: JSON.parse(cached), fromCache: true };
  }

  // 2. 未命中,查 D1
  const { results } = await env.DB.prepare("SELECT * FROM users").all();

  // 3. 写回 KV,带过期时间
  await env.MY_KV.put("cache:users", JSON.stringify(results), {
    expirationTtl: CACHE_TTL,
  });

  return { results, fromCache: false };
}

// 新增用户时删除缓存
async function createUser(env, body) {
  await env.DB.prepare("INSERT INTO users (name, age) VALUES (?, ?)")
    .bind(body.name, body.age).run();
  await env.MY_KV.delete("cache:users");
}

这个模式能显著降低 D1 的读取压力,是生产环境最常用的组合拳。注意缓存删不掉时会短暂读到旧数据,配合 TTL 兜底,业务上可接受即可。

免费额度

D1 免费计划提供 5GB 存储,每天 500 万行读取、10 万行写入。个人项目远用不完,但同样建议关注仪表盘用量。

到这里,数据层的能力已经齐了。下一篇学习定时任务,让 Worker 在固定时间自动执行。

上一篇 Cloudflare Workers KV 存储
下一篇 Cloudflare Workers 定时任务

上一篇
Cloudflare Workers KV 存储
下一篇
Cloudflare Workers 定时任务