跳到主要内容

数据库函数

数据库函数是运行在 PostgreSQL 内部的可复用逻辑。你可以把复杂 SQL、事务性写入、聚合统计、权限辅助判断等能力封装为函数,再通过 SQL、服务端直连或 HTTP API 的 RPC 方式调用。

数据库函数适合靠近数据层执行的逻辑,但不适合执行耗时外部调用。涉及第三方接口、长任务或复杂业务编排时,建议放在云函数或云托管中处理。

适用场景

场景说明
复用复杂查询将多表关联、过滤、排序和聚合封装为一个函数
事务性写入在一个数据库事务内完成多表写入或状态流转
数据校验在写入前检查库存、额度、状态等业务约束
RPC API通过 HTTP API 调用函数,向前端或第三方系统暴露受控能力
权限辅助封装团队成员、资源归属等权限判断逻辑

创建数据库函数

可以通过控制台或 SQL 语句创建数据库函数。当前云开发控制台的 PostgreSQL 管理界面与 Supabase 控制台保持一致,适合查看、创建和管理数据库函数。

  1. 进入 云开发平台/PostgreSQL 数据库 管理页面。
  2. 选择目标环境,进入数据库管理界面。
  3. 打开「数据库函数」管理页面。
  4. 点击「新建函数」。
  5. 填写函数名称、Schema、参数、返回类型和函数体。
  6. 按函数逻辑选择语言:简单 SQL 表达式或查询优先选择 LANGUAGE SQL;需要变量、条件分支、异常处理或多条语句时选择 LANGUAGE plpgsql
  7. 按需配置函数稳定性、权限模式等选项,确认后提交。

控制台适合创建常规函数和查看已有函数。涉及迁移脚本、批量发布或需要代码评审的变更时,建议使用 SQL 语句统一管理。

使用 PL/pgSQL 编写过程逻辑

只有当函数需要变量、条件分支、异常处理或多条语句时,才使用 LANGUAGE plpgsql。下面的示例需要 DECLARE 变量、IF 判断、RAISE EXCEPTION 和多条写入语句,因此必须使用 LANGUAGE plpgsql

create or replace function public.create_order(
p_user_id varchar,
p_product_id bigint,
p_quantity integer
)
returns bigint
LANGUAGE plpgsql
as $$
declare
v_order_id bigint;
v_stock integer;
begin
select stock into v_stock
from public.products
where id = p_product_id
for update;

if v_stock is null then
raise exception 'product not found';
end if;

if v_stock < p_quantity then
raise exception 'insufficient stock';
end if;

update public.products
set stock = stock - p_quantity
where id = p_product_id;

insert into public.orders(user_id, product_id, quantity, status)
values (p_user_id, p_product_id, p_quantity, 'created')
returning id into v_order_id;

return v_order_id;
end;
$$;

函数在单个数据库事务内执行。如果函数内部抛出异常,当前调用会回滚。

返回表数据

函数可以返回表结构,适合封装搜索、筛选和统计查询。

create or replace function public.search_articles(keyword text)
returns table (
id bigint,
title text,
created_at timestamptz
)
LANGUAGE SQL
stable
as $$
select a.id, a.title, a.created_at
from public.articles a
where a.title ilike '%' || keyword || '%'
order by a.created_at desc
limit 20;
$$;

调用:

select * from public.search_articles('cloudbase');

如果函数通过 HTTP API 暴露为 RPC,返回表数据的函数还可以继续使用 PostgREST 支持的字段选择、过滤、排序和分页能力。

返回 JSON

需要返回汇总结果或多层结构时,可以返回 jsonjsonb

create or replace function public.get_order_stats()
returns jsonb
LANGUAGE SQL
stable
as $$
select jsonb_build_object(
'total', count(*),
'paid', count(*) filter (where status = 'paid'),
'pending', count(*) filter (where status = 'pending')
)
from public.orders;
$$;

通过 HTTP API 调用

CloudBase PostgreSQL 的 HTTP API 基于 PostgREST 协议,可以通过 /rpc/:function_name 调用数据库函数。

curl -X POST 'https://<envId>.api.tcloudbasegateway.com/v1/rdb/rest/rpc/add_numbers' \
-H 'Authorization: Bearer <access_token>' \
-H 'Content-Type: application/json' \
-d '{ "a": 1, "b": 2 }'

更完整的请求、返回和过滤示例请参考 调用 RPC通过 HTTP API 访问

权限控制

函数是否可被调用由 execute 权限控制。生产环境建议先回收默认权限,再按角色授予。

revoke execute on function public.add_numbers(integer, integer) from public;

grant execute on function public.add_numbers(integer, integer) to authenticated;

如果函数会被 HTTP API 调用,需要确认调用用户对应的角色拥有 execute 权限,并且函数内部访问的表也满足 RLS 或函数安全模式要求。

Security invoker 与 security definer

PostgreSQL 函数默认以调用者权限执行,等价于 security invoker。这通常更安全,因为表权限和 RLS 会按调用者身份生效。

create or replace function public.list_my_orders()
returns setof public.orders
LANGUAGE SQL
security invoker
stable
as $$
select *
from public.orders
where user_id = (select auth.uid());
$$;

security definer 会以函数拥有者权限执行,可能绕过调用者权限边界。只有在确实需要封装受控的高权限操作时才使用。

create or replace function public.get_team_member_count(p_team_id bigint)
returns integer
LANGUAGE SQL
security definer
set search_path = public
stable
as $$
select count(*)::integer
from public.team_members
where team_id = p_team_id;
$$;

使用 security definer 时必须显式设置 search_path,并严格控制 execute 授权,避免用户借函数访问不应访问的数据。

调试和错误处理

可以使用 raise notice 输出调试信息,使用 raise exception 主动中断调用。

raise notice 'creating order for user %, product %', p_user_id, p_product_id;
raise exception 'insufficient stock';

上线前应移除不必要的 notice,并让异常信息保持简洁、可定位,不要包含密码、Token 或敏感数据。

变更和发布

create or replace function 可以更新函数体,但修改参数类型、参数顺序或返回类型通常需要先删除旧函数再创建新函数。

drop function if exists public.add_numbers(integer, integer);

如果函数通过 HTTP API 暴露,修改函数签名后可能需要等待或刷新 Schema Cache。相关排查请参考 Schema Cache 问题

最佳实践

  • 函数保持短小,聚焦数据库内逻辑。
  • 参数使用明确命名,避免依赖参数顺序理解业务含义。
  • 对外暴露的函数要控制 execute 权限。
  • 优先使用 security invoker;谨慎使用 security definer
  • 在函数中访问高频过滤字段时,为相关字段创建索引。
  • 复杂业务编排放在云函数或云托管中,数据库函数只处理需要靠近数据的一致性逻辑。