关于 cloudflare-d1
cloudflare-d1 是面向通过 Workers 使用 Cloudflare D1 的开发者的实践指南,涵盖数据库配置、迁移、查询和本地开发,并提供 Wrangler 示例。
主要功能
- 01数据库与绑定配置
- 02版本化 SQL 迁移
- 03预处理语句与批量查询
- 04本地和远程数据库工作流
- 05查询性能与错误处理指南
适用场景
- 01创建 D1 数据库并执行迁移
- 02在 Worker 中查询 D1
- 03批量处理相关数据库操作
- 04在本地测试架构变更
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ ovachiever/ droid-tings --skill cloudflare-d1安装后在 Claude Code 中输入/cloudflare-d1确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
README.md.claude-plugin/plugin.jsonreferences/best-practices.mdquery-patterns.mdtemplates/d1-setup-migration.shd1-worker-queries.tsschema-example.sql详细介绍
Cloudflare D1 数据库
状态:已可用于生产环境 ✅ 最后更新:2025-11-23 依赖项:cloudflare-worker-base(用于 Worker 设置) 最新版本:[email protected]、@cloudflare/[email protected]
近期更新(2025 年):
- 2025 年 11 月:支持司法管辖区(符合数据本地化要求);远程绑定正式发布([email protected]+);自动配置资源
- 2025 年 9 月:自动重试只读查询(最多 2 次);远程绑定公开测试版
- 2025 年 7 月:存储限制提高(250GB → 1TB);移除 Alpha 备份访问;REST API 提速 50-500ms
- 2025 年 5 月:修复 HTTP API 权限安全问题(写入操作需要 D1:Edit 权限)
- 2025 年 4 月:读取副本公开测试版(跨区域只读副本)
- 2025 年 2 月:支持 PRAGMA optimize;修复只读访问权限错误
- 2025 年 1 月:开始执行免费套餐限制(2 月 10 日起);Worker API 查询提速 40-60%
快速入门(5 分钟)
1. 创建 D1 数据库
# Create a new D1 database
npx wrangler d1 create my-database
# Output includes database_id - save this!
# ✅ Successfully created DB 'my-database'
#
# [[d1_databases]]
# binding = "DB"
# database_name = "my-database"
# database_id = "<UUID>"
2. 配置绑定
添加到你的 wrangler.jsonc:
{
"name": "my-worker",
"main": "src/index.ts",
"compatibility_date": "2025-10-11",
"d1_databases": [
{
"binding": "DB", // Available as env.DB in your Worker
"database_name": "my-database", // Name from wrangler d1 create
"database_id": "<UUID>", // ID from wrangler d1 create
"preview_database_id": "local-db" // For local development
}
]
}
重要:
binding是你在代码中访问数据库的方式(env.DB)database_id是生产数据库的 UUIDpreview_database_id用于本地开发(可以是任意字符串)- 切勿将真实的
database_id值提交到公共仓库——请使用环境变量或机密信息
3. 创建第一个迁移
# Create migration file
npx wrangler d1 migrations create my-database create_users_table
# This creates: migrations/0001_create_users_table.sql
编辑迁移文件:
-- migrations/0001_create_users_table.sql
DROP TABLE IF EXISTS users;
CREATE TABLE IF NOT EXISTS users (
user_id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT NOT NULL UNIQUE,
username TEXT NOT NULL,
created_at INTEGER NOT NULL,
updated_at INTEGER
);
-- Create index for common queries
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
-- Optimize database
PRAGMA optimize;
4. 应用迁移
# Apply locally first (for testing)
npx wrangler d1 migrations apply my-database --local
# Apply to production when ready
npx wrangler d1 migrations apply my-database --remote
5. 在 Worker 中查询
// src/index.ts
import { Hono } from 'hono';
type Bindings = {
DB: D1Database;
};
const app = new Hono<{ Bindings: Bindings }>();
app.get('/api/users/:email', async (c) => {
const email = c.req.param('email');
try {
// ALWAYS use prepared statements with bind()
const result = await c.env.DB.prepare(
'SELECT * FROM users WHERE email = ?'
)
.bind(email)
.first();
if (!result) {
return c.json({ error: 'User not found' }, 404);
}
return c.json(result);
} catch (error: any) {
console.error('D1 Error:', error.message);
return c.json({ error: 'Database error' }, 500);
}
});
export default app;
D1 迁移系统
迁移工作流
# 1. Create migration
npx wrangler d1 migrations create <DATABASE_NAME> <MIGRATION_NAME>
# 2. List unapplied migrations
npx wrangler d1 migrations list <DATABASE_NAME> --local
npx wrangler d1 migrations list <DATABASE_NAME> --remote
# 3. Apply migrations
npx wrangler d1 migrations apply <DATABASE_NAME> --local # Test locally
npx wrangler d1 migrations apply <DATABASE_NAME> --remote # Deploy to production
迁移文件命名
迁移会自动进行版本管理:
migrations/
├── 0000_initial_schema.sql
├── 0001_add_users_table.sql
├── 0002_add_posts_table.sql
└── 0003_add_indexes.sql
规则:
- 文件按顺序执行
- 每个迁移只运行一次(记录在
d1_migrations表中) - 失败的迁移会回滚(事务性)
- 不能修改或删除已应用的迁移
自定义迁移配置
{
"d1_databases": [
{
"binding": "DB",
"database_name": "my-database",
"database_id": "<UUID>",
"migrations_dir": "db/migrations", // Custom directory (default: migrations/)
"migrations_table": "schema_migrations" // Custom tracking table (default: d1_migrations)
}
]
}
迁移最佳实践
✅ 始终要做:
-- Use IF NOT EXISTS to make migrations idempotent
CREATE TABLE IF NOT EXISTS users (...);
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
-- Run PRAGMA optimize after schema changes
PRAGMA optimize;
-- Use transactions for data migrations
BEGIN TRANSACTION;
UPDATE users SET updated_at = unixepoch() WHERE updated_at IS NULL;
COMMIT;
❌ 绝不要做:
-- DON'T include BEGIN TRANSACTION at start (D1 handles this)
BEGIN TRANSACTION; -- ❌ Remove this
-- DON'T use MySQL/PostgreSQL syntax
ALTER TABLE users MODIFY COLUMN email VARCHAR(255); -- ❌ Not SQLite
-- DON'T create tables without IF NOT EXISTS
CREATE TABLE users (...); -- ❌ Fails if table exists
在迁移中处理外键
-- Temporarily disable foreign key checks during schema changes
PRAGMA defer_foreign_keys = true;
-- Make schema changes that would violate foreign keys
ALTER TABLE posts DROP COLUMN author_id;
ALTER TABLE posts ADD COLUMN user_id INTEGER REFERENCES users(user_id);
-- Foreign keys re-enabled automatically at end of migration
D1 Workers API
类型定义:
interface Env { DB: D1Database; }
type Bindings = { DB: D1Database; };
const app = new Hono<{ Bindings: Bindings }>();
prepare() - 主要方法(处理用户输入时务必使用):
const user = await env.DB.prepare('SELECT * FROM users WHERE email = ?')
.bind(email).first();
原因:可防止 SQL 注入、可重复使用、性能更佳、类型安全
查询结果方法:
.all()→{ results, meta }- 获取所有行.first()→ 行对象或 null - 获取第一行.first('column')→ 值 - 获取单列值(例如 COUNT).run()→{ success, meta }- 执行 INSERT/UPDATE/DELETE(不返回结果)
batch() - 对性能至关重要:
const results = await env.DB.batch([
env.DB.prepare('SELECT * FROM users WHERE user_id = ?').bind(1),
env.DB.prepare('SELECT * FROM posts WHERE user_id = ?').bind(1)
]);
- 按顺序执行,只需一次网络往返
- 如果其中一条失败,其余语句不会执行
- 适用于:批量插入、获取关联数据
exec() - 生产环境中应避免使用:
await env.DB.exec('SELECT * FROM users;'); // Only for migrations/maintenance
- ❌ 切勿用于用户输入(存在 SQL 注入风险)
- ✅ 仅用于:迁移文件、一次性任务
查询模式
基本 CRUD 操作
// CREATE
const { meta } = await env.DB.prepare(
'INSERT INTO users (email, username, created_at) VALUES (?, ?, ?)'
).bind(email, username, Date.now()).run();
const newUserId = meta.last_row_id;
// READ (single)
const user = await env.DB.prepare('SELECT * FROM users WHERE user_id = ?')
.bind(userId).first();
// READ (multiple)
const { results } = await env.DB.prepare('SELECT * FROM users LIMIT ?')
.bind(10).all();
// UPDATE
const { meta } = await env.DB.prepare('UPDATE users SET username = ? WHERE user_id = ?')
.bind(newUsername, userId).run();
const rowsAffected = meta.rows_written;
// DELETE
await env.DB.prepare('DELETE FROM users WHERE user_id = ?').bind(userId).run();
// COUNT
const count = await env.DB.prepare('SELECT COUNT(*) as total FROM users').first('total');
// EXISTS check
const exists = await env.DB.prepare('SELECT 1 FROM users WHERE email = ? LIMIT 1')
.bind(email).first();
分页模式
const page = parseInt(c.req.query('page') || '1');
const limit = 20;
const offset = (page - 1) * limit;
const [countResult, usersResult] = await c.env.DB.batch([
c.env.DB.prepare('SELECT COUNT(*) as total FROM users'),
c.env.DB.prepare('SELECT * FROM users ORDER BY created_at DESC LIMIT ? OFFSET ?')
.bind(limit, offset)
]);
return c.json({
users: usersResult.results,
pagination: { page, limit, total: countResult.results[0].total }
});
批处理模式(模拟事务)
// D1 doesn't support multi-statement transactions, but batch() provides sequential execution
await env.DB.batch([
env.DB.prepare('UPDATE users SET credits = credits - ? WHERE user_id = ?').bind(amount, fromUserId),
env.DB.prepare('UPDATE users SET credits = credits + ? WHERE user_id = ?').bind(amount, toUserId),
env.DB.prepare('INSERT INTO transactions (from_user, to_user, amount) VALUES (?, ?, ?)').bind(fromUserId, toUserId, amount)
]);
// If any statement fails, batch stops (transaction-like behavior)
错误处理
常见错误类型:
D1_ERROR- 通用 D1 错误D1_EXEC_ERROR- SQL 语法错误D1_TYPE_ERROR- 类型不匹配(应使用 null,却使用了 undefined)D1_COLUMN_NOTFOUND- 列不存在
常见错误及修复方法:
| 错误 | 原因 | 解决方案 |
|---|---|---|
| 语句过长 | 包含 1000 行以上的大型 INSERT | 使用 batch() 分成每批 100-250 行 |
| 排队的请求过多 | 在循环中逐条执行查询 | 使用 batch(),不要使用循环 |
| D1_TYPE_ERROR | 在绑定参数中使用了 undefined | 可选值使用 null:.bind(email, bio || null) |
| 事务冲突 | 在迁移中使用 BEGIN TRANSACTION | 删除 BEGIN/COMMIT(D1 会自动处理) |
| 外键约束违规 | 架构变更破坏了约束 | 使用 PRAGMA defer_foreign_keys = true |
自动重试(2025 年 9 月):
D1 会在遇到可重试错误时,自动重试只读查询(SELECT、EXPLAIN、WITH),最多 2 次。检查响应中的 meta.total_attempts,查看重试次数。
性能优化
索引最佳实践:
- ✅ 为 WHERE 子句中的列创建索引:
CREATE INDEX idx_users_email ON users(email) - ✅ 为外键创建索引:
CREATE INDEX idx_posts_user_id ON posts(user_id) - ✅ 为排序使用的列创建索引:
CREATE INDEX idx_posts_created_at ON posts(created_at DESC) - ✅ 创建多列索引:
CREATE INDEX idx_posts_user_published ON posts(user_id, published) - ✅ 创建部分索引:
CREATE INDEX idx_users_active ON users(email) WHERE deleted = 0 - ✅ 使用以下命令进行测试:
EXPLAIN QUERY PLAN SELECT ...
PRAGMA optimize(2025 年 2 月):
CREATE INDEX idx_users_email ON users(email);
PRAGMA optimize; -- Run after schema changes
查询优化:
- ✅ 使用指定的列(不要使用
SELECT *) - ✅ 对大型结果集始终添加 LIMIT
- ✅ 为 WHERE 条件使用索引
- ❌ 避免在 WHERE 中使用函数(无法使用索引):
WHERE LOWER(email)→ 改为存储小写值
本地开发
本地与远程(2025 年 11 月 - 远程绑定正式发布):
# Local database (automatic creation)
npx wrangler d1 migrations apply my-database --local
npx wrangler d1 execute my-database --local --command "SELECT * FROM users"
# Remote database
npx wrangler d1 execute my-database --remote --command "SELECT * FROM users"
# Remote bindings ([email protected]+) - connect local Worker to deployed D1
# Add to wrangler.jsonc: { "binding": "DB", "remote": true }
本地数据库位置:
.wrangler/state/v3/d1/miniflare-D1DatabaseObject/<database_id>.sqlite
填充本地数据库:
npx wrangler d1 execute my-database --local --file=seed.sql
最佳实践总结
✅ 务必做到:
- 对用户输入使用预处理语句和
.bind() - 多条查询使用
.batch()(降低延迟) - 为经常查询的列创建索引
- 架构变更后运行
PRAGMA optimize - 在迁移中使用
IF NOT EXISTS,确保幂等性 - 应用到生产环境前,在本地测试迁移
- 使用 try/catch 妥善处理错误
- 可选值使用
null,不要使用undefined - 将输入绑定到查询前先进行验证
- 执行 UPDATE/DELETE 后检查
meta.rows_written
❌ 切勿:
- 切勿使用
.exec()处理用户输入(有 SQL 注入风险) - 切勿在公开仓库中硬编码
database_id - 切勿在绑定参数中使用
undefined(会导致 D1_TYPE_ERROR) - 切勿在循环中逐条执行查询(改用 batch)
- 对可能产生大型结果集的查询切勿忘记添加
LIMIT - 在生产环境中切勿使用
SELECT *(请指定列) - 切勿在迁移文件中包含
BEGIN TRANSACTION - 切勿修改已应用的迁移(请创建新的迁移)
- 数据库操作切勿跳过错误处理
- 切勿假设查询一定成功(务必检查结果)
已避免的已知问题
| 问题 | 描述 | 如何避免 |
|---|---|---|
| 语句过长 | 大型 INSERT 语句超出 D1 限制 | 拆分为每批 100-250 行 |
| 事务冲突 | 迁移文件中包含 BEGIN TRANSACTION | 移除 BEGIN/COMMIT(D1 会处理) |
| 外键违规 | 架构变更破坏外键约束 | 使用 PRAGMA defer_foreign_keys = true |
| 速率限制 / 队列过载 | 单独查询过多 | 使用 batch() 代替循环 |
| 超出内存限制 | 查询将过多数据加载到内存中 | 添加 LIMIT、对结果分页、拆分查询 |
| 类型不匹配错误 | 使用 undefined 而不是 null | 可选值始终使用 null |
Wrangler 命令参考
# Database management
wrangler d1 create <DATABASE_NAME>
wrangler d1 list
wrangler d1 delete <DATABASE_NAME>
wrangler d1 info <DATABASE_NAME>
# Migrations
wrangler d1 migrations create <DATABASE_NAME> <MIGRATION_NAME>
wrangler d1 migrations list <DATABASE_NAME> --local|--remote
wrangler d1 migrations apply <DATABASE_NAME> --local|--remote
# Execute queries
wrangler d1 execute <DATABASE_NAME> --local|--remote --command "SELECT * FROM users"
wrangler d1 execute <DATABASE_NAME> --local|--remote --file=./query.sql
# Time Travel (view historical data)
wrangler d1 time-travel info <DATABASE_NAME> --timestamp "2025-10-20"
wrangler d1 time-travel restore <DATABASE_NAME> --timestamp "2025-10-20"
官方文档
- D1 概览:https://developers.cloudflare.com/d1/
- 快速入门:https://developers.cloudflare.com/d1/get-started/
- 迁移:https://developers.cloudflare.com/d1/reference/migrations/
- Workers API:https://developers.cloudflare.com/d1/worker-api/
- 最佳实践:https://developers.cloudflare.com/d1/best-practices/
- Wrangler 命令:https://developers.cloudflare.com/workers/wrangler/commands/#d1
立即开始使用 D1 构建! 🚀