AgentSkillsCN

supabase

Supabase 数据库开发规范、最佳实践、检查清单与 SQL 模板。

中文原作
SKILL.md
--- frontmatter
name: supabase
description: Supabase 数据库开发规范、最佳实践、检查清单与 SQL 模板。

Supabase Skill

用于指导 Supabase 数据库开发的规范和最佳实践。

使用场景

当进行以下操作时,请参考此规范:

  • 创建新的数据库表
  • 设计 RLS 策略
  • 添加数据库索引
  • 编写数据库迁移
  • 配置本地开发环境
  • 部署到生产环境

资源

  • SQL 模板集 - 包含建表、RLS、索引等常用 SQL 模板

开发规范

目录

  1. 环境配置规范
  2. 表设计规范
  3. RLS 策略规范
  4. 索引规范
  5. 迁移规范
  6. 本地开发 vs 生产环境

环境配置规范

环境变量

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_role key
  • 永远不要在前端暴露 service_role key
  • 前端只能使用 anon key

表设计规范

命名规范

元素规范示例
表名snake_case,复数形式follow_rules, signals, orders
列名snake_casecreated_at, user_id, order_price
主键idid UUID PRIMARY KEY
外键{关联表单数}_iduser_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 类型说明
UUIDUUID主键、外键
时间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-treeWHERE created_at > '2024-01-01'
全文搜索GINWHERE to_tsvector(content) @@ query
JSONB 查询GINWHERE config_json @> '{"key": "value"}'
数组包含GINWHERE '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};

迁移最佳实践

  1. 使用 IF NOT EXISTS / IF EXISTS

    sql
    CREATE TABLE IF NOT EXISTS ...
    CREATE INDEX IF NOT EXISTS ...
    DROP TABLE IF EXISTS ...
    
  2. 小步迭代:每个迁移文件只做一件事

  3. 向后兼容:新增列提供默认值或允许 NULL

  4. 先添加后删除

    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;
    
  5. 保留回滚脚本:在注释中记录如何回滚

执行迁移

bash
# 本地环境:通过 Supabase CLI
supabase db reset                     # 重置并应用所有迁移
supabase db push                      # 推送迁移到远程

# 生产环境:通过 Supabase Dashboard 或 CLI
supabase db push --db-url $PROD_URL   # 推送到生产数据库

本地开发 vs 生产环境

环境区分

配置项本地开发生产环境
SUPABASE_URLhttp://localhost:54321https://{project}.supabase.co
Database本地 PostgreSQLSupabase 托管
数据测试数据真实数据
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_role key 仅在后端
  • anon key 仅在前端使用
  • 敏感数据已加密存储
  • 环境变量已正确配置
  • 数据库连接使用 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 策略不生效

  1. 确认表已启用 RLS:ALTER TABLE xxx ENABLE ROW LEVEL SECURITY
  2. 确认策略已创建:SELECT * FROM pg_policies WHERE tablename = 'xxx'
  3. 确认使用的是 anon key 而非 service_role key

查询性能问题

  1. 使用 EXPLAIN ANALYZE 分析查询计划
  2. 检查 RLS 策略中的子查询是否有索引
  3. 考虑添加复合索引

迁移失败

  1. 检查是否有语法错误
  2. 检查是否违反约束(外键、唯一性等)
  3. 检查是否有数据依赖问题