程序员求职经验分享与学习资料整理平台

网站首页 > 文章精选 正文

Cloudflare D1 详细使用指南

balukai 2025-05-14 11:58:25 文章精选 2 ℃

这是一份 Cloudflare D1 的详细使用指南。


Cloudflare D1 是 Cloudflare 推出的一个基于 SQLite 构建的无服务器数据库。它与 Cloudflare Workers 紧密集成,允许您在边缘运行数据驱动的应用程序,从而减少延迟并提高性能。


**核心概念:**


* **无服务器 (Serverless):** 您无需管理数据库服务器、操作系统或进行容量规划。Cloudflare 会处理这些。

* **基于 SQLite:** D1 使用 SQLite 作为其底层数据库引擎,这意味着它支持标准的 SQL 语法。

* **边缘存储:** 您的数据存储在 Cloudflare 的全球网络中,靠近您的用户,从而实现低延迟访问。

* **与 Workers 集成:** D1 专为与 Cloudflare Workers 一起使用而设计,允许您通过 Workers 函数轻松查询和操作数据。

* **按需付费:** 您只需为实际使用的存储和操作付费。


**详细使用指南:**


### 1. 准备工作


在使用 D1 之前,您需要:


* **Cloudflare 账户:** 如果您还没有,请注册一个 Cloudflare 账户。

* **Wrangler CLI:** 这是 Cloudflare 的命令行工具,用于管理 Workers 和 D1 数据库。确保您已安装并配置了最新版本的 Wrangler。

* 安装命令 (通常使用 npm): `npm install -g wrangler`

* 登录 Wrangler: `wrangler login`


### 2. 创建 D1 数据库


您可以使用 Wrangler CLI 创建一个新的 D1 数据库。


* **创建数据库命令:**

```bash

wrangler d1 create <DATABASE_NAME>

```

例如:

```bash

wrangler d1 create my-first-d1-db

```

执行此命令后,Wrangler 会在您的 Cloudflare 账户中创建一个新的 D1 数据库,并在您的 `wrangler.toml` 文件中(如果存在于当前目录)或全局配置中记录数据库的 `binding`、`database_name` 和 `database_id`。


* **`wrangler.toml` 配置:**

创建数据库后,您需要将其绑定到您的 Worker 项目中。在您的 `wrangler.toml` 文件中添加或更新 D1 绑定配置:

```toml

[[d1_databases]]

binding = "DB" # 您的 Worker 代码中将使用此名称来访问数据库

database_name = "my-first-d1-db" # 您创建时使用的名称

database_id = "
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" # 创建数据库时生成的唯一 ID

preview_database_id = "your-preview-db-id" # 可选,用于本地预览的数据库 ID

migrations_dir = "migrations" # 可选,指定迁移文件目录,默认为 ./migrations

```

* `binding`: 这是您在 Worker 代码中引用数据库时将使用的变量名。

* `database_name`: 您在创建数据库时指定的名称。

* `database_id`: Cloudflare 分配给数据库的唯一标识符。

* `preview_database_id`: (可选) 用于 `wrangler dev --remote` 模式下的预览数据库。通常,在本地开发时,Wrangler 会使用本地 SQLite 文件。

* `migrations_dir`: (可选) 存放数据库迁移文件的目录。


### 3. 数据库模式 (Schema) 和迁移 (Migrations)


D1 使用迁移来管理数据库模式的变更。迁移是一系列 SQL 文件,按顺序应用以更新数据库结构。


* **创建迁移文件:**

您可以使用 Wrangler CLI 生成一个新的迁移文件:

```bash

wrangler d1 migrations create <DATABASE_NAME> <MIGRATION_DESCRIPTION>

```

例如:

```bash

wrangler d1 migrations create my-first-d1-db create_users_table

```

这会在您 `wrangler.toml` 中指定的 `migrations_dir` (默认为 `./migrations`) 目录下创建一个新的 SQL 文件,例如 `
0000_create_users_table.sql`。


