使用声明式 Schemas 简化数据库管理
LW14: 第 4 天 使用声明式 Schemas 简化数据库管理 了解更多
使用声明式 Schemas 简化数据库管理
2025 年 4 月 3 日 • 7 分钟阅读
今天我们发布了声明式 schemas,以简化管理和维护复杂的数据库 schemas。 使用声明式 schemas,您可以以清晰、集中和版本控制的方式定义数据库结构。
什么是声明式 Schemas?#
声明式 schemas 将数据库的最终期望状态存储在 .sql
文件中,这些文件可以与项目一起保存并进行版本控制。 例如,这是一个经典的 products
表的声明式 schema:
1
create table "products" (
2
"id" serial primary key,
3
"name" text not null,
4
"description" text,
5
"price" numeric(10,2) not null,
6
"created_at" timestamp default now()
7
);
89
alter table "products"
10
enable row level security;
与直接更改数据库 schema 相比,声明式 schemas 提供了许多好处:
- 单一管理界面。 在一个地方维护整个数据库 schema,从而减少冗余和潜在错误。
- 版本化的迁移。 自动生成迁移文件,确保跨环境的一致 schema 更新。 将声明式 schema 文件与项目文件一起存储在版本控制系统中。
- 简洁的代码审查。 轻松查看表、视图和函数的更改,而无需手动重复复杂的迁移脚本。
声明式 Schemas vs 迁移#
最佳实践是使用 Migrations 来跟踪和应用数据库的更改。 每次进行更改时,您都会创建一个包含所有更改的新文件,从而保持更改的版本控制和可重现性。
但是,随着数据库 schemas 复杂性的增加,使用版本化迁移进行开发变得越来越困难,因为没有一个地方可以查看整个数据库 schema。
例如,在 Supabase,我们有一个复杂且经常更新的 projects
表。 这是一个启用了 RLS 的部分示例:
1
create table private.projects (
2
id bigint not null,
3
name text not null,
4
organization_id bigint not null,
5
inserted_at timestamp not null,
6
updated_at timestamp not null
7
);
89
alter table private.projects
10
enable row level security;
1112
create policy projects_insert
13
on private.projects
14
for insert
15
to authenticated
16
with check auth.can_write(project_id);
1718
create policy projects_select
19
on private.projects
20
for select
21
to authenticated
22
using auth.can_read(project_id);
2324
-- Users can only view the projects that they have access to
25
create view public.projects as select
26
projects.id,
27
projects.name,
28
projects.organization_id,
29
projects.inserted_at,
30
projects.updated_at
31
from private.projects
32
where auth.can_read(projects.id);
projects
表是在一个私有 schema 中创建的,公开视图用于读取。 基于属性的访问控制 (ABAC) 在 RLS 策略之上实现,以确保查询仅返回用户有权访问的项目。
由于默认情况下 Postgres 视图不可更新,因此我们定义了触发器函数,以便在 Supabase 用户创建新项目时将写入级联到基础表。 这使得开发更容易,因为可以使用常规 PostgREST 调用插入 projects
视图,同时调用基础表上的相应 RLS 策略。
1
-- Triggers to update views from PostgREST: select('projects').insert({ ... })
2
create function public.public_projects_on_insert() returns trigger
3
as $$
4
begin
5
insert into private.projects(
6
name,
7
organization_id,
8
inserted_at,
9
updated_at
10
) values (
11
NEW.name,
12
NEW.organization_id,
13
coalesce(NEW.inserted_at, now()),
14
coalesce(NEW.updated_at, now())
15
) returning * into NEW;
16
return NEW;
17
end
18
$$ language plpgsql;
1920
create trigger public_projects_on_insert
21
instead of insert
22
on public.projects
23
for each row
24
execute function public.public_projects_on_insert();
这种复杂性降低了开发速度,因为对表的更改可能会破坏其他视图或函数。 早在 2022 年初,添加新列的简单更改涉及以下步骤。
- 在我们的迁移文件中或通过查询我们的数据库找到
projects
表的最新 schema。 - 在新的迁移文件中编写
alter table
语句。 - 复制并更新
projects
视图定义以包含新列。 - 复制并更新触发器函数定义以包含新列。
- 添加新的 pgTAP 测试并验证现有测试是否通过。
- 提交新的迁移文件以进行审查,该文件至少有数百行。
这个过程很繁琐,并且多个工程师同时处理 projects
表令人沮丧。 合并 PR 会导致合并冲突,必须通过重复步骤 1-5 来解决。
在生产环境中使用声明式 Schemas#
采用声明式 schemas 为我们的工程师在更新数据库 schemas 时提供了一个单一管理界面。 我们只需要在一个地方更改 schema 定义,而无需在迁移文件中手动复制受影响的 postgres 实体。
然后,我们使用 schema 差异工具(例如 migra)来确定生成迁移文件时对视图和函数进行的必要更新。
例如,将新的 metadata
列添加到 projects
表现在变成单行差异。
1
--- a/supabase/schemas/projects.sql
2
+++ b/supabase/schemas/projects.sql
3
@@ -2,6 +2,7 @@ create table private.projects (
4
id bigint not null,
5
name text not null,
6
organization_id bigint not null,
7
+ metadata jsonb,
8
inserted_at timestamp not null,
9
updated_at timestamp not null
10
);
同样的流程也适用于视图、数据库函数、RLS 策略、角色授权、自定义类型和约束。 虽然仍然需要对生成的迁移文件进行手动审查,但它已将我们的开发时间从数小时缩短到数分钟。 在其他 PR 引入的合并冲突上进行 rebase 也容易得多。
开始使用声明式 Schemas#
声明式 schemas 今天在 Supabase 上可用。
- 阅读文档 以了解如何在一个地方管理数据库 schemas 并生成版本化的迁移。
- 注册 Supabase 并立即开始使用。
我们将过去 2 年在内部使用的一组相同工具添加到了 Supabase CLI。 无论您是刚开始使用迁移,还是已经厌倦了管理数百个迁移文件,都可以尝试使用声明式 schemas,因为它可能会简化您的开发过程。
查看我们关于 Postgres Language Server 的博客文章,了解使用声明式 schemas 进行开发时更好的工具和 IDE 集成。
Launch Week 14 2025 年 3 月 31 日 - 4 月 04 日
第 1 天 -Supabase UI Library第 2 天 -Supabase Edge Functions: Deploy from the Dashboard + Deno 2.1第 3 天 -Realtime: Broadcast from Database第 4 天 -Declarative Schemas for Simpler Database Management
内容概要: