mcpField
C
cloudflare-d1

cloudflare-d1

github.com/ovachiever数据与数据库5649 次安装48 周前更新
代码仓库

关于 cloudflare-d1

cloudflare-d1 是面向通过 Workers 使用 Cloudflare D1 的开发者的实践指南,涵盖数据库配置、迁移、查询和本地开发,并提供 Wrangler 示例。

主要功能

  1. 01数据库与绑定配置
  2. 02版本化 SQL 迁移
  3. 03预处理语句与批量查询
  4. 04本地和远程数据库工作流
  5. 05查询性能与错误处理指南

适用场景

  1. 01创建 D1 数据库并执行迁移
  2. 02在 Worker 中查询 D1
  3. 03批量处理相关数据库操作
  4. 04在本地测试架构变更

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/ovachiever/droid-tings --skill cloudflare-d1

安装后在 Claude Code 中输入/cloudflare-d1确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

README.md
.claude-plugin/
plugin.json
references/
best-practices.md
query-patterns.md
templates/
d1-setup-migration.sh
d1-worker-queries.ts
schema-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 是生产数据库的 UUID
  • preview_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

最佳实践总结

✅ 务必做到:
  1. 对用户输入使用预处理语句和 .bind()
  2. 多条查询使用 .batch()(降低延迟)
  3. 为经常查询的列创建索引
  4. 架构变更后运行 PRAGMA optimize
  5. 在迁移中使用 IF NOT EXISTS,确保幂等性
  6. 应用到生产环境前,在本地测试迁移
  7. 使用 try/catch 妥善处理错误
  8. 可选值使用 null,不要使用 undefined
  9. 将输入绑定到查询前先进行验证
  10. 执行 UPDATE/DELETE 后检查 meta.rows_written
❌ 切勿:
  1. 切勿使用 .exec() 处理用户输入(有 SQL 注入风险)
  2. 切勿在公开仓库中硬编码 database_id
  3. 切勿在绑定参数中使用 undefined(会导致 D1_TYPE_ERROR)
  4. 切勿在循环中逐条执行查询(改用 batch)
  5. 对可能产生大型结果集的查询切勿忘记添加 LIMIT
  6. 在生产环境中切勿使用 SELECT *(请指定列)
  7. 切勿在迁移文件中包含 BEGIN TRANSACTION
  8. 切勿修改已应用的迁移(请创建新的迁移)
  9. 数据库操作切勿跳过错误处理
  10. 切勿假设查询一定成功(务必检查结果)

已避免的已知问题

问题描述如何避免
语句过长大型 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 构建! 🚀

评论

支持 Markdown