V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
leia
V2EX  ›  分享发现

秒杀传统数据库! Cloudflare D1 + Drizzle 组合拳,高并发高可用,让我们的成本爆降 10 倍 - D1

  •  
  •   leia · 2025 年 7 月 10 日 · 3369 次点击
    这是一个创建于 189 天前的主题,其中的信息可能已经有所发展或是发生改变。

    秒杀传统数据库! Cloudflare D1 + Drizzle 组合拳,高并发高可用,让我们的成本爆降 10 倍 - D1

    想象一下:我们的应用用户量稳步增长,​传统数据库的成本和维护压力也随之上升​。而在这个时代,有没有更高效、更经济的数据库解决方案?​Cloudflare D1 结合 Drizzle ORM 的组合​,正在为众多​出海应用开发提供一条全新的技术路径​。

    传统数据库方案在高并发场景下往往需要复杂的扩容、分片和负载均衡,成本随着流量呈指数级增长。而当我们了解了 Cloudflare D1 这款​基于 SQLite 构建的边缘数据库​,再配合 Drizzle 这个轻量级 ORM 的强大能力,我们会惊讶于这个组合如何能在保持高性能的同时,​将我们的基础设施成本直接腰斩​!

    无需复杂的数据库集群,无需昂贵的专用服务器,无需担心地理位置带来的延迟问题 — 这个方案将​彻底改变我们对数据库架构的认知​。

    Cloudflare D1 实战:从零开始搭建高性能数据库

    Cloudflare D1 是 Cloudflare 推出的一款分布式 SQL 数据库,它基于 SQLite 构建,完全集成在 Cloudflare Workers 生态系统中。D1 将 SQLite 数据库部署到 Cloudflare 的全球边缘网络,让我们的数据库与应用代码一样,运行在离用户最近的位置,大幅降低延迟。

    D1 成本计算与对比

    在深入技术细节前,让我们先来看看 D1 在成本方面的巨大优势。Cloudflare D1 采用了极具竞争力的定价模型:

    资源类型 Workers Free (免费版) Workers Paid (付费版)
    读取行数 每天 500 万行限制 每月前 250 亿行免费,超出部分 $0.001/百万行
    写入行数 每天 10 万行限制 每月前 5000 万行免费,超出部分 $1.00/百万行
    存储空间 总计 5GB 限制 前 5GB 免费,超出部分 $0.75/GB-月

    让我们来分析一下免费版的套餐:

    • 读取成本​:每天 500 万行的读取量,一个月约 1.5 亿行,完全在免费额度内。即使你的应用流量翻倍,达到每天 1000 万行读取,每月 3 亿行,超出免费额度的 5000 万行只需要额外支付 $0.05/月。
    • 写入成本​:每天 10 万行的写入,一个月约 300 万行,远低于免费额度的 5000 万行。即使写入量增长 10 倍,仍然在免费额度内。
    • 存储成本​:5GB 的存储空间完全免费。对于大多数中小型应用来说,这已经足够存储数百万条记录

    付费版的价格是 5$,免费版的规模足够处理 5000-20000 日活的应用,付费 20000-100w 日活。

    快速上手 D1

    1. 安装 Wrangler CLI

    首先,我们需要安装 Cloudflare 的 Wrangler CLI 工具:

    npm install -g wrangler
    

    2. 创建 D1 数据库

    登录我们的 Cloudflare 账户后,创建一个新的 D1 数据库:

    wrangler login
    wrangler d1 create my-database
    

    执行后,我们会看到类似这样的输出:

    ✅ Successfully created DB 'my-database' in region APAC
    Created D1 database 'my-database' with id: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
    

    请记下这个数据库 ID ,我们后续会用到。

    3. 配置 wrangler.toml

    在我们的项目根目录创建或编辑 wrangler.toml 文件,添加 D1 数据库配置:

    [[d1_databases]]
    binding = "DB" # 在 Workers 中使用的变量名
    database_name = "my-database"
    database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" # 替换为我们的数据库 ID
    

    4. 创建数据表

    创建一个 SQL 文件,例如 schema.sql

    CREATE TABLE users (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      name TEXT NOT NULL,
      email TEXT UNIQUE NOT NULL,
      created_at DATETIME DEFAULT CURRENT_TIMESTAMP
    );
    

    然后执行:

    wrangler d1 execute my-database --file=./schema.sql
    

    5. 在 Workers 中使用 D1

    现在,我们可以在 Cloudflare Workers 中使用 D1 数据库了:

    export interface Env {
      DB: D1Database
    }
    
    export default {
      async fetch(request: Request, env: Env): Promise<Response> {
        // 查询用户列表
        const { results } = await env.DB.prepare('SELECT * FROM users ORDER BY created_at DESC LIMIT 10').all()
    
        return new Response(JSON.stringify(results), {
          headers: { 'Content-Type': 'application/json' }
        })
      }
    }
    

    D1 的实用命令与简单实践

    在实际开发中,我们需要更多的工具来管理数据库。D1 提供了一系列强大的命令行工具,让数据库管理变得轻松高效。 数据库迁移:管理我们的架构变更 数据库结构会随着需求不断变化。D1 提供了完善的迁移系统,让我们可以版本化管理数据库结构:

    创建一个新的迁移文件
    wrangler d1 migrations create my-database add_user_role
    

    这会在项目中创建一个类似 migrations/0001_add_user_role.sql` 的文件。编辑这个文件,添加我们的 SQL 变更:

    -- Migration: add_user_role
    -- Created at: 2023-10-15 14:30:00
    
    -- 向用户表添加角色字段
    ALTER TABLE users ADD COLUMN role TEXT DEFAULT 'user' NOT NULL;
    
    -- 创建一个新的角色权限表
    CREATE TABLE role_permissions (
      role TEXT NOT NULL,
      permission TEXT NOT NULL,
      PRIMARY KEY (role, permission)
    );
    

    然后应用这些迁移:

    应用到本地开发环境
    wrangler d1 migrations apply my-database --local
    
    应用到生产环境
    wrangler d1 migrations apply my-database --remote
    

    这种方式让我们可以: - 追踪数据库的所有变更历史 - 在团队中同步数据库结构 - 在不同环境(开发、测试、生产)之间保持一致性

    数据导入导出:备份与恢复

    需要备份数据或将数据迁移到其他环境? D1 提供了简单的导出导入功能

    导出整个数据库(结构+数据)
    wrangler d1 export my-database --output=backup.sql
    
    只导出特定表
    wrangler d1 export my-database --table=users --output=users_backup.sql
    
    只导出结构,不导出数据
    wrangler d1 export my-database --output=schema.sql --no-data
    

    导入数据同样简单:

    wrangler d1 execute my-database --file=backup.sql
    

    实战案例:构建一个博客系统

    让我们通过一个实际案例来展示 D1 的强大功能。假设我们要构建一个简单的博客系统,需要存储文章和评论。

    首先,创建数据库结构:

    -- migrations/0001_create_blog_tables.sql
    CREATE TABLE posts (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      title TEXT NOT NULL,
      content TEXT NOT NULL,
      author_id INTEGER NOT NULL,
      published_at DATETIME DEFAULT CURRENT_TIMESTAMP,
      status TEXT DEFAULT 'draft' NOT NULL
    );
    
    CREATE TABLE comments (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      post_id INTEGER NOT NULL,
      author_name TEXT NOT NULL,
      content TEXT NOT NULL,
      created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
      FOREIGN KEY (post_id) REFERENCES posts(id) ON DELETE CASCADE
    );
    
    CREATE INDEX idx_posts_status ON posts(status);
    CREATE INDEX idx_comments_post_id ON comments(post_id);
    

    npx wrangler d1 execute prod-d1-tutorial --local --file=./migrations/0001_create_blog_tables.sql

    然后,在 Workers 中实现 API 接口:

    export interface Env {
      DB: D1Database
    }
    
    export default {
      async fetch(request: Request, env: Env): Promise<Response> {
        const url = new URL(request.url)
        const path = url.pathname
    
        // 获取博客文章列表
        if (path === '/api/posts' && request.method === 'GET') {
          const { results } = await env.DB.prepare(
            "SELECT id, title, published_at FROM posts WHERE status = 'published' ORDER BY published_at DESC LIMIT 10"
          ).all()
    
          return new Response(JSON.stringify(results), {
            headers: { 'Content-Type': 'application/json' }
          })
        }
    
        // 获取单篇文章及其评论
        if (path.match(/^\/api\/posts\/\d+$/) && request.method === 'GET') {
          const postId = path.split('/').pop()
    
          // 获取文章详情
          const post = await env.DB.prepare('SELECT * FROM posts WHERE id = ?').bind(postId).first()
    
          if (!post) {
            return new Response(JSON.stringify({ error: 'Post not found' }), {
              status: 404,
              headers: { 'Content-Type': 'application/json' }
            })
          }
    
          // 获取文章评论
          const { results: comments } = await env.DB.prepare(
            'SELECT * FROM comments WHERE post_id = ? ORDER BY created_at DESC'
          )
            .bind(postId)
            .all()
    
          return new Response(JSON.stringify({ post, comments }), {
            headers: { 'Content-Type': 'application/json' }
          })
        }
    
        // 添加评论
        if (path.match(/^\/api\/posts\/\d+\/comments$/) && request.method === 'POST') {
          const postId = path.split('/')[3]
          const { author_name, content } = await request.json()
    
          // 插入评论
          const result = await env.DB.prepare(
            'INSERT INTO comments (post_id, author_name, content) VALUES (?, ?, ?) RETURNING id'
          )
            .bind(postId, author_name, content)
            .run()
    
          return new Response(JSON.stringify({ id: result.results[0].id }), {
            status: 201,
            headers: { 'Content-Type': 'application/json' }
          })
        }
    
        return new Response('Not Found', { status: 404 })
      }
    }
    

    这个简单的博客 API 已经能够: - 获取已发布的文章列表 - 获取单篇文章及其评论 - 为文章添加新评论

    本地开发与调试

    在开发过程中,我们可以使用本地数据库进行测试:

    启动本地开发服务器,使用本地 D1 数据库
    wrangler dev --local
    

    这会在本地创建一个 SQLite 数据库文件,我们可以在开发过程中使用它,而不需要每次都操作远程数据库。当我们的代码准备好后,再将变更应用到远程数据库。

    应用迁移到远程数据库
    wrangler d1 migrations apply my-database --remote
    

    通过这种方式,我们可以在本地快速迭代开发,同时确保生产环境的数据安全。

    结束

    而在下一章节中,就讲解``Drizzle\,讲这个的主要目的是为了给大家普及一下`海外批量应用`的基础套件的知识 关于我

    27 条回复    2025-07-13 20:09:03 +08:00
    summerwar
        1
    summerwar  
       2025 年 7 月 10 日
    cloudflare d1 这种数据库的一个坑:一个 SELECT ... LIMIT 10 OFFSET 90000 的查询,可能会导致 D1 计算接近 90,010 次“行读取”。

    这个时候尽量用游标分页,就可以避免。
    julyclyde
        2
    julyclyde  
       2025 年 7 月 10 日
    @summerwar
    是按行读取收费吗?还是行读取多了会严重性能下降呢?
    summerwar
        3
    summerwar  
       2025 年 7 月 10 日   ❤️ 1
    @julyclyde 是按行读取收费

    其实这个不是 d1 的坑,offset 在 mysql 、sqlite 中也都是读取这么多行,只不过因为 d1 是按行读取计费,所以显得这条命令变成了坑。
    chihiro2014
        4
    chihiro2014  
       2025 年 7 月 10 日
    有了 cf ,基本连服务器钱都不需要了
    FlashEcho
        5
    FlashEcho  
       2025 年 7 月 10 日
    Craveu 不是 cozyai 的吗?为什么这是你的站点?
    june4
        6
    june4  
       2025 年 7 月 10 日
    @summerwar 根本就不应该允许用户翻这么多页。真有这类需求也有高效的分页方案,比如按一个字段排序分页。
    streamrx
        7
    streamrx  
       2025 年 7 月 10 日 via iPhone
    D1 延迟挺高的。 用 druable objects + sqlite 比 d1 好用
    cookii
        8
    cookii  
       2025 年 7 月 10 日 via Android
    我看到有个老哥吐槽,count 一下就是几美刀
    qingmeng
        9
    qingmeng  
       2025 年 7 月 10 日
    免费版 5GB 是十个库的额度,单库只有 500MB ,完全不够用
    lloovve
        10
    lloovve  
       2025 年 7 月 10 日 via iPhone
    全球分布式数据库?一个节点数据能实时同步到其他节点吗?其他节点能实时同步过来?
    iX8NEGGn
        11
    iX8NEGGn  
       2025 年 7 月 10 日
    D1 延迟挺高,不能高并发吧,而且不支持交互式事务,这才是致命缺陷,不过个人项目玩玩倒无所谓。
    RedBeanIce
        12
    RedBeanIce  
       2025 年 7 月 11 日
    @chihiro2014worker 好像只能 nodejs?
    pottwr
        13
    pottwr  
       2025 年 7 月 11 日
    适合 MVP 阶段,确定要真正投入时间做产品的话还是谨慎使用
    hanguofu
        14
    hanguofu  
       2025 年 7 月 11 日 via Android
    谢谢分享宝贵经验
    ttthys
        15
    ttthys  
       2025 年 7 月 11 日
    按行收费的话那不是 count 一下就过免费额度了,还以为是按返回的行收费😂
    ersic
        16
    ersic  
       2025 年 7 月 11 日
    数据多了别 count ,算全表总行数的读
    bigtear
        17
    bigtear  
       2025 年 7 月 11 日 via Android
    无服务器方案到最后都是后悔
    yb2313
        18
    yb2313  
       2025 年 7 月 11 日
    又秒杀上了
    wenjie83
        19
    wenjie83  
       2025 年 7 月 11 日
    这种付费方式,不仅要单独根据付费特性去优化语句,而且如果以后付费规则发生变动,是迁移还是不迁移?
    感觉还是买断制放心
    elevioux
        20
    elevioux  
       2025 年 7 月 11 日   ❤️ 1
    这个账号专门给自己博客引流的吗?
    chihiro2014
        21
    chihiro2014  
       2025 年 7 月 11 日
    @RedBeanIce worker 支持的语言都可以操作 D1 吧,nodejs 只是 worker 用的频繁罢了。rust 啥的也都可以
    leia
        22
    leia  
    OP
       2025 年 7 月 11 日
    @elevioux 没有了 哈哈哈 老哥,
    leia
        23
    leia  
    OP
       2025 年 7 月 11 日
    @yb2313 包秒的
    molvqingtai
        24
    molvqingtai  
       2025 年 7 月 11 日
    默认不是分布式,延迟太高了,国内一个请求 500+ms
    Foxkeh
        25
    Foxkeh  
       2025 年 7 月 11 日
    "成本暴降 10 倍"?
    Selenium39
        26
    Selenium39  
       2025 年 7 月 11 日
    一股 AI 味
    leia
        27
    leia  
    OP
       2025 年 7 月 13 日
    @Selenium39 原文写完让他润色改格式是这样的
    关于   ·   帮助文档   ·   自助推广系统   ·   博客   ·   API   ·   FAQ   ·   Solana   ·   954 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 38ms · UTC 21:19 · PVG 05:19 · LAX 13:19 · JFK 16:19
    ♥ Do have faith in what you're doing.