3.2 数据管理与 CRUD 操作


3.2 数据管理与 CRUD 操作

直接定义:CRUD 就是 Create(增)、Read(查)、Update(改)、Delete(删)四件事。在 Supabase 里,你不用写 REST 路由去实现它们——PostgREST 已经把表直接变成带这四个动作的 HTTP 接口,supabase-js 只是把这层接口包成了带类型的函数。这一节我们把四件事逐个写通,并讲 RLS 怎么在背后默默守门。

它到底解决了什么:CRUD 不必手写

先用一段 SQL 建一张 todos 表,并写一条"只能操作自己数据"的策略,这是后面 CRUD 安全的前提:

create table public.todos ( id bigint generated always as identity primary key, user_id uuid references auth.users(id) on delete cascade, content text not null, done boolean default false, created_at timestamptz default now() ); alter table public.todos enable row level security; create policy "自己的待办自己管" on public.todos for all using (auth.uid() = user_id) with check (auth.uid() = user_id);

这里 for all 一次性覆盖增改删查,using 管读、with check 管写,都要过 auth.uid() = user_id

Create:插入

const { data, error } = await supabase .from('todos') .insert({ user_id: (await supabase.auth.getUser()).data.user!.id, content: '写教程' }) .select() console.log(data) // [{ id: 1, content: '写教程', done: false, ... }] console.log(error) // null

.select() 让插入后返回刚写的行,方便前端直接拿到带 id 的对象。如果不加 .select(),默认只返回状态不返回数据。

Read:查询与过滤

PostgREST 的查询参数被 supabase-js 封装成链式方法。常用过滤:

// 查当前用户未完成的待办,按创建时间倒序,最多 10 条 const { data } = await supabase .from('todos') .select('id, content, done') .eq('done', false) .order('created_at', { ascending: false }) .limit(10) // 模糊搜索 const { data: hit } = await supabase .from('todos') .select('*') .ilike('content', '%教程%')

注意:RLS 已经把结果限定在当前用户,所以这里不用再手动加 user_id 过滤——加了是冗余,不加也安全。这正是 RLS 的价值:查询代码更干净,且不会因为"忘了写条件"而泄数据。

Update 与 Delete

// 标记完成 await supabase.from('todos').update({ done: true }).eq('id', 1) // 删除 await supabase.from('todos').delete().eq('id', 1)

eq('id', 1) 是行定位条件。即便你写成 delete().neq('id', 0)(想删全部),RLS 的 using (auth.uid() = user_id) 仍会把范围收回到当前用户,别人家的待办删不掉。

下面 SVG 把"一次 CRUD 请求如何被 RLS 收窄"画出来,帮助你建立安全直觉:

03-02-fig01

展开案例:多人协作待办里的越权尝试

背景:做一个团队待办,A 和 B 是同项目成员,但待办是私人拥有的。A 试图删 B 的一条待办。

操作过程:

  1. A 登录后拿到自己的 JWT,构造 delete().eq('id', B的待办id)
  2. PostgREST 执行时,RLS 策略 auth.uid() = user_id 生效:B的待办id 对应的 user_id 是 B,不等于 A 的 auth.uid()
  3. 删除影响行数为 0,A 什么也没删掉,且不会报错暴露"那条不属于你"(返回空结果,不泄露存在性)。

结果:A 的越权删除静默失败,数据安全。

解读:很多人担心"返回空是不是信息泄露"。这里 RLS 返回 0 行,对外表现就是"没有符合条件的行",不会告诉 A"这条存在但归别人"——这点 Supabase 处理得比较克制。

变式:如果业务要"团队成员可见彼此待办但只能改自己的",就写两条策略:一条 for select using (team_id 相同) 放开读,一条 for update using (auth.uid() = user_id) 收紧写。读宽写窄是协作系统的常用模式。

三句话带走本节

  • CRUD 由 PostgREST 自动提供,supabase-js 封装成链式调用。
  • RLS 在数据库层强制收窄操作范围,前端漏写条件也不泄数据。
  • 读宽写窄是协作系统的常见策略组合。

提醒:用 service_role 密钥做 CRUD 会完全绕过 RLS,等于裸库操作。只在受信任的服务端(如带校验的 Edge Function)使用,且务必自行在代码里加权限判断。

建议:我们建议所有业务表默认 enable row level security 并至少一条策略,即使暂时 using (true) 全开放,也留好收紧的位置。空表无策略 + 未开 RLS 是新手最常见的数据裸奔原因。

CRUD 之外的批量与幂等写法

真实业务里你不只做单行增删改。下面两招能避免常见性能与重复写入问题。

批量插入:一次网络往返插多行,比循环插快得多。

// 一次插入多条待办,仍是单次请求 const { data, error } = await supabase .from('todos') .insert([ { user_id: me, content: '写文档' }, { user_id: me, content: '改策略' }, { user_id: me, content: '跑测试' }, ]) .select() console.log(data?.length) // 3

UPSERT(存在则更新、不存在则插入),靠唯一约束避免重复:

-- 给 todos 的 (user_id, content) 加唯一约束,撑起 upsert alter table public.todos add constraint todos_user_content_uniq unique (user_id, content);
// 重复内容不会插出第二份,而是更新 done 字段 await supabase .from('todos') .upsert({ user_id: me, content: '写文档', done: true }) .select()

💡 关键直觉:凡是"循环里一次次调接口"的写法,都该先想能不能合成一条批量或 upsert。多一次往返就多一次延迟、多占一个连接,量上来就会变成 4.4 节讲的 N+1 慢查询。

再展开一个案例:导入Excel 时的重复数据

背景:运营从 Excel 导了 500 条商品到 products 表,导了两次,结果出现两批完全相同的行,前端列表显示重复。

操作过程:

  1. 先给 (sku) 加唯一约束,让重复无处容身:
alter table public.products add constraint products_sku_uniq unique (sku);
  1. 用 upsert 重导,第二次遇到相同 sku 自动更新而非新增:
await supabase .from('products') .upsert(rows.map(r => ({ sku: r.sku, name: r.name, price: r.price }))) .select()
  1. 已存在的重复行,用一条 delete 保留每组 sku 的第一条清掉:
delete from public.products p where p.ctid not in ( select min(ctid) from public.products group by sku );

结果:重复清零,后续导入永不重复。

解读:重复数据本质是"缺唯一约束 + 用 insert 而非 upsert"。约束是数据库的硬保证,比在应用层先 select 再判断可靠——并发时应用层判断会漏。

⚠️ 常见坑:加唯一约束前表里已有重复数据,约束会建失败。务必先清重(如上 ctid 法)再 add constraint,否则约束一直挂不上。

下一节我们专门讲认证与授权:登录、第三方登录、以及 JWT 和 RLS 怎么咬合。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U