首页
/ Supabase Database:专属 Postgres 实例、自动生成的 API 与 RLS 访问控制解析

Supabase Database:专属 Postgres 实例、自动生成的 API 与 RLS 访问控制解析

2026-09-06 16:11:25作者:殷蕙予

Supabase 的核心理念是"每个项目就是一套专属的 Postgres 数据库"。本篇基于仓库内的产品文档(apps/www/content/md/database.md)并结合 docker/ 自托管栈的真实配置与初始化脚本,系统讲解 Supabase Database 的架构定位、自动生成的 REST/GraphQL API、连接池方案、Row Level Security 与 JWT 的集成机制、Postgres 扩展体系,以及备份、计算规格等技术细节,帮助读者理解 Supabase 数据库能力在自托管环境下的实际落地方式。

专属 Postgres,而非共享集群

官方文档对 Supabase Database 的定义非常明确:它不是"Postgres 兼容"的替代品,也不是多租户共享集群,而是每个项目独占一个可直接用任意 Postgres 客户端连接的 Postgres 实例。文档给出的定位可以概括为两点:

  • Dedicated Postgres:每个项目是隔离的 Postgres 实例,而不是共享租户;
  • 100% portable:可以自带已有的 Postgres 数据库,也可以随时导出迁移走。

从自托管栈的源码结构看,这一"专属实例"的定位体现在 docker-compose.yml 中:db 服务使用 supabase/postgres:17.6.1.136 镜像,数据目录通过命名卷 ./volumes/db/data:/var/lib/postgresql/data 持久化,其他服务(Studio、Auth、Storage、Realtime、meta)全部通过连接串指向同一个 POSTGRES_HOST/POSTGRES_DB。文档中提到的"连接任意 Postgres 客户端"在自托管场景下即为标准的 postgres:// 连接串;supavisor(连接池)容器将 POSTGRES_PORT(默认 5432)暴露到宿主机,客户端可像连接普通 Postgres 一样连接。

db 服务还通过挂载初始化脚本完成 Supabase 特有的数据库改造,包括 realtime.sql(创建 _realtime schema)、webhooks.sqlroles.sqljwt.sql_supabase.sql(内部数据分析)以及 logs.sqlpooler.sql(连接池支持)。这套初始化脚本说明:Supabase 的数据库能力全部构建在原生 Postgres 之上,扩展以 schema、角色和数据库级参数(GUC)的形式注入,不改动 Postgres 核心行为,这正是"可迁移性"承诺的底层保障。

自动生成 API:PostgREST 与 GraphQL 端点

文档将"Auto-generated APIs"列为核心特性:从数据库 schema 自动生成 REST(PostgREST)和 GraphQL(pg_graphql)API,无需编写后端代码

在自托管栈中,这一特性由 rest 服务实现,镜像为 postgrest/postgrest:v14.12(见 docker-compose.yml)。其关键配置项均可作为实操参考:

environment:
  PGRST_DB_URI: postgres://authenticator:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}
  PGRST_DB_SCHEMAS: ${PGRST_DB_SCHEMAS}          # 默认 public,graphql_public
  PGRST_DB_MAX_ROWS: ${PGRST_DB_MAX_ROWS:-1000}  # 单请求最大返回行数,默认 1000
  PGRST_DB_EXTRA_SEARCH_PATH: ${PGRST_DB_EXTRA_SEARCH_PATH:-public}
  PGRST_DB_ANON_ROLE: anon
  PGRST_JWT_SECRET: ${JWT_JWKS:-${JWT_SECRET}}   # 支持明文 secret / JWK / JWKS

几个值得注意的实现细节(依据 CONFIG.md 的配置参考):

  • PGRST_DB_SCHEMAS 决定哪些 schema 被暴露为 REST API,默认值为 public,graphql_public——graphql_public 的存在直接印证了文档中"GraphQL API 由 pg_graphql 生成"的说法:GraphQL 端点复用 PostgREST 的 /graphql 路由;
  • PGRST_DB_MAX_ROWS 默认 1000,即任何单个 REST 请求最多返回 1000 行,超出必须用分页(Range 头或 limit/offset);
  • PGRST_DB_URI 使用的是 authenticator 角色而非超级用户——这是 PostgREST 的标准安全模式,由它接管请求级角色切换(anon/authenticated/service_role);
  • PGRST_JWT_SECRET 优先读取 JWKS,回落到对称 JWT_SECRET,与 Auth 服务签发 JWT 的密钥体系保持一致,从而让 REST API 的请求级鉴权与 RLS 策略联动。