* **编写迁移 SQL:**

编辑生成的 SQL 文件以定义您的表结构。例如,创建一个 `users` 表:

```sql

-- migrations/0000_create_users_table.sql

CREATE TABLE users (

id INTEGER PRIMARY KEY AUTOINCREMENT,

name TEXT NOT NULL,

email TEXT UNIQUE NOT NULL,

created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP

);


-- 您还可以添加索引

CREATE INDEX idx_users_email ON users (email);

``` **注意:** D1 使用 SQLite 语法。`AUTOINCREMENT` 关键字在 SQLite 中用于整数主键,以确保新插入的行获得一个比该列中已存在的最大值更大的值。


* **应用迁移:**

* **本地开发:** 在本地开发时,Wrangler 会自动尝试应用迁移到本地的 `
.wrangler/state/d1/DB.sqlite3` 文件 (或您配置的预览数据库)。

```bash

wrangler d1 execute <DATABASE_NAME> --local --file ./migrations/0000_create_users_table.sql

# 或者,如果您配置了 migrations_dir 并且想应用所有未应用的迁移:

# wrangler d1 migrations apply <DATABASE_NAME> --local (此命令在较新版本中可能已简化或集成到 dev 流程中)

# 通常在 `wrangler dev` 启动时,它会检查并应用本地迁移。

```

* **部署到生产/预览环境:** 当您部署 Worker 时,Wrangler 会自动应用新的迁移。您也可以手动应用:

```bash

wrangler d1 migrations apply <DATABASE_NAME>

```

或者在部署时自动应用:

```bash

wrangler deploy

```


### 4. 在 Worker 中与 D1 交互


一旦您的数据库创建完成并且模式已迁移,您就可以在 Cloudflare Worker 代码中与之交互。


* **基本查询:**

在您的 Worker 脚本 (例如 `src/index.js` 或 `src/index.ts`) 中,您可以通过 `wrangler.toml` 中定义的绑定名称访问 D1 实例。


```typescript

// src/index.ts

export interface Env {

DB: D1Database; // 'DB' 必须与 wrangler.toml 中的 binding 名称匹配

}


export default {

async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {

const { pathname } = new URL(request.url);


if (pathname === "/api/users" && request.method === "POST") {

try {

const { name, email } = await request.json<{ name: string; email: string }>();

if (!name || !email) {

return new Response("Name and email are required", { status: 400 });

}


const stmt = env.DB.prepare("INSERT INTO users (name, email) VALUES (?, ?)");

const { success } = await stmt.bind(name, email).run();


if (success) {

return new Response("User created successfully", { status: 201 });

} else {

return new Response("Failed to create user", { status: 500 });

}

} catch (e: any) {

return new Response(`Error: ${e.message}`, { status: 500 });

}

}


if (pathname === "/api/users" && request.method === "GET") {

try {

const stmt = env.DB.prepare("SELECT id, name, email, strftime('%Y-%m-%d %H:%M:%S', created_at) as created_at FROM users");

const { results } = await stmt.all();

return new Response(JSON.stringify(results), {

headers: { "Content-Type": "application/json" },

});

} catch (e: any) {

return new Response(`Error: ${e.message}`, { status: 500 });

}

}


return new Response("Not found", { status: 404 });

},

};

```


* **D1 API 方法:**

D1 绑定对象提供了以下主要方法:

* `prepare(query: string): D1PreparedStatement`: 创建一个预处理语句。这是执行 SQL 查询的首选方式,因为它可以防止 SQL 注入。

* `dump(): Promise<ArrayBuffer>`: 将整个数据库导出为 SQL 文件格式的 ArrayBuffer。

* `batch(statements: D1PreparedStatement[]): Promise<D1Result[]> `: 在单个事务中执行多个预处理语句。如果任何语句失败,则整个批处理将回滚。

