最近使用 Cloudflare D1 作为服务端的数据库,ORM 选择了很多人推荐的 Prisma,但使用过程中遇到了一些问题,主要包括
首先说一下第一个问题,Cloudflare D1 本身并不支持事务,仅支持使用 batch 批处理,这是一种有限制的事务。https://developers.cloudflare.com/d1/worker-api/d1-database/#batch
例如
1 | |
而如果你使用 Prisma 的 $transaction 函数,会得到一条警告。
1 | |
这条警告指向了 cloudflare/workers-sdk,看起来是 cloudflare d1 的问题(当然,不支持事务确实是问题),但也转移了关注点,问题是为什么 prisma 内部不使用 d1 batch 函数呢?嗯,它目前不支持,仅此而已,检查 @prisma/adapter-d1 的事务实现。
例如下面这个统计查询,统计 + 去重,看起来很简单?
1 | |
你在 prisma 中可能会想这样写。
1 | |
不,prisma 不支持,检查已经开放了 4 年 的 issue#4228。
顺便说一句,drizzle 允许你这样做。
1 | |
这一点没有真正分析过,只是体感上感觉服务端 API 请求很慢,平均时间甚至达到了 1s,而目前最大的一张表数据也只有 30+k,而大多数其他表还不到 1k,这听起来不正常。但事后来看,从 prisma 切换到 drizzle 之后,bundle 尺寸从 2776.05 KiB / gzip: 948.21 KiB 降低到了 487.87 KiB / gzip: 93.10 KiB,gzip 之后甚至降低了 90%,这听起来并不那么不可思议了。

有人做了一些测试,似乎批量插入 1k 条的性能问题更糟糕,甚至超过了 30s。https://github.com/prisma/prisma/discussions/23646#discussioncomment-10965747
说完了 Prisma 的这么多问题,接下来说一下在迁移过程中踩到的坑。
在迁移时,首先使用 Grok 从 schema.prisma 自动生成了 drizzle 需要的 schema.ts。但发现了以下问题
原本的表结构
1 | |
Grok 自动转换生成
1 | |
这里的自动转换有几个问题
sql`uuid()` 在 prisma 中由应用抽象层填充,但 schema.ts 里使用 sql`uuid()`,这里应该同样由应用抽象层填充sql`CURRENT_TIMESTAMP`Invalid Date实际上需要修改为
1 | |
嗯,在 join 查询时 drizzle 并不会自动解决冲突的列名。假设有 User 和 ModList 两张表
| id | screenName | name |
|---|---|---|
| user-1 | user-screen-name | user-name |
| id | name | userId |
|---|---|---|
| modlist-1 | modlist-name | user-1 |
然后执行以下代码,非批量查询的结果将与批量查询的结果不同。
1 | |
1 | |
这里的 ModList 和 User 中有冲突的列名 id/name,在批量查询时后面的列将会覆盖掉前面的,参考相关的 issue。
https://github.com/cloudflare/workers-sdk/issues/3160
https://github.com/drizzle-team/drizzle-orm/issues/555
需要手动指定列的别名。
1 | |
然后就会得到一致的结果。
1 | |
甚至可以实现一个通用的别名生成器。
1 | |
然后就不再需要手动设置别名,而且它还是类型安全的!
1 | |
数据迁移时兼容性最重要,修改或优化 schema 必须放在迁移之后。整体上这次的迁移结果还是挺喜人的,后续开新坑数据库 ORM 可以直接用 drizzle 了。
最近使用 Cloudflare D1 作为服务端的数据库,ORM 选择了很多人推荐的 Prisma,但使用过程中遇到了一些问题,主要包括
首先说一下第一个问题,Cloudflare D1 本身并不支持事务,仅支持使用 batch 批处理,这是一种有限制的事务。https://developers.cloudflare.com/d1/worker-api/d1-database/#batch
例如
1 | |
而如果你使用 Prisma 的 $transaction 函数,会得到一条警告。
1 | |
这条警告指向了 cloudflare/workers-sdk,看起来是 cloudflare d1 的问题(当然,不支持事务确实是问题),但也转移了关注点,问题是为什么 prisma 内部不使用 d1 batch 函数呢?嗯,它目前不支持,仅此而已,检查 @prisma/adapter-d1 的事务实现。
例如下面这个统计查询,统计 + 去重,看起来很简单?
1 | |
你在 prisma 中可能会想这样写。
1 | |
不,prisma 不支持,检查已经开放了 4 年 的 issue#4228。
顺便说一句,drizzle 允许你这样做。
1 | |
这一点没有真正分析过,只是体感上感觉服务端 API 请求很慢,平均时间甚至达到了 1s,而目前最大的一张表数据也只有 30+k,而大多数其他表还不到 1k,这听起来不正常。但事后来看,从 prisma 切换到 drizzle 之后,bundle 尺寸从 2776.05 KiB / gzip: 948.21 KiB 降低到了 487.87 KiB / gzip: 93.10 KiB,gzip 之后甚至降低了 90%,这听起来并不那么不可思议了。

有人做了一些测试,似乎批量插入 1k 条的性能问题更糟糕,甚至超过了 30s。https://github.com/prisma/prisma/discussions/23646#discussioncomment-10965747
说完了 Prisma 的这么多问题,接下来说一下在迁移过程中踩到的坑。
在迁移时,首先使用 Grok 从 schema.prisma 自动生成了 drizzle 需要的 schema.ts。但发现了以下问题
原本的表结构
1 | |
Grok 自动转换生成
1 | |
这里的自动转换有几个问题
sql`uuid()` 在 prisma 中由应用抽象层填充,但 schema.ts 里使用 sql`uuid()`,这里应该同样由应用抽象层填充sql`CURRENT_TIMESTAMP`Invalid Date实际上需要修改为
1 | |
嗯,在 join 查询时 drizzle 并不会自动解决冲突的列名。假设有 User 和 ModList 两张表
| id | screenName | name |
|---|---|---|
| user-1 | user-screen-name | user-name |
| id | name | userId |
|---|---|---|
| modlist-1 | modlist-name | user-1 |
然后执行以下代码,非批量查询的结果将与批量查询的结果不同。
1 | |
1 | |
这里的 ModList 和 User 中有冲突的列名 id/name,在批量查询时后面的列将会覆盖掉前面的,参考相关的 issue。
https://github.com/cloudflare/workers-sdk/issues/3160
https://github.com/drizzle-team/drizzle-orm/issues/555
需要手动指定列的别名。
1 | |
然后就会得到一致的结果。
1 | |
甚至可以实现一个通用的别名生成器。
1 | |
然后就不再需要手动设置别名,而且它还是类型安全的!
1 | |
数据迁移时兼容性最重要,修改或优化 schema 必须放在迁移之后。整体上这次的迁移结果还是挺喜人的,后续开新坑数据库 ORM 可以直接用 drizzle 了。