Cloudflare D1 数据库查询优化之路
背景
最近在做一些服务端相关的事情,使用了 Cloudflare Workers + D1 数据库,在此过程中,遇到了一些数据库相关的问题,而对于前端而言数据库是一件相当不同的事情,所以在此记录一下。
下图是最近 30 天的请求记录,可以看到数据库查询变化之剧烈。

发现问题
解决问题的前提是发现问题,有几个方法可以更容易留意到相关问题。
- 检查 D1 仪表盘,确定数据库操作是否有异常增长
- 检查查询语句及读取/写入行数,特别关注 count/rows read/rows written 排在前列的查询
- 使用
c.env.DB.prepare('<sql>').run()).meta并检查返回的 meta,它包含这个 sql 实际读取/写入的行数
使用 batch 批量请求
首先明确一点,Workers 和 D1 虽然同为 Cloudflare 的服务,但同时使用它们并不会让 D1 变得更快。拿下面这个简单的查询举例,它的平均响应时间(在 Workers 上发起查询到在 Workers 上得到结果)超过了 200ms。
1 | |
所以在一个接口中包含大量的数据库操作时,应该尽量使用 d1 batch 来批量完成,尤其是对于写入操作,由于没有只读副本,它只会比查询更慢。例如
1 | |
应该更换为
1 | |
这样只会向 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 | |
实际执行的 SQL 语句
1 | |
然而,一旦这个 id 被其他表通过外键引用了。它就会导致大量的 rows read 操作。例如另一张名为 tweet 的表有一个 userId 引用了这个字段,并且有 1000 行数据。
1 | |