* `exec(query: string): Promise<D1ExecResult>`: 执行一个或多个以分号分隔的 SQL 语句,并返回执行结果,但不返回查询数据。适用于 `CREATE TABLE`, `DROP TABLE` 等DDL语句或不期望返回结果集的简单 `INSERT`。


* **`D1PreparedStatement` 方法:**

`prepare()` 方法返回一个 `D1PreparedStatement` 对象,该对象具有以下方法:

* `bind(...values: any[]): D1PreparedStatement`: 将值绑定到预处理语句中的占位符 (`?`)。按顺序绑定。

* `first(colName?: string): Promise<T | null>`: 执行查询并返回第一行结果。如果指定了 `colName`,则只返回该列的值。

* `run(): Promise<D1Result>`: 执行不返回数据的查询(如 `INSERT`, `UPDATE`, `DELETE`)。返回一个包含 `success` 状态和 `meta` 信息(如更改的行数、最后插入的行 ID)的对象。

* `all(): Promise<D1Result<T[]>>`: 执行查询并返回所有结果行。

* `raw<T = unknown[]>(): Promise<T[]>`: 执行查询并以原始数组数组的形式返回所有结果行,而不是对象数组。


**示例:使用 `bind` 和 `run` 进行插入**

```typescript

const newUser = { name: "Alice", email: "alice@example.com" };

const stmt = env.DB.prepare("INSERT INTO users (name, email) VALUES (?1, ?2)");

const info = await stmt.bind(newUser.name, newUser.email).run();

// info.success 会是 true 或 false

// info.meta.last_row_id 会包含新插入用户的 ID (如果表有 INTEGER PRIMARY KEY)

// info.meta.changes 会是更改的行数 (通常是 1)

```


**示例:使用 `bind` 和 `first` 获取单条记录**

```typescript

const userId = 1;

const stmt = env.DB.prepare("SELECT * FROM users WHERE id = ?1");

const user = await stmt.bind(userId).first();

// user 将是包含用户数据的对象,或 null (如果未找到)

```


**示例:使用 `bind` 和 `all` 获取多条记录**

```typescript

const stmt = env.DB.prepare("SELECT id, name FROM users WHERE name LIKE ?1");

const { results } = await stmt.bind("%Smith%").all();

// results 将是一个包含匹配用户对象的数组

```


* **事务 (Transactions):**

使用 `batch()` 方法可以执行事务。所有在 `batch()` 中提供的语句要么全部成功,要么全部失败(回滚)。

```typescript

try {

const results = await env.DB.batch([

env.DB.prepare("INSERT INTO logs (message) VALUES (?)").bind("Log message 1"),

env.DB.prepare("UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE id = ?").bind(123),

]);

// results 是一个 D1Result 数组,对应每个语句的结果

console.log("Batch operation successful:", results);

} catch (e) {

console.error("Batch operation failed:", e);

// 事务已回滚

}

```


### 5. 本地开发和测试


Wrangler 支持 D1 的本地开发。


* **启动本地开发服务器:**

```bash

wrangler dev

```

当您运行 `wrangler dev` 时:

* Wrangler 会在您的项目目录下的 `.wrangler/state/d1/` 中为每个 D1 绑定创建一个本地 SQLite 文件 (例如 `DB.sqlite3`)。

* 它会自动应用 `migrations_dir` 中的迁移到这些本地 SQLite 文件。

* 您的 Worker 代码在本地运行时将与这些本地 SQLite 文件交互。


* **本地直接执行 SQL:**

您可以使用 Wrangler CLI 直接在本地 D1 数据库上执行 SQL 命令,这对于调试或快速检查数据非常有用。

```bash

wrangler d1 execute <DATABASE_NAME> --local --command "SELECT * FROM users;"

```

或者执行一个 SQL 文件:

```bash

wrangler d1 execute <DATABASE_NAME> --local --file ./my_query.sql

```


* **`wrangler dev --remote`:**

如果您想在本地开发时连接到 Cloudflare 上实际的(可能是预览或生产)D1 数据库,而不是本地 SQLite 文件,可以使用 `--remote` 标志:

