很多人在使用 Supabase 的时候都会遇到一个让人抓狂的问题:RLS(行级安全)明明开了,但执行查询要么返回 permission denied,要么干脆给你一个空数组,而表里明明有数据。原因通常并不复杂——不是策略压根没启用,就是策略条件跟你当前会话角色或上下文对不上号。糟糕的是,Supabase 不会告诉你哪条策略拦住了请求,它只会默默地拒绝,然后让你自己猜。

确认 RLS 已真正启用
第一步,回到 Supabase Studio,进入 Table Editor,找到目标表,看看表名右侧那个「RLS」开关是不是绿色的「Enabled」。如果是灰色或显示「Disabled」,点一下开启。但千万记住:开启开关只是第一步,策略本身必须存在且通过验证才算真正生效。
更保险的做法是直接跑 SQL 确认。在 SQL Editor 里执行:SELECT relname, relrowsecurity FROM pg_class WHERE relname = 'your_table_name';。如果返回的 relrowsecurity 是 false,说明 RLS 实际上没开启,这时候需要手动执行 ALTER TABLE your_table_name ENABLE ROW LEVEL SECURITY; 来强制启用。
策略写错?先查 auth.uid() 是否可取
很多策略都依赖 auth.uid() 来获取当前用户 ID。如果 auth.uid() 本身是 NULL,那所有基于它的策略必然翻车。怎么查?在 SQL Editor 里跑一行:SELECT auth.uid();。如果返回 NULL,说明当前会话没有认证——可能是用了 anon key 但用户没登录,或者是直接用 service_role 密钥绕过了 auth 上下文。
前端调用时务必使用已登录用户的客户端。确保初始化 Supabase 客户端后,已经调用了 await supabase.auth.signInWithPassword(...) 之类的方法完成登录。anon key 搭配未登录用户,auth.uid() 一定是 NULL,所有依赖它的策略都会直接失败,这是最隐蔽的坑之一。
为了验证 auth.uid() 是否能正确捕获真实用户 ID,可以插入一条测试记录,然后用已登录账号执行 SELECT * FROM your_table WHERE user_id = auth.uid();。如果能查到,说明 auth.uid() 可用;查不到的话,就该检查 user_id 字段类型了——常见错误是设成了 TEXT 而非 UUID,结果跟 auth.uid() 返回的 uuid 格式对不上,自然匹配不上。
策略语法与逻辑调试三步法
第一步:确认策略绑定的操作类型。比如你想控制的是读取权限,却写了个 FOR INSERT 的策略,那 SELECT 操作仍然会被默认拒绝。策略的 FOR 子句决定了它针对哪种命令生效。
第二步:搞明白 USING 和 WITH CHECK 的区别。SELECT、UPDATE、DELETE 用 USING (condition) 来控制「能否看到或修改某一行」;INSERT 和 UPDATE 则用 WITH CHECK (condition) 来控制「插入或更新后的值是否合规」。很多人漏掉 WITH CHECK,结果 INSERT 成功了但 UPDATE 失败,或者允许插入违反规则的数据。
第三步:用真实角色模拟请求,这才是最接近前端真实环境的调试方法。在 SQL Editor 右上角把「Role」切换成 anon 或 authenticated,然后再执行 SELECT 或 INSERT 语句。这比直接用 service_role 查表有价值得多——它能暴露那些只在特定角色下才会出现的策略问题。
常见报错对应修复清单
报错:permission denied for table xxx → 大概率是忘了给角色授权。RLS 开启后,PostgreSQL 会忽略默认权限,必须显式执行 GRANT SELECT ON TABLE xxx TO anon; 或 TO authenticated; 才能让对应角色访问表本身。这一步容易忽略,但缺了它什么都查不到。
报错:new row violates row-level security policy → 问题出在 INSERT 或 UPDATE 的 WITH CHECK 条件上。比如策略写的是 WITH CHECK (auth.uid() = user_id AND status = 'pending'),但代码里却传了 status: 'published',这一行直接就被策略拦住了。检查一下你插入的数据是否符合 WITH CHECK 的逻辑。
报错:查询返回空数组,但表里明明有数据 → 这时候需要打开浏览器开发者工具,切到 Network 标签页,找到对应的 Supabase 请求,看看 Response Headers 里的 x-supabase-api-version 以及响应体里有没有 error 字段。另外,检查前端调用时是否传了 { count: 'exact' } 之类的参数——某些参数会触发隐式的策略校验路径,导致意想不到的结果。
