最近,Cloudflare 推出了 D1 数据库的正式版。这是一个基于 SQLite 的边缘数据库,可以直接在 Cloudflare Workers 中使用,不需要自己搭建数据库服务器。

本文介绍 D1 的基本用法。

一、D1 是什么?

D1 是 Cloudflare 提供的 Serverless 数据库,底层是 SQLite。

它的核心特点:

  • 不需要管理服务器:数据库运行在 Cloudflare 的边缘网络上,你不用关心部署、备份、扩容。
  • 全球分布:数据自动复制到离用户最近的节点,读取延迟极低。
  • SQLite 语法:你会写 SQL 就能用,没有学习成本。
  • 免费额度:5GB 存储,每天 500 万次读取、10 万次写入,个人项目足够了。

简单说,D1 就是把 SQLite 搬到了云端,让你在 Worker 里直接读写数据库。

二、准备工作

你需要:

  1. 一个 Cloudflare 账号
  2. 安装 Node.js(18+)
  3. 安装 Wrangler CLI
npm install -g wrangler

登录 Cloudflare:

wrangler login

浏览器会弹出授权页面,点确认即可。

三、创建数据库

一条命令就够了:

wrangler d1 create my-db

输出类似这样:

✅ Successfully created DB 'my-db'

[[d1_databases]]
binding = "MY_DB"
database_name = "my-db"
database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

记住这里的 database_id,后面要用。

四、配置项目

新建一个项目目录,创建 wrangler.toml 文件:

name = "my-d1-app"
main = "src/index.js"
compatibility_date = "2024-12-01"

[[d1_databases]]
binding = "MY_DB"
database_name = "my-db"
database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

注意,binding 的值是 "MY_DB",后面代码里会通过 env.MY_DB 访问数据库。这个值可以自定义,但必须和代码里一致。

五、建表

创建一个 SQL 文件 schema.sql:

CREATE TABLE IF NOT EXISTS todos (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    title TEXT NOT NULL,
    completed INTEGER NOT NULL DEFAULT 0,
    created_at INTEGER NOT NULL DEFAULT (unixepoch())
);

执行建表:

wrangler d1 execute my-db --remote --file=schema.sql

--remote 表示操作远程数据库。如果省略,操作的是本地数据库。

六、插入数据

同样用 SQL 文件,避免中文乱码问题。

创建 seed.sql(文件编码必须是 UTF-8):

INSERT INTO todos (title) VALUES ('学习 Cloudflare Workers');
INSERT INTO todos (title) VALUES ('写一篇 D1 教程');
INSERT INTO todos (title) VALUES ('部署上线');

执行:

wrangler d1 execute my-db --remote --file=seed.sql

也可以用 --command 参数执行单条 SQL,但不建议传中文,Windows 终端编码容易出问题:

# 英文数据可以用 --command
wrangler d1 execute my-db --remote --command="INSERT INTO todos (title) VALUES ('hello world')"

# 中文数据请用 --file

七、在 Worker 中查询数据

创建 src/index.js:

export default {
    async fetch(request, env) {
        const url = new URL(request.url);

        // 获取所有待办事项
        if (url.pathname === "/api/todos") {
            const { results } = await env.MY_DB.prepare(
                "SELECT * FROM todos ORDER BY created_at DESC"
            ).all();
            return Response.json(results);
        }

        // 新增待办事项
        if (url.pathname === "/api/todos" && request.method === "POST") {
            const { title } = await request.json();
            const { meta } = await env.MY_DB.prepare(
                "INSERT INTO todos (title) VALUES (?)"
            ).bind(title).run();
            return Response.json({ id: meta.last_row_id, title }, { status: 201 });
        }

        // 切换完成状态
        if (url.pathname.startsWith("/api/todos/") && request.method === "PATCH") {
            const id = url.pathname.split("/").pop();
            await env.MY_DB.prepare(
                "UPDATE todos SET completed = NOT completed WHERE id = ?"
            ).bind(id).run();
            return Response.json({ success: true });
        }

        // 删除
        if (url.pathname.startsWith("/api/todos/") && request.method === "DELETE") {
            const id = url.pathname.split("/").pop();
            await env.MY_DB.prepare(
                "DELETE FROM todos WHERE id = ?"
            ).bind(id).run();
            return Response.json({ success: true });
        }

        return new Response("Todo API: GET/POST /api/todos, PATCH/DELETE /api/todos/:id");
    },
};