```bash

wrangler dev --remote

```

在这种模式下,您需要在 `wrangler.toml` 中为 D1 绑定配置 `preview_database_id`,或者它会默认使用生产 `database_id`(请谨慎操作)。


### 6. 备份和恢复 (Time Travel)


D1 提供了一种称为 "Time Travel" 的功能,允许您将数据库恢复到过去某个时间点的状态(目前支持恢复到过去30天内的任何时间点,具体请查阅最新文档)。


* **使用 Wrangler CLI 进行恢复:**

```bash

# 列出可用的备份(这可能不是直接命令,而是通过 Cloudflare 仪表板或特定 API)

# 实际恢复命令(具体命令请查阅最新文档,可能如下所示):

wrangler d1 time-travel restore <DATABASE_NAME> --timestamp="YYYY-MM-DDTHH:MM:SSZ"

```

**重要:** Time Travel 会创建一个新的数据库副本,或者将现有数据库恢复到指定时间点。请仔细阅读文档以了解其确切行为和潜在影响。


* **通过 Cloudflare 仪表板:**

您通常也可以通过 Cloudflare 仪表板的 D1 部分来管理和执行 Time Travel 恢复操作。


* **手动备份 (`dump`):**

您可以使用 `wrangler d1 execute <DB_NAME> --command ".dump" > backup.sql` 来获取数据库的 SQL 转储。

或者通过 Worker 的 `dump()` 方法:

```typescript

// In your worker

const dump = await env.DB.dump();

// dump is an ArrayBuffer containing the SQL statements

// You can then save this to R2 or return it in a response

```


### 7. 限制和定价


* **限制:**

* **数据库大小:** D1 数据库有大小限制(例如,免费套餐可能有较小的限制,付费套餐有更大的限制)。具体限制请查阅最新的 Cloudflare D1 文档。

* **查询复杂度/时间:** 长时间运行或非常复杂的查询可能会被终止。

* **并发性:** 虽然 D1 设计为可扩展,但在极高并发下仍需注意性能。

* **区域性:** D1 旨在提供全球低延迟,但其复制和一致性模型可能与传统集中式数据库不同。

* **定价:**

D1 的定价通常基于:

* **存储量:** 存储的数据总量。

* **读取操作数:** 执行的读取操作(如 `SELECT`)的数量。

* **写入操作数:** 执行的写入操作(如 `INSERT`, `UPDATE`, `DELETE`)的数量。

* **扫描的数据量:** 查询扫描的数据量。

Cloudflare 通常提供一个免费套餐,超出部分按使用量计费。请务必查阅 Cloudflare 官方网站上最新的 D1 定价信息。


### 8. 最佳实践和注意事项


* **使用预处理语句:** 始终使用 `prepare()` 和 `bind()` 来执行 SQL 查询,以防止 SQL 注入漏洞。

* **索引:** 为经常用于 `WHERE` 子句、`JOIN` 条件和 `ORDER BY` 子句的列创建索引,以提高查询性能。

* **批量操作:** 对于多个相关的写入操作,使用 `batch()` 方法将它们组合在单个事务中,以确保原子性并可能提高性能。

* **数据验证:** 在将数据插入数据库之前,在您的 Worker 代码中进行彻底的数据验证。

* **错误处理:** 妥善处理 D1 API 调用可能抛出的错误。

* **迁移管理:** 保持迁移文件的有序和清晰,以便于团队协作和版本控制。

* **不要在循环中执行 `prepare()`:** 如果您需要在循环中执行相同的查询但使用不同的参数,请在循环外部调用 `prepare()` 一次,然后在循环内部调用 `bind()` 和相应的执行方法 (`run()`, `all()`, `first()`)。

```typescript

// 好:

const stmt = env.DB.prepare("INSERT INTO items (value) VALUES (?)");

for (const item of itemsToInsert) {

await stmt.bind(item.value).run();

}


// 不好(性能较低):

for (const item of itemsToInsert) {

await env.DB.prepare("INSERT INTO items (value) VALUES (?)").bind(item.value).run();

}

```

