网站首页 > 文章精选 正文
这是一份 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!
- 上一篇: 深入理解类:ArkTS面向对象编程的核心概念
- 下一篇: ArkTS中的空安全:全面解析与实践
猜你喜欢
- 2025-05-14 TS,TypeScript,Windows环境下构建环境,安装、编译且运行
- 2025-05-14 TypeScript 也能开发AI应用了!
- 2025-05-14 搞懂 TypeScript 装饰器
- 2025-05-14 前端小哥哥:如何使用typescript开发实战项目?
- 2025-05-14 在 React 项目中,一般怎么处理错误?
- 2025-05-14 react19 常用状态管理
- 2025-05-14 Vue3开发极简入门(2):TypeScript定义对象类型
- 2025-05-14 C#与TypeScript语法深度对比
- 2025-05-14 360前端一面~面试题解析
- 2025-05-14 Python标准库中的七个“小众但神奇”的实用函数
- 05-14TS,TypeScript,Windows环境下构建环境,安装、编译且运行
- 05-14TypeScript 也能开发AI应用了!
- 05-14搞懂 TypeScript 装饰器
- 05-14前端小哥哥:如何使用typescript开发实战项目?
- 05-14在 React 项目中,一般怎么处理错误?
- 05-14react19 常用状态管理
- 05-14Vue3开发极简入门(2):TypeScript定义对象类型
- 05-14C#与TypeScript语法深度对比
- 最近发表
- 标签列表
-
- newcoder (56)
- 字符串的长度是指 (45)
- drawcontours()参数说明 (60)
- unsignedshortint (59)
- postman并发请求 (47)
- python列表删除 (50)
- 左程云什么水平 (56)
- 计算机网络的拓扑结构是指() (45)
- 编程题 (64)
- postgresql默认端口 (66)
- 数据库的概念模型独立于 (48)
- 产生系统死锁的原因可能是由于 (51)
- 数据库中只存放视图的 (62)
- 在vi中退出不保存的命令是 (53)
- 哪个命令可以将普通用户转换成超级用户 (49)
- noscript标签的作用 (48)
- 联合利华网申 (49)
- swagger和postman (46)
- 结构化程序设计主要强调 (53)
- 172.1 (57)
- apipostwebsocket (47)
- 唯品会后台 (61)
- 简历助手 (56)
- offshow (61)
- mysql数据库面试题 (57)