代码不长,但有几个要点:

  1. env.MY_DB 就是 wrangler.toml 中 binding 对应的值。
  2. .prepare(sql) 预编译 SQL,.bind(params) 绑定参数,防止 SQL 注入。
  3. 查询用 .all() 返回所有结果,写入用 .run() 返回 meta 信息。

八、D1 的 API

D1 的原生 API 很简单,只有几个方法:

方法 用途 返回值
.prepare(sql) 预编译 SQL Statement 对象
.bind(...params) 绑定参数 Statement 对象
.all() 查询所有结果 { results: [] }
.first() 查询第一条 对象 或 null
.run() 执行写入操作 { meta: { last_row_id, changes } }
.batch([stmt1, stmt2]) 批量执行(事务) 结果数组

最常用的就是 all()、first() 和 run()。

查询示例

// 查询所有
const { results } = await env.MY_DB.prepare("SELECT * FROM todos").all();

// 查询单条
const todo = await env.MY_DB.prepare("SELECT * FROM todos WHERE id = ?").bind(1).first();

// 带条件查询
const { results } = await env.MY_DB.prepare(
    "SELECT * FROM todos WHERE completed = ?"
).bind(0).all();

写入示例

// 插入
const { meta } = await env.MY_DB.prepare(
    "INSERT INTO todos (title) VALUES (?)"
).bind("新任务").run();

const newId = meta.last_row_id;  // 自增 ID

// 更新
const { meta } = await env.MY_DB.prepare(
    "UPDATE todos SET completed = ? WHERE id = ?"
).bind(1, 5).run();

const affectedRows = meta.changes;  // 影响行数

// 删除
await env.MY_DB.prepare("DELETE FROM todos WHERE id = ?").bind(5).run();

批量操作(事务)

// batch 是隐式事务,要么全成功,要么全回滚
const results = await env.MY_DB.batch([
    env.MY_DB.prepare("INSERT INTO todos (title) VALUES (?)").bind("任务A"),
    env.MY_DB.prepare("INSERT INTO todos (title) VALUES (?)").bind("任务B"),
    env.MY_DB.prepare("INSERT INTO todos (title) VALUES (?)").bind("任务C"),
]);

九、本地开发

D1 支持本地开发,数据存在 .wrangler/state/ 目录下。

wrangler dev

访问 http://localhost:8787,所有操作都在本地数据库执行,不会影响线上数据。

本地数据库的建表和导入数据,去掉 --remote 参数即可:

wrangler d1 execute my-db --file=schema.sql
wrangler d1 execute my-db --file=seed.sql

十、部署

wrangler deploy

部署后,你的 Worker 就有了全局 URL,比如:

https://my-d1-app.你的子域.workers.dev/api/todos

十一、常见问题

1. 中文乱码

原因:Windows 终端默认编码是 GBK,通过 --command 传中文会乱码。

解决:用 SQL 文件(UTF-8 编码)代替 --command,或者通过 Worker API 插入。

2. Error 1101

原因:代码中 env.xxx 的绑定名和 wrangler.toml 中的 binding 不一致。

解决:确保两者完全一致。比如 binding = "MY_DB",代码里就写 env.MY_DB。

3. 查询返回空数组

原因:数据库没有数据,或者建表/导入操作没有加 --remote。

解决:

# 检查远程数据库是否有数据
wrangler d1 execute my-db --remote --command="SELECT * FROM todos"

4. D1 的限制

指标 免费版 付费版
存储空间 5GB 25GB
每日读取 500万 250亿
每日写入 10万 5000万
单次查询行数 1000 1000
单库大小 10GB 10GB

D1 适合中小规模的应用。如果你的数据量很大,或者需要完整的 PostgreSQL 功能,可以考虑 Turso 或 Supabase。

十二、参考链接


(完)

留言(0 条)

还没有留言,来写第一条吧

使用 GitHub 登录后即可在下方直接留言