* **理解 SQLite 的特性和限制:** 虽然 D1 管理底层设施,但它仍然是 SQLite。了解 SQLite 的数据类型、函数和特性将非常有帮助。例如,SQLite 的动态类型系统与某些其他 SQL 数据库不同。

* **监控和日志:** 利用 Cloudflare Workers 的日志和分析功能来监控您的 D1 交互和性能。


### 9. 示例:一个简单的待办事项列表应用 (概念)


**`wrangler.toml`:**

```toml

name = "todo-app"

main = "src/index.ts"

compatibility_date = "2023-10-30" # 使用较新的兼容性日期


[[d1_databases]]

binding = "TODOS_DB"

database_name = "my-todos-db"

database_id = "your-database-id"

migrations_dir = "migrations_todos"

```


**`migrations_todos/0000_create_todos_table.sql`:**

```sql

CREATE TABLE todos (

id INTEGER PRIMARY KEY AUTOINCREMENT,

task TEXT NOT NULL,

completed BOOLEAN DEFAULT FALSE,

created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP

);

```


**`src/index.ts` (简化版):**

```typescript

export interface Env {

TODOS_DB: D1Database;

}


export default {

async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {

const { pathname, searchParams } = new URL(request.url);


if (request.method === "POST" && pathname === "/todos") {

const { task } = await request.json<{ task: string }>();

if (!task) return new Response("Task is required", { status: 400 });


const stmt = env.TODOS_DB.prepare("INSERT INTO todos (task) VALUES (?) RETURNING id, task, completed, strftime('%Y-%m-%d %H:%M:%S', created_at) as created_at");

const { results } = await stmt.bind(task).all(); // 使用 RETURNING 获取插入的行

return Response.json(results ? results[0] : null, { status: 201 });

}


if (request.method === "GET" && pathname === "/todos") {

const stmt = env.TODOS_DB.prepare("SELECT id, task, completed, strftime('%Y-%m-%d %H:%M:%S', created_at) as created_at FROM todos ORDER BY created_at DESC");

const { results } = await stmt.all();

return Response.json(results);

}


if (request.method === "PUT" && pathname.startsWith("/todos/")) {

const id = parseInt(pathname.split("/")[2]);

const { completed } = await request.json<{ completed: boolean }>();


if (isNaN(id) || typeof completed !== 'boolean') {

return new Response("Invalid input", { status: 400 });

}


const stmt = env.TODOS_DB.prepare("UPDATE todos SET completed = ? WHERE id = ? RETURNING id, task, completed, strftime('%Y-%m-%d %H:%M:%S', created_at) as created_at");

const { results } = await stmt.bind(completed, id).all();

if (results && results.length > 0) {

return Response.json(results[0]);

} else {

return new Response("Todo not found", { status: 404 });

}

}


return new Response("Not Found", { status: 404 });

},

};

```

**运行步骤:**

1. `wrangler d1 create my-todos-db`

2. 更新 `wrangler.toml` 中的 `database_id`。

3. 创建 `
migrations_todos/0000_create_todos_table.sql` 文件。

4. `wrangler deploy` (首次部署会运行迁移) 或 `wrangler d1 migrations apply my-todos-db` 后再 `wrangler deploy`。

5. 或者本地测试:`wrangler dev` (会自动应用迁移到本地 SQLite 文件)。


### 10. 总结


Cloudflare D1 为无服务器应用程序提供了一个强大且易于使用的数据库解决方案。通过与 Workers 的紧密集成和利用 SQLite 的能力,您可以快速构建具有状态的全球分布式应用程序。请务必查阅最新的 [Cloudflare D1 官方文档](
https://developers.cloudflare.com/d1/) 以获取最准确和最新的信息,因为该服务仍在不断发展中。


希望这份详细指南能帮助您开始使用 Cloudflare D1!

最近发表
标签列表