rxliuli blog

rxliuli blog

马上订阅 rxliuli blog RSS 更新: https://blog.rxliuli.com/atom.xml

Cloudflare D1 数据库查询优化之路

2025年4月3日 15:56

背景

最近在做一些服务端相关的事情,使用了 Cloudflare Workers + D1 数据库,在此过程中,遇到了一些数据库相关的问题,而对于前端而言数据库是一件相当不同的事情,所以在此记录一下。

下图是最近 30 天的请求记录,可以看到数据库查询变化之剧烈。

1743670296635.jpg

发现问题

解决问题的前提是发现问题,有几个方法可以更容易留意到相关问题。

  1. 检查 D1 仪表盘,确定数据库操作是否有异常增长
  2. 检查查询语句及读取/写入行数,特别关注 count/rows read/rows written 排在前列的查询
  3. 使用 c.env.DB.prepare('<sql>').run()).meta 并检查返回的 meta,它包含这个 sql 实际读取/写入的行数

使用 batch 批量请求

首先明确一点,Workers 和 D1 虽然同为 Cloudflare 的服务,但同时使用它们并不会让 D1 变得更快。拿下面这个简单的查询举例,它的平均响应时间(在 Workers 上发起查询到在 Workers 上得到结果)超过了 200ms。

1
await db.select().from(user).limit(1)

所以在一个接口中包含大量的数据库操作时,应该尽量使用 d1 batch 来批量完成,尤其是对于写入操作,由于没有只读副本,它只会比查询更慢。例如

1
2
await db.insert(user).values({...})
await db.insert(tweet).values({...})

应该更换为

1
2
3
4
await db.batch([
db.insert(user).values({...}),
db.insert(tweet).values({...})
])

这样只会向 d1 发出一次 rest 请求即可完成多个数据库写入操作。

ps1: prisma 不支持 d1 batch,吾辈因此换到了 drizzle 中,参考 记录一次从 Prisma 到 Drizzle 的迁移
ps2: 使用 batch 进行批量查询时需要小心,尤其是多表有同名的列时,参考 https://github.com/drizzle-team/drizzle-orm/issues/555

update 操作排除 id

在 update 时应该排除 id(即使实际上没有修改)。例如下面的代码,将外部传入的 user 传入并更新,看起来没问题?

1
await db.update(user).set(userParam).where(eq(user.id, userParam.id))

实际执行的 SQL 语句

1
update "User" set "id" = ?, "screenName" = ?, "updatedAt" = ? where "User"."id" = ?

然而,一旦这个 id 被其他表通过外键引用了。它就会导致大量的 rows read 操作。例如另一张名为 tweet 的表有一个 userId 引用了这个字段,并且有 1000 行数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
await db.batch([
db.insert(user).values({
id: `test-user-1`...

剩余内容已隐藏

查看完整文章以阅读更多