Supabase Skill
用于指导 Supabase 数据库开发的规范和最佳实践。
使用场景
当进行以下操作时,请参考此规范:
- •创建新的数据库表
- •设计 RLS 策略
- •添加数据库索引
- •编写数据库迁移
- •配置本地开发环境
- •部署到生产环境
资源
- •SQL 模板集 - 包含建表、RLS、索引等常用 SQL 模板
开发规范
目录
环境配置规范
环境变量
bash
# .env.example # 本地开发环境 SUPABASE_URL=http://localhost:54321 SUPABASE_SERVICE_ROLE_KEY=your_local_service_role_key # 生产环境 SUPABASE_URL=https://your-project.supabase.co SUPABASE_SERVICE_ROLE_KEY=your_production_service_role_key
客户端初始化规范
python
# 正确做法:使用环境变量 + 单例模式
from supabase import Client, create_client
import os
_client: Client | None = None
def get_supabase_client() -> Client:
global _client
if _client is None:
url = os.getenv("SUPABASE_URL")
key = os.getenv("SUPABASE_SERVICE_ROLE_KEY")
if not url or not key:
raise ValueError("Supabase 环境变量未设置")
_client = create_client(url, key)
return _client
Key 类型选择
| Key 类型 | 用途 | RLS 行为 |
|---|---|---|
anon | 前端客户端 | 受 RLS 约束 |
service_role | 后端服务 | 绕过 RLS |
规则:
- •后端服务必须使用
service_rolekey - •永远不要在前端暴露
service_rolekey - •前端只能使用
anonkey
表设计规范
命名规范
| 元素 | 规范 | 示例 |
|---|---|---|
| 表名 | snake_case,复数形式 | follow_rules, signals, orders |
| 列名 | snake_case | created_at, user_id, order_price |
| 主键 | id | id UUID PRIMARY KEY |
| 外键 | {关联表单数}_id | user_id, signal_id |
| 时间戳 | *_at 后缀 | created_at, updated_at, deleted_at |
必需列
每个表必须包含以下列:
sql
CREATE TABLE table_name (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- ... 业务字段 ...
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
软删除规范
需要软删除的表添加 deleted_at 列:
sql
deleted_at TIMESTAMPTZ -- NULL 表示未删除
查询时过滤已删除记录:
sql
SELECT * FROM table_name WHERE deleted_at IS NULL;
自动更新时间戳
必须为每个表创建 updated_at 触发器:
sql
-- 通用触发器函数(只需创建一次)
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- 为每个表创建触发器
CREATE TRIGGER {table_name}_updated_at
BEFORE UPDATE ON {table_name}
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
数据类型规范
| 数据类型 | PostgreSQL 类型 | 说明 |
|---|---|---|
| UUID | UUID | 主键、外键 |
| 时间 | TIMESTAMPTZ | 必须使用带时区的时间 |
| 金额/价格 | DECIMAL(18, 6) | 高精度数值 |
| 百分比/比例 | DECIMAL(10, 6) | 小数 |
| JSON 数据 | JSONB | 优先使用 JSONB |
| 布尔值 | BOOLEAN | 默认值明确设置 |
| 枚举值 | VARCHAR + CHECK | 使用约束验证 |
枚举值约束
sql
-- 推荐:使用 CHECK 约束
status VARCHAR(20) NOT NULL DEFAULT 'pending'
CHECK (status IN ('pending', 'processing', 'completed', 'ignored'))
-- 不推荐:PostgreSQL ENUM 类型(修改困难)
数组类型
sql
-- 文本数组 leader_addresses TEXT[] NOT NULL -- 查询示例 SELECT * FROM follow_rules WHERE 'address123' = ANY(leader_addresses);
RLS 策略规范
启用 RLS
所有表必须启用 RLS:
sql
ALTER TABLE table_name ENABLE ROW LEVEL SECURITY;
策略命名规范
格式:"{动作描述}"
sql
-- 好的命名 "Users can view their own follow_rules" "Users can insert signals for their follow_rules" -- 避免的命名 "policy_1" "select_policy"
基础策略模板
直接用户关联表
sql
-- 用户只能访问自己的数据
CREATE POLICY "Users can view their own {table}"
ON {table} FOR SELECT
USING (auth.uid() = user_id);
CREATE POLICY "Users can insert their own {table}"
ON {table} FOR INSERT
WITH CHECK (auth.uid() = user_id);
CREATE POLICY "Users can update their own {table}"
ON {table} FOR UPDATE
USING (auth.uid() = user_id);
CREATE POLICY "Users can delete their own {table}"
ON {table} FOR DELETE
USING (auth.uid() = user_id);
间接关联表(通过外键)
sql
-- 通过父表关联验证权限
CREATE POLICY "Users can view {child_table} from their {parent_table}"
ON {child_table} FOR SELECT
USING (
EXISTS (
SELECT 1 FROM {parent_table}
WHERE {parent_table}.id = {child_table}.{parent}_id
AND {parent_table}.user_id = auth.uid()
)
);
多级关联表
sql
-- 通过多级关联验证权限
CREATE POLICY "Users can view orders from their signals"
ON orders FOR SELECT
USING (
EXISTS (
SELECT 1 FROM signals
JOIN follow_rules ON follow_rules.id = signals.follow_rule_id
WHERE signals.id = orders.signal_id
AND follow_rules.user_id = auth.uid()
)
);
Service Role 绕过 RLS
sql
-- service_role key 自动绕过 RLS,无需额外配置 -- 如需在特定会话中绕过 RLS: SET LOCAL request.jwt.claims.role = 'service_role';
RLS 性能优化
sql
-- 为 RLS 策略中使用的列创建索引
CREATE INDEX idx_{table}_user_id ON {table}(user_id);
-- 对于间接关联,确保外键有索引
CREATE INDEX idx_{child}_parent_id ON {child_table}({parent}_id);
索引规范
必需索引
每个表必须创建以下索引:
sql
-- 1. 外键索引(加速 JOIN)
CREATE INDEX idx_{table}_{fk_column} ON {table}({fk_column});
-- 2. 常用查询条件索引
CREATE INDEX idx_{table}_{column} ON {table}({column});
-- 3. 时间范围查询索引(降序,便于获取最新数据)
CREATE INDEX idx_{table}_created_at ON {table}(created_at DESC);
索引命名规范
格式:idx_{表名}_{列名}
sql
-- 单列索引 CREATE INDEX idx_signals_status ON signals(status); CREATE INDEX idx_signals_follow_rule_id ON signals(follow_rule_id); -- 复合索引 CREATE INDEX idx_signals_status_created ON signals(status, created_at DESC);
索引类型选择
| 场景 | 索引类型 | 示例 |
|---|---|---|
| 等值查询 | B-tree(默认) | WHERE status = 'pending' |
| 范围查询 | B-tree | WHERE created_at > '2024-01-01' |
| 全文搜索 | GIN | WHERE to_tsvector(content) @@ query |
| JSONB 查询 | GIN | WHERE config_json @> '{"key": "value"}' |
| 数组包含 | GIN | WHERE 'addr' = ANY(addresses) |
JSONB 索引
sql
-- GIN 索引(支持 @>, ?, ?& 等操作符)
CREATE INDEX idx_{table}_{jsonb_col}_gin ON {table} USING GIN ({jsonb_col});
-- 特定路径索引
CREATE INDEX idx_{table}_{jsonb_path} ON {table} (({jsonb_col}->>'key'));
部分索引
sql
-- 只索引活跃数据
CREATE INDEX idx_follow_rules_active
ON follow_rules(user_id)
WHERE is_active = true AND deleted_at IS NULL;
迁移规范
迁移文件结构
code
migrations/ ├── 001_init_schema.sql # 初始化 schema ├── 002_add_feature_x.sql # 添加功能 X ├── 003_optimize_indexes.sql # 优化索引 └── ...
迁移文件命名
格式:{序号}_{描述}.sql
- •序号:3位数字,从 001 开始
- •描述:snake_case,简洁描述变更内容
code
001_init_schema.sql 002_add_user_preferences.sql 003_add_signals_market_title.sql 004_optimize_orders_indexes.sql
迁移文件模板
sql
-- Migration: {序号}_{描述}
-- Description: {详细描述变更内容}
-- Author: {作者}
-- Date: {日期}
-- ============================================
-- 1. 变更描述
-- ============================================
-- 添加新列
ALTER TABLE {table} ADD COLUMN {column} {type};
-- 添加索引
CREATE INDEX IF NOT EXISTS idx_{table}_{column} ON {table}({column});
-- 添加约束
ALTER TABLE {table} ADD CONSTRAINT {constraint_name} CHECK (...);
-- ============================================
-- 2. RLS 策略更新(如需要)
-- ============================================
-- ...
-- ============================================
-- 3. 回滚脚本(注释形式保留)
-- ============================================
-- ROLLBACK:
-- ALTER TABLE {table} DROP COLUMN {column};
-- DROP INDEX idx_{table}_{column};
迁移最佳实践
- •
使用 IF NOT EXISTS / IF EXISTS
sqlCREATE TABLE IF NOT EXISTS ... CREATE INDEX IF NOT EXISTS ... DROP TABLE IF EXISTS ...
- •
小步迭代:每个迁移文件只做一件事
- •
向后兼容:新增列提供默认值或允许 NULL
- •
先添加后删除:
sql-- 步骤 1:添加新列 ALTER TABLE users ADD COLUMN new_email VARCHAR(255); -- 步骤 2:迁移数据(另一个迁移文件) UPDATE users SET new_email = email; -- 步骤 3:删除旧列(确认无问题后) ALTER TABLE users DROP COLUMN email;
- •
保留回滚脚本:在注释中记录如何回滚
执行迁移
bash
# 本地环境:通过 Supabase CLI supabase db reset # 重置并应用所有迁移 supabase db push # 推送迁移到远程 # 生产环境:通过 Supabase Dashboard 或 CLI supabase db push --db-url $PROD_URL # 推送到生产数据库
本地开发 vs 生产环境
环境区分
| 配置项 | 本地开发 | 生产环境 |
|---|---|---|
| SUPABASE_URL | http://localhost:54321 | https://{project}.supabase.co |
| Database | 本地 PostgreSQL | Supabase 托管 |
| 数据 | 测试数据 | 真实数据 |
| RLS | 可禁用测试 | 必须启用 |
本地开发环境设置
1. 安装 Supabase CLI
bash
# macOS brew install supabase/tap/supabase # npm npm install -g supabase
2. 初始化本地项目
bash
supabase init supabase start
3. 本地环境变量
bash
# .env.local SUPABASE_URL=http://localhost:54321 SUPABASE_SERVICE_ROLE_KEY=<local-service-role-key> SUPABASE_ANON_KEY=<local-anon-key>
获取本地 keys:
bash
supabase status
4. 本地数据库操作
bash
# 重置数据库并应用迁移
supabase db reset
# 生成迁移文件(从本地 schema 变更)
supabase db diff -f {migration_name}
# 查看本地数据库
supabase db studio # 打开本地 Studio
生产环境规范
1. 安全检查清单
- • 所有表启用 RLS
- • 使用
service_rolekey 仅在后端 - •
anonkey 仅在前端使用 - • 敏感数据已加密存储
- • 环境变量已正确配置
- • 数据库连接使用 SSL
2. 生产环境变量
bash
# .env.production
SUPABASE_URL=https://{project}.supabase.co
SUPABASE_SERVICE_ROLE_KEY=<production-service-role-key>
3. 生产迁移流程
bash
# 方式一:Supabase CLI
supabase link --project-ref {project-id}
supabase db push
# 方式二:Dashboard SQL Editor
# 1. 登录 Supabase Dashboard
# 2. 进入 SQL Editor
# 3. 粘贴迁移脚本执行
# 方式三:直接连接(需谨慎)
psql $DATABASE_URL -f migrations/xxx.sql
4. 生产数据库连接
python
# 生产环境必须使用 SSL
import os
from supabase import create_client
client = create_client(
os.getenv("SUPABASE_URL"),
os.getenv("SUPABASE_SERVICE_ROLE_KEY")
)
环境差异处理
python
import os
def is_local_env() -> bool:
"""判断是否为本地开发环境"""
url = os.getenv("SUPABASE_URL", "")
return "localhost" in url or "127.0.0.1" in url
def get_db_config():
if is_local_env():
return {
"ssl": False,
"timeout": 30,
}
else:
return {
"ssl": True,
"timeout": 10,
}
数据同步策略
从生产导出测试数据
bash
# 导出特定表(脱敏后) pg_dump $PROD_URL -t follow_rules --data-only > seed_data.sql # 导入到本地 psql $LOCAL_URL < seed_data.sql
Seed 数据
sql
-- supabase/seed.sql
-- 本地开发测试数据
INSERT INTO follow_rules (id, user_id, name, leader_addresses, config_version, config_json)
VALUES (
'00000000-0000-0000-0000-000000000001',
'auth-user-uuid',
'Test Rule',
ARRAY['0x123', '0x456'],
1,
'{"fixed_slippage": 0.01, "min_usdc_size": 10, "cooldown_seconds": 60}'
);
检查清单
新建表检查清单
code
[ ] 表名:snake_case 复数形式(如 follow_rules, signals)
[ ] 主键:id UUID PRIMARY KEY DEFAULT gen_random_uuid()
[ ] 时间戳:created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
[ ] 时间戳:updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
[ ] 触发器:创建 {table}_updated_at 触发器
[ ] RLS:ALTER TABLE {table} ENABLE ROW LEVEL SECURITY
[ ] 策略:创建 SELECT/INSERT/UPDATE/DELETE 策略
[ ] 索引:为外键列创建索引
[ ] 索引:为常用查询条件创建索引
[ ] 索引:为 created_at 创建降序索引
RLS 策略检查清单
code
[ ] 所有表已启用 RLS [ ] 直接关联表:使用 auth.uid() = user_id [ ] 间接关联表:使用 EXISTS 子查询验证权限链 [ ] 后端服务:确认使用 service_role key [ ] 前端:确认只使用 anon key [ ] 性能:RLS 策略使用的列有索引
迁移文件检查清单
code
[ ] 文件名:{序号}_{描述}.sql(如 001_init_schema.sql)
[ ] 头部注释:包含 Migration、Description、Author、Date
[ ] 幂等性:使用 IF NOT EXISTS / IF EXISTS
[ ] 回滚脚本:在注释中保留回滚 SQL
[ ] 小步迭代:每个迁移只做一件事
[ ] 向后兼容:新列有默认值或允许 NULL
生产部署检查清单
code
[ ] 本地测试:在本地环境验证迁移 [ ] RLS 验证:确认 RLS 策略正确 [ ] 索引验证:确认必要索引存在 [ ] 环境变量:生产环境变量已配置 [ ] 备份:重大变更前已备份数据库 [ ] 回滚准备:准备好回滚脚本
环境配置检查清单
本地环境
code
[ ] Supabase CLI 已安装 [ ] supabase init 已执行 [ ] supabase start 已启动 [ ] .env.local 已配置本地 URL 和 Key
生产环境
code
[ ] .env.production 已配置生产 URL 和 Key [ ] service_role key 仅在后端使用 [ ] SSL 连接已启用 [ ] 敏感数据已脱敏处理
常见问题排查
RLS 策略不生效
- •确认表已启用 RLS:
ALTER TABLE xxx ENABLE ROW LEVEL SECURITY - •确认策略已创建:
SELECT * FROM pg_policies WHERE tablename = 'xxx' - •确认使用的是 anon key 而非 service_role key
查询性能问题
- •使用 EXPLAIN ANALYZE 分析查询计划
- •检查 RLS 策略中的子查询是否有索引
- •考虑添加复合索引
迁移失败
- •检查是否有语法错误
- •检查是否违反约束(外键、唯一性等)
- •检查是否有数据依赖问题