数据库

Prisma 错误排查


本指南将解决您在使用 Supabase 时可能遇到的常见 Prisma 错误。

理解连接字符串参数

与其他库不同,Prisma 允许您通过在连接字符串后附加特殊选项来配置其设置

这些被称为"查询参数"的选项可用于解决特定错误。

1
2
3
# 查询参数示例connection_string.../postgres?KEY1=VALUE&KEY2=VALUE&KEY3=VALUE

常见错误

... prepared statement already exists

事务模式下的 Supavisor(端口 6543)不支持 prepared statements,而 Prisma 会在后台尝试创建这些语句。

解决方案

  • 在连接字符串中添加 pgbouncer=true。这会关闭 Prisma 中的预处理语句功能。
1
.../postgres?pgbouncer=true

Can't reach database server at:

Prisma 在超时前无法与 Postgres 或 Supavisor 建立连接

可能原因

  • 数据库过载:数据库服务器负载过高,导致 Prisma 难以连接
  • 连接字符串格式错误:Prisma 使用的连接字符串不正确或不完整
  • 临时网络问题:临时网络故障中断了连接

解决方案:

  • 检查数据库健康状态:使用报表仪表盘监控CPU、内存和I/O使用情况。如果数据库过载,考虑增加计算规格优化查询
  • 验证连接字符串:仔细检查Prisma配置中的连接字符串,确保与项目连接页面中的一致。
  • 增加连接超时时间:尝试在Prisma配置中增加connect_timeout参数值,给予更多建立连接的时间。
1
.../postgres?connect_timeout=30

从连接池获取新连接超时:

Prisma无法快速分配连接以满足待处理查询的需求。

可能原因:

  • 服务器过载:托管Prisma的服务器负载过高,限制了其管理连接的能力。默认情况下,Prisma会创建num_cpus * 2 + 1数量的连接。服务器压力的常见原因是将connection_limit设置得远高于默认值。
  • 连接池大小不足:Supavisor连接池没有足够的可用连接来快速满足Prisma的请求。
  • 慢查询:Prisma的查询执行时间过长,导致无法释放连接以供重用。

解决方案:

  • 增加连接池超时时间:在 Prisma 配置中增加 pool_timeout 参数,为连接池分配连接提供更多时间。
  • 降低连接数限制:如果您在 Prisma 配置中显式增加了 connection_limit 参数,尝试将其降低到更合理的值。
  • 增大连接池大小:如果使用 Supavisor 连接,尝试在数据库设置中增加连接池大小。
  • 优化查询语句提升查询效率以减少执行时间。
  • 升级计算规格:与前一个方案类似,这是减少查询执行时间的策略。

服务器已关闭连接

根据这个Prisma的GitHub Issue,该错误可能与查询返回大量数据有关,也可能是由于数据库负载过高导致的。

解决方案:

  • 限制返回行数:对于特别大的查询请求,尝试限制返回的总行数。
  • 减轻数据库负载:检查报告页面的数据库负载情况。如果存在明显负载,考虑进行查询优化或升级计算规格。

检测到架构漂移:数据库架构与迁移历史不同步

Prisma 依赖迁移文件来确保数据库与 Prisma 模型保持一致。外部对数据库架构的修改会被检测为"漂移",Prisma 会尝试覆盖这些变更,可能导致数据丢失。

可能原因:

  • Supabase 托管模式:Supabase 可能会更新如 auth 和 storage 等托管模式以引入新功能。授予 Prisma 访问这些模式的权限可能导致更新时出现差异。
  • 外部模式修改:您的团队或其他工具可能在 Prisma 之外修改了数据库模式,导致差异。

解决方案:

  • 基线迁移基线化通过将当前数据库模式作为未来迁移的起点来重新同步 Prisma。

达到最大客户端连接数

Postgres 或 Supavisor 拒绝了更多连接请求

可能原因:

  • 在事务模式下工作(端口 6543):当客户端尝试与连接池建立超过其支持能力的连接时,会出现"达到最大客户端连接数"错误。
  • 在会话模式下工作(端口 5432):客户端最大数量受限于数据库设置中的"Pool Size"值。如果"Pool Size"设置为15,即使连接池可以处理200个客户端连接,每个唯一的"数据库角色+数据库"组合实际上仍会被限制在15个连接。
  • 使用直接连接时:Postgres 已经在处理最大数量的连接

解决方案

  • 无服务器应用的交易模式:如果使用无服务器函数(Supabase Edge、Vercel、AWS Lambda),请切换到交易模式(端口6543)。该模式比会话模式或直接连接能处理更多连接。
  • 减少Prisma连接数:单个客户端服务器可以通过连接池建立多个连接。通常无服务器设置不需要过多连接,初始使用较少的连接数(如5个、3个甚至1个)往往就足够。在无服务器环境中,建议从connection_limit=1开始,仅在必要时谨慎增加以避免连接数耗尽。
  • 扩大连接池规模:如果使用Supavisor连接,尝试在数据库设置中增加连接池大小。
  • 适时断开连接:当Prisma连接不再需要时及时关闭。
  • 缩短查询时间:通过降低查询复杂度或为表添加战略索引来加速查询。
  • 提升计算规格:有时最佳方案是升级计算规格,这也会同步提高最大客户端连接数和查询执行速度

仅当目标schema列在数据源的schemas属性中时,才允许跨schema引用

Prisma迁移正在引用一个未被授权管理的schema。

可能原因:

  • 迁移引用了Prisma未被授权管理的schema

解决方案:

  • 多Schema支持:如果外部Schema不由Supabase管理,修改您的prisma.schema文件以启用multiSchema预览功能
1
2
3
4
5
6
7
8
9
10
11
generator client { provider = "prisma-client-js" previewFeatures = ["multiSchema"] // 添加此行}datasource db { provider = "postgresql" url = env("DATABASE_URL") directUrl = env("DIRECT_URL") schemas = ["public", "other_schema"] // 列出相关schemas}
  • Supabase管理的Schemas:由Supabase管理的schemas(如authstorage)可能会因支持新特性而变更。直接引用这些schemas会导致未来的schema漂移。最佳实践是从您的迁移中移除对这些schemas的引用。

引用这些表的替代策略是通过触发器将值复制到Prisma管理的表中。以下是将auth.users中的值复制到名为profiles的表示例。

显示/隐藏详情
1
2
3
4
5
-- 在'public' schema中创建'profiles'表create table public.profiles ( id uuid primary key, -- 'id'是UUID类型的主键 email varchar(256) -- 'email'是最大长度256字符的变长字符字段);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- 处理新用户插入到'profiles'表的函数create function public.handle_new_user()returns triggerlanguage plpgsqlsecurity definer set search_path = ''as $$begin -- 将新用户数据插入到'profiles'表 insert into public.profiles (id, email) values (new.id, new.email); return new; -- 返回新记录end;$$;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-- 处理'profiles'表中用户信息更新的函数create function public.update_user()returns triggerlanguage plpgsqlsecurity definer set search_path = ''as$$begin -- 更新'profiles'表中的用户数据 update public.profiles set email = new.email -- 更新'email'字段 where id = new.id; -- 根据'id'字段匹配新记录 return new; -- 返回新记录end;$$;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- 处理从'profiles'表删除用户的函数create function public.delete_user()returns triggerlanguage plpgsqlsecurity definer set search_path = ''as$$begin -- 从'profiles'表删除用户数据 delete from public.profiles where id = old.id; -- 根据'id'字段匹配旧记录 return old; -- 返回旧记录end;$$;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- 在'auth.users'表插入新用户后触发'handle_new_user'函数create trigger on_auth_user_created after insert on auth.users for each row execute procedure public.handle_new_user();-- 在'auth.users'表更新用户后触发'update_user'函数create trigger on_auth_user_updated after update on auth.users for each row execute procedure public.update_user();-- 在'auth.users'表删除用户后触发'delete_user'函数create trigger on_auth_user_deleted after delete on auth.users for each row execute procedure public.delete_user();