rest 服务还启用了管理端口(PGRST_ADMIN_SERVER_PORT: 3001,仅限 localhost),供 Studio 等内部组件做健康检查与配置变更。

连接与连接池:Supavisor 与 PgBouncer

文档"Technical Details"部分指出:连接方式包括直连 Postgres(connection string)通过 PgBouncer 风格的连接池(Supavisor)。仓库中两种方案都有完整实现,可对照选择。

默认方案:Supavisor

docker-compose.yml 中的 supavisor 服务(镜像 supabase/supavisor:2.9.5)是默认连接池:

ports:
  - ${POSTGRES_PORT}:5432                    # session/直连模式端口
  - ${POOLER_PROXY_PORT_TRANSACTION}:6543     # 事务模式端口
environment:
  POOLER_POOL_MODE: transaction
  POOLER_DEFAULT_POOL_SIZE: ${POOLER_DEFAULT_POOL_SIZE}
  POOLER_MAX_CLIENT_CONN: ${POOLER_MAX_CLIENT_CONN}
  DB_POOL_SIZE: ${POOLER_DB_POOL_SIZE}

Supavisor 的入口命令会先执行迁移(/app/bin/migrate),再加载 pooler.exs 中的租户配置。POOLER_TENANT_ID 决定了客户端连接串中的"租户"参数——这也是官方客户端 sessiontransaction 两种连接模式(对应连接串中 mode=transaction 参数或不同端口)背后的机制。

可替换方案:PgBouncer

docker-compose.pgbouncer.yml 提供了一个标准 PgBouncer 覆盖文件,使用时通过 !reset null 移除 Supavisor 服务,并加入 edoburu/pgbouncer:v1.25.2-p0 容器:

environment:
  DB_USER: pgbouncer                      # 以专用角色连接 Postgres
  AUTH_TYPE: scram-sha-256
  AUTH_QUERY: SELECT * FROM pgbouncer.get_auth($$1)  # 按需查询其他角色口令
  POOL_MODE: transaction                  # 可选 session/transaction/statement
  DEFAULT_POOL_SIZE: ${POOLER_DEFAULT_POOL_SIZE}
  MAX_CLIENT_CONN: ${POOLER_MAX_CLIENT_CONN}

其用法在文件头部注释中写明:docker compose -f docker-compose.yml -f docker-compose.pgbouncer.yml up -d,或 sh run.sh config add pgbouncer。值得注意的是 AUTH_QUERY 机制:PgBouncer 自身只持有 pgbouncer 角色的口令,转发连接时通过 get_auth() 函数按需换取目标角色的口令,避免了在配置中存放所有角色密码。该文件同时提醒:若直接暴露 Postgres 端口到外部流量,必须在网络层限制访问。

Row Level Security 与 JWT 的深度集成

文档将 RLS 描述为"使用 Postgres RLS 策略实现细粒度访问控制,并与 Supabase Auth 的 JWT 集成"。仓库中的初始化脚本展示了这套集成的具体落地方式。

JWT 设置注入数据库jwt.sql 在数据库初始化阶段执行:

ALTER DATABASE postgres SET "app.settings.jwt_secret" TO :'jwt_secret';
ALTER DATABASE postgres SET "app.settings.jwt_exp" TO :'jwt_exp';

这把 JWT_SECRET 与 token 有效期(JWT_EXPIRY)作为数据库级 GUC 写入。RLS 策略中常用的 auth.jwt() 辅助函数(读取 request.jwt.claims 头)以及 PostgREST 的 PGRST_APP_SETTINGS_JWT_SECRET 都依赖这套密钥配置,使得策略里能直接比较 JWT 中的 raw_user_idrole 等声明:

-- 典型 RLS 策略模式:仅允许访问属于当前登录用户的行
alter table profiles enable row level security;
create policy "Users can view their own profile"
  on profiles for select
  using (auth.uid() = (select id from auth.users where id = auth.uid()));

角色体系由初始化脚本统一改造roles.sql 在首次初始化时为 authenticatorpgbouncersupabase_auth_adminsupabase_functions_adminsupabase_storage_admin 五个关键角色统一设置密码(取自 POSTGRES_PASSWORD 环境变量)。从源码结构看,anon/authenticated/service_role 三个 API 角色由 supabase/postgres 镜像本身预置,webhooks.sql 等脚本再为它们授予 supabase_functions schema 的使用权限——这意味着 RLS 策略的"三档权限模型"(匿名、登录用户、服务角色)是整个栈共享的一致概念。

Auth 服务侧的对应配置docker-compose.ymlauth(GoTrue)服务的关键环境变量与 RLS 集成直接相关:GOTRUE_JWT_AUD: authenticatedGOTRUE_JWT_DEFAULT_GROUP_NAME: authenticated 决定签发的 JWT 中 audrole 声明的默认值,GOTRUE_JWT_EXP 控制 token 生命周期,GOTRUE_JWT_ADMIN_ROLES: service_role 声明管理员角色。PostgREST 读到这些声明后切换到对应 Postgres 角色,RLS 策略即按角色执行,从而实现了"JWT → 角色 → 行级权限"的完整链路。

扩展体系:从 40+ 一键启用到扩展清单源码

文档声称 Supabase 支持 40 余种 Postgres 扩展(pgvector、PostGIS、pg_cron、pg_stat_statements 等)且可一键启用。仓库中 packages/shared-data/extensions.json 是扩展目录的机器可读清单,Studio 的扩展管理页面即基于此渲染:每项包含 namecommenttags(Utility / Index / Data Type / Search / Geo 等分类)、官方文档 link 以及可选的 github_url/product 信息。示例条目:

{
  "name": "pg_net",
  "comment": "HTTP requests from SQL",
  "tags": ["Utility"],
  "link": "https://github.com/prisma/pg_net"
}

清单中涵盖了 autoincbloombtree_gincitextcubedblinkdict_intfuzzystrmatchhstoreaddress_standardizer(PostGIS 系)等条目,与文档"一键启用 Postgres 扩展"的描述一一对应——Studio 的启用操作最终就是执行 CREATE EXTENSION

另一个扩展在 Webhooks 机制中直接可见:webhooks.sql 首行即 CREATE EXTENSION IF NOT EXISTS pg_net SCHEMA extensions;,说明"Database Webhooks"功能依赖 pg_net 在数据库内发起 HTTP 请求(见下一节)。

数据库 Webhooks:表事件触发 HTTP 回调

文档将"Database Webhooks"定义为:在表的增删改事件上触发 Edge Functions 或外部 HTTP 端点。其实现完整保留在 webhooks.sql 中,核心是一个 PL/pgSQL 触发器函数:

CREATE FUNCTION supabase_functions.http_request()
  RETURNS trigger
  LANGUAGE plpgsql
  AS $function$
  DECLARE
    request_id bigint;
    payload jsonb;
    url text := TG_ARGV[0]::text;       -- 触发时传入的目标 URL
    method text := TG_ARGV[1]::text;    -- HTTP 方法
    headers jsonb DEFAULT '{}'::jsonb;  -- 自定义请求头
    params jsonb DEFAULT '{}'::jsonb;   -- 查询参数
    timeout_ms integer DEFAULT 1000;    -- 请求超时,默认 1000ms
  ...

该函数从 TG_ARGV 数组读取 URL、方法与可选的 headers/params/timeout,并通过 supabase_functions.hooks 表记录每次触发的审计日志(含 request_id 关联 pg_net 的请求状态)。脚本同时为 postgresanonauthenticatedservice_role 四个角色授予 supabase_functions schema 的默认权限。使用方式是在目标表上创建触发器并传入目标端点参数,例如:

create trigger notify_on_insert
  after insert on orders
  for each row execute function
  supabase_functions.http_request('https://api.example.com/hook', 'POST');

Realtime:通过 WebSocket 订阅数据变更

文档列出 Realtime 能力为"通过 WebSockets 订阅 INSERT、UPDATE、DELETE 等变更"。自托管栈中对应 realtime 服务(镜像 supabase/realtime:v2.102.3,Elixir 实现),其 docker-compose.yml 配置展示了它如何挂在数据库上:

environment:
  DB_USER: supabase_admin
  DB_AFTER_CONNECT_QUERY: 'SET search_path TO _realtime'   # 初始化脚本创建的 schema
  DB_ENC_KEY: ${REALTIME_DB_ENC_KEY:-supabaserealtime}
  SEED_SELF_HOST: "true"

DB_AFTER_CONNECT_QUERYrealtime.sql 创建的 _realtime schema 相呼应:Realtime 服务在该 schema 中维护复制槽(replication slot)与订阅关系,将 Postgres 的 WAL 变更翻译成 WebSocket 事件广播。RLIMIT_NOFILE: "10000" 则表明其为高并发连接场景调高文件描述符上限。需要指出:_realtime schema 只是"挂载点",实际的复制逻辑由 Realtime 服务在连接后创建,属于从源码结构可以推断的实现方式。

编辑器能力:Table Editor 与 SQL Editor

文档将 Table Editor(表格化的数据查看/编辑界面,支持关系、JSON 列与外键查找)和 SQL Editor(带自动补全与语法高亮、可保存查询)列为产品级特性。从仓库源码结构看,这两个编辑器均实现于 apps/studio 中,并依赖两个后端组件协同:

  • postgres-meta(meta 服务,镜像 supabase/postgres-meta:v0.96.6:提供 schema 自省、表结构、角色管理、查询执行等 REST API。CONFIG.md 明确 STUDIO_PG_META_URL(如 http://meta:8080)为必填项;Studio 通过它执行 SQL 编辑器的读写查询,且 POSTGRES_USER_READ_WRITE(默认 postgres)决定 SQL 编辑器使用的角色;
  • Snippets 持久化SNIPPETS_MANAGEMENT_FOLDER: /app/snippets 挂载自 ./volumes/snippets,即 SQL Editor 中"保存查询"(Snippets)的落盘位置;Edge Functions 的在线编辑源文件则挂载自 ./volumes/functions(只读)。

技术细节总览:引擎、客户端、备份与计算规格

文档"Technical Details"一节给出了平台侧的硬性指标,汇总如下(注意:备份与计算规格描述的是 Supabase 托管平台能力,自托管部署的备份需自行规划,docker/README.md 的"Important Notes"也明确提醒默认配置不面向生产、需要自行设置备份流程):

维度 说明
引擎 PostgreSQL 最新稳定版;自托管栈当前镜像为 supabase/postgres:17.6.1.136,并提供 docker-compose.pg17.yml 覆盖文件与 upgrade-pg17.sh 就地升级脚本(另有 docker-compose.pg15.yml 兼容 PG 15 部署)
连接 直连连接串 + 连接池(默认 Supavisor,可切换 PgBouncer 事务模式)
客户端库 JavaScript、Python、Dart (Flutter)、Swift、Kotlin、C#
备份 托管平台提供每日自动备份,Point-in-Time Recovery 为可选附加项
计算 从 Micro(2 核 ARM)到 16XL+(64 核 ARM 及以上)可配置,支持自动扩缩

其中引擎版本从源码得到双重印证:默认 compose 文件的 db 服务注释"To upgrade an existing Postgres 15 database in place, see utils/upgrade-pg17.sh"与镜像标签一致,test-pg17-upgrade.sh 则提供了升级流程的自动化测试。

周边能力:Branching、只读副本与 Pipelines

文档还列出三项面向规模化场景的能力,它们属于托管平台特性,自托管栈中无对应服务,此处按文档原样说明并标注适用范围:

  • Database Branching:创建与 git 分支同步的隔离数据库分支,支持 Vercel Preview 工作流——适用于按 PR 隔离数据环境的团队协作;
  • Read Replicas:跨多地域分布读流量以降低延迟、提升吞吐;
  • Supabase Pipelines:将已发布的 Postgres 数据以近实时方式同步到分析型目的地。

小结与可继续深入的路径

Supabase Database 的架构主张可以浓缩为一句话:原生专属 Postgres + 数据库内注入的元能力(角色、GUC、扩展 schema)+ 围绕数据库的一组网关服务。仓库中的自托管栈完整呈现了这一架构:PostgREST 从 schema 生成 REST/GraphQL API,Supavisor/PgBouncer 承担连接池,jwt.sql/roles.sql 初始化 RLS 与 JWT 集成的基础,webhooks.sqlpg_net 实现表事件触发,realtime.sql 为变更广播预留 _realtime schema,extensions.json 则驱动 Studio 中的一键扩展管理。

若需继续深入,建议从以下仓库路径入手:

登录后查看全文
热门项目推荐
相关项目推荐