常见错误速查
CloudBase PostgreSQL 数据库使用过程中出错时,多半涉及 GRANT / RLS Policy / JWT 三层。本文按「错误现象 → 根因 → 排查」的格式列出常见问题,方便快速定位。
💡 阅读建议:先从「快速排查决策树」入手定位大类问题,再翻具体小节。
快速排查决策树
请求失败 / 拿不到数据
│
├─ HTTP 401 ? → JWT 缺失 / 过期 / 签名错误 → 见 [§ JWT 与认证](#jwt)
├─ HTTP 403 ? → 表级 GRANT 不足 → 见 [§ 表级 GRANT](#grant)
│ → 或 RLS WITH CHECK 拒绝写入
├─ HTTP 404 ? → 表 / RPC 函数不存在 → 见 [§ 资源不存在](#not-found)
├─ HTTP 406 ? → 单对象返回不符合数量约束 → 见 [§ 返回格式](#return-format)
├─ HTTP 409 ? → 唯一约束 / 外键冲突 → 检查表约束
├─ HTTP 500 ? → 函数内 RAISE / 实例错误 → 见 [§ 内部错误](#internal)
└─ 200 但数据空 ? → RLS 静默过滤 → 见 [§ RLS 静默过滤](#silent)
JWT 与认证
401 Unauthorized / JWT expired
| 现象 | 根因 | 排查 |
|---|---|---|
完全未带 Authorization Header | 客户端忘记携带 | 前端确保使用 SDK(自动携带)或手动添加 Authorization: Bearer <token> |
JWT expired | access_token 过期 | 前端调用刷新或重新登录;Publishable Key / API Key 永不过期 |
Invalid JWT | 签名错误、Token 被篡改 | 检查是否复制粘贴时混入空格、换行;确认环境 ID 与 Token 中 aud / project_id 匹配 |
iss claim mismatch | 跨环境使用了别的环境的 Token | 前端环境 ID 必须与 Token 签发环境一致 |
检查当前 Token 中的 role / sub
最快的方法是用 jwt.io 或后端解码(不验签):
echo "<your-jwt>" | awk -F. '{print $2}' | base64 -d 2>/dev/null
应能看到 role: "anon" | "authenticated" | "service_role" 与 sub: "<uuid>"。
表级 GRANT
permission denied for table xxx
ERROR: permission denied for table todos
| 根因 | 排查 |
|---|---|
| 角色没有对应 DML 的 GRANT | \dp public.todos 检查;按需 GRANT SELECT/INSERT/UPDATE/DELETE ON public.todos TO <role>; |
| RLS Policy 已写但漏掉 GRANT | RLS 是第二层,GRANT 是第一层。两层都要过 |
| 用了非业务角色 | 应用流量必须落到 anon / authenticated / service_role 三者之一,其他系统角色不可用 |
permission denied for sequence xxx_id_seq
ERROR: permission denied for sequence todos_id_seq
原因:serial / bigserial 主键背后是 SEQUENCE 对象,授予表的 INSERT 不会自动授予 sequence 权限。
修复:
GRANT USAGE, SELECT ON SEQUENCE public.todos_id_seq TO authenticated;
GRANT USAGE, SELECT ON SEQUENCE public.todos_id_seq TO service_role;
一个表的 sequence 名通常是
<table>_<column>_seq,可用\ds public.*查看。
RLS Policy
new row violates row-level security policy for table "xxx"
ERROR: new row violates row-level security policy for table "orders"
| 根因 | 排查 |
|---|---|
WITH CHECK 不通过 | 写入的某字段不满足 Policy。最常见:前端伪造 owner_id/buyer_id 与 JWT sub 不一致 → 让数据库 DEFAULT 自动绑定,前端别传 |
| INSERT Policy 缺失 | 启用 RLS 后必须为每种角色明确写 INSERT Policy(或角色具备 BYPASSRLS) |
UPDATE Policy 没写 WITH CHECK | 只写 USING 时,UPDATE 后行不会被检查;建议 USING + WITH CHECK 配套 |
200 OK 但 SELECT 返回空数组(RLS 静默过滤)
特征:HTTP 200,无报错,但 data 是 [] 或 null。
| 根因 | 排查 |
|---|---|
当前角色(如 anon)没有匹配的 SELECT Policy | 即使加 ?owner_id=eq.xxx 过滤也无济于事,先得通过 RLS |
| Policy 条件没匹配上当前用户 | 用 ExecutePGSql 指定 Role: authenticated 模拟执行,看是不是预期 |
current_setting('request.jwt.claims', true) 为空 | 检查请求是否真的带了 JWT、网关是否成功解析(一般直接 select current_setting(...) 在 SQL 编辑器里反而看不到,是 PostgREST 才会注入) |
调试技巧:用 ExecutePGSql 模拟特定角色
{
"EnvId": "<envId>",
"Sql": "SELECT * FROM public.todos",
"Role": "authenticated"
}
不用真的发 HTTP 请求即可验证 Policy 是否生效。
资源不存在
404 Not Found 调用 RPC 时
| 根因 | 排查 |
|---|---|
| 函数名拼错 | 与 SQL 中 CREATE FUNCTION 名称完全一致(含 schema) |
函数不在 public schema 下且未配置 db-schemas | 默认仅 public 下的函数会被 PostgREST 暴露 |
| 函数刚创建,PostgREST metadata 未刷新 | 等几秒重试,或尝试通知后端刷新 |
| 参数签名不匹配 | PostgREST 通过参数名 + 类型寻址;JSON body 中所有 key 必须与函数 argname 完全相同 |
404 Not Found 访问表时
{"message":"relation \"public.todoes\" does not exist"}
- 表名、schema 拼错
- 表不在
publicschema 中(需要Accept-Profile: <schema>Header 切换)
返回格式
406 Not Acceptable JSON object requested, multiple (or no) rows returned
{"message":"JSON object requested, multiple (or no) rows returned"}
原因:你设置了 Accept: application/vnd.pgrst.object+json,但结果不是恰好 1 行。
修复:要么去掉该 Header,要么用 .single() / .maybeSingle() 并保证查询条件能精确匹配 1 行。
写入后想拿到完整行
加 Prefer: return=representation:
curl -X POST '.../v1/rdb/rest/orders' \
-H 'Authorization: Bearer <token>' \
-H 'Prefer: return=representation' \
-H 'Content-Type: application/json' \
-d '{"product_id":1, ...}'
ExecutePGSql 错误
InternalError 执行 DDL 失败
部分 DDL(CREATE/ALTER/DROP/GRANT/REVOKE/TRUNCATE/COMMENT 等)直接执行可能返回 InternalError。
修复:用 DO $$ BEGIN EXECUTE '...'; END $$ 包装重试:
DO $$ BEGIN EXECUTE 'CREATE TABLE public.products (id serial PRIMARY KEY, name text)'; END $$;
字符串内的单引号
'需转义为''。
每次 API 调用只能执行一条 SQL
ExecutePGSql 不支持多语句拼接,请按 ; 拆分逐条调用。
permission denied(带 Role 参数时)
带了 "Role": "authenticated" 参数后,被以普通角 色执行;触发了 GRANT/RLS。这不是 bug——这正是它的用途。如需绕过权限调试,去掉 Role 参数。
内部错误
500 Internal Server Error 来自函数 RAISE
{"message":"商品不存在或库存不足"}
这是函数体内 RAISE EXCEPTION '...' 抛出的业务错误,正常的失败链路。前端按业务文案处理即可。
实例不可用 / 503
数据库实例临时故障或重启。客户端做指数退避重试即可。
网关错误
INVALID_REQUEST / PERMISSION_DENIED / RESOURCE_NOT_FOUND
参考 HTTP API - PostgREST RESTful API 的错误码表,与 PostgreSQL 原生错误的对应关系:
| 网关错误码 | HTTP | 含义 |
|---|---|---|
INVALID_PARAM | 400 | 请求参数无效 |
INVALID_REQUEST | 400 / 406 | 请求体无效 / 单对象数量约束不满足 |
PERMISSION_DENIED | 401 / 403 | 鉴权失败 / 权限不足 |
RESOURCE_NOT_FOUND | 404 | 表 / 函数不存在 |
SYS_ERR | 500 | 系统内部错误 |
OPERATION_FAILED | 503 | 数据库连接失败 |
RESOURCE_UNAVAILABLE | 503 | 数据库不可用 |
通用排查清单
遇到数据库相关问题,按下面顺序自查 80% 都能定位:
- JWT 是否带了?
Authorization: Bearer <token>不能漏 - JWT 中
role是不是预期的? 用 base64 解 payload 看一眼 - 表级 GRANT 是否给了对应角色?
\dp <table>检查 - RLS 是否启用?
SELECT relname, relrowsecurity FROM pg_class WHERE relname='xxx'; - 该角色 + 该操作有没有匹配的 Policy?
\d+ <table>看 Policy 列表 - Policy 的 USING / WITH CHECK 是否真的对当前用户成立? 用
ExecutePGSql带Role模拟执行 serial主键忘记授 SEQUENCE 权限了吗?