SQLSTATE 40001 (serialization_failure) in an RPC function causes infinite retries
Last edited: 10/14/2026
在你的 PL/pgSQL 函数中使用带有 SQLSTATE 40001 (serialization_failure) 的自定义 raise exception 会告诉 PostgREST 这个失败是暂时的,因此 PostgREST 会重试事务。这会让你的日志充满重复记录,并把一次 API 请求分成几个独立的 Postgres 事务。
🌐 A custom raise exception using SQLSTATE 40001 (serialization_failure) in your PL/pgSQL function tells PostgREST the failure is transient, so PostgREST retries the transaction. That floods your logs with duplicates and splits one API request into several independent Postgres transactions.
这个bug存在于 PostgREST 14 中,并在 PostgREST 16 中修复。关注Supabase 更新日志,以便在 PostgREST 16 发布时收到通知。
🌐 This bug is present in PostgREST 14 and is fixed in PostgREST 16. Follow the Supabase changelog to get notified when PostgREST 16 is released.
如何解决 #
🌐 How to resolve
为了解决这个问题,更新你的 Postgres 函数,使用标准的异常处理或者与 HTTP 状态码正确对应的 SQLSTATE 代码。
🌐 To resolve this, update your Postgres function to use standard exception handling or SQLSTATE codes that map correctly to HTTP status codes.
1. 修改 Raise 语句
用标准异常或符合 PostgREST 的代码替换自定义的 errcode 分配:
- 标准修复:将
raise exception using errcode = '40001', message = '...';改为raise exception 'YOUR_ERROR_MESSAGE';。这会默认使用 SQLSTATEP0001,不会触发重试循环。 - HTTP 映射:使用
PT前缀 映射到特定的 HTTP 状态码。例如,要返回 HTTP 409,使用:raise sqlstate 'PT409' using message = 'YOUR_ERROR_MESSAGE';
2. 终止挂起的后台进程
现有的循环进程必须手动停止——修复函数并不会停止已经在进行中的事务。你在 Postgres 日志(日志浏览器)中看到的重复错误条目中包含一个 process_id 字段,用于标识引发错误的确切后台进程,例如:
"process_id": 183165,"sql_state_code": "40001","user_name": "authenticator"你也可以在 pg_stat_activity 中通过过滤 authenticator 来找到候选后台,这是 PostgREST 连接的角色:
🌐 You can also find candidate backends in pg_stat_activity by filtering on authenticator, the role PostgREST connects as:
select pid, state, query_start, queryfrom pg_stat_activitywhere usename = 'authenticator'order by query_start;将 pid 与日志中的 process_id 匹配,以确认你找到了正确的后台,然后在 SQL 编辑器 中终止它:
🌐 Match the pid against the process_id from your logs to confirm you have the right backend, then terminate it from the SQL editor:
SELECT pg_terminate_backend(pid);
或者,你可以从控制面板重新启动项目,一次性清除所有挂起的后台。
🌐 Alternatively, you can restart the project from the dashboard to clear all hanging backends at once.