跳到主要内容

Supabase详解

Supabase概述

Supabase是一个开源的Firebase替代品,提供了一套完整的后端服务,包括PostgreSQL数据库、认证、存储、实时功能和边缘函数等。它的核心理念是将传统的PostgreSQL数据库转变为一个实时的API服务,同时保持开源和可扩展性。

Supabase成立于2020年,由Paul Copplestone和Tania Rascia创立,旨在为开发者提供一个开源的无服务器后端解决方案。Supabase的特点是完全开源、与PostgreSQL兼容、提供实时数据同步、简单易用的API,以及强大的认证和存储功能。

Supabase的核心功能

Supabase提供了丰富的功能模块,覆盖了现代应用开发的各个方面:

1. 数据库服务

  • PostgreSQL数据库:完全托管的PostgreSQL数据库,提供所有原生PostgreSQL功能
  • 自动生成REST API:基于数据库模式自动生成RESTful API
  • 自动生成GraphQL API:基于数据库模式自动生成GraphQL API
  • 数据导入导出:支持多种格式的数据导入和导出

2. 认证服务

  • 用户认证:支持电子邮件/密码、第三方认证(GitHub、Google等)
  • JWT令牌:基于JWT的身份验证机制
  • 行级安全(RLS)集成:与PostgreSQL的行级安全功能深度集成
  • 多因素认证:支持TOTP等多因素认证方式

3. 存储服务

  • 文件存储:用于存储和提供用户生成的内容,如照片和视频
  • 访问控制:细粒度的访问控制规则
  • CDN支持:通过内容分发网络加速文件访问

4. 实时功能

  • 实时数据库更改:订阅数据库表的更改并实时接收通知
  • 通道和广播:支持自定义实时通道和消息广播
  • ** presence系统**:跟踪谁在线以及他们正在做什么

5. 边缘函数

  • 无服务器函数:在边缘位置运行的TypeScript函数
  • 低延迟:靠近用户的边缘部署,提供低延迟响应
  • 集成API:与Supabase的其他服务无缝集成

6. 向量相似度搜索

  • pgvector集成:支持向量存储和相似度搜索
  • AI应用支持:为AI应用提供向量数据库功能

项目设置与配置

1. 创建Supabase项目

  1. 访问Supabase控制台
  2. 点击"New Project"按钮
  3. 输入项目名称
  4. 选择数据库密码
  5. 选择区域
  6. 点击"Create new project"按钮

2. 安装Supabase客户端库

# 使用npm安装
npm install @supabase/supabase-js

# 或使用yarn安装
yarn add @supabase/supabase-js

3. 初始化Supabase客户端

import { createClient } from '@supabase/supabase-js';

// Supabase配置
const supabaseUrl = 'https://your-project-ref.supabase.co';
const supabaseKey = 'your-anon-key';

// 创建Supabase客户端
const supabase = createClient(supabaseUrl, supabaseKey);

export default supabase;

4. 创建数据库表

可以通过Supabase控制台的Table Editor或SQL Editor创建表:

使用Table Editor:

  1. 在Supabase控制台中,导航到"Table Editor"部分
  2. 点击"New Table"按钮
  3. 定义表名和列
  4. 设置主键和约束
  5. 点击"Save"按钮

使用SQL Editor:

  1. 在Supabase控制台中,导航到"SQL Editor"部分
  2. 输入SQL创建表语句
  3. 点击"Run"按钮
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
email VARCHAR(255) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);

CREATE TABLE posts (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
title VARCHAR(255) NOT NULL,
content TEXT,
author_id UUID REFERENCES users(id),
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);

认证服务

1. 用户注册

import supabase from './supabase';

// 用户注册
export const registerUser = async (email, password) => {
try {
const { data, error } = await supabase.auth.signUp({
email,
password
});

if (error) {
throw error;
}

return data;
} catch (error) {
console.error('注册失败:', error.message);
throw error;
}
};

2. 用户登录

import supabase from './supabase';

// 用户登录
export const loginUser = async (email, password) => {
try {
const { data, error } = await supabase.auth.signInWithPassword({
email,
password
});

if (error) {
throw error;
}

return data;
} catch (error) {
console.error('登录失败:', error.message);
throw error;
}
};

3. 第三方登录

import supabase from './supabase';

// GitHub登录
export const signInWithGitHub = async () => {
try {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'github',
options: {
redirectTo: 'http://localhost:3000/auth/callback'
}
});

if (error) {
throw error;
}

return data;
} catch (error) {
console.error('GitHub登录失败:', error.message);
throw error;
}
};

// Google登录
export const signInWithGoogle = async () => {
try {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'google',
options: {
redirectTo: 'http://localhost:3000/auth/callback'
}
});

if (error) {
throw error;
}

return data;
} catch (error) {
console.error('Google登录失败:', error.message);
throw error;
}
};

4. 用户状态管理

import supabase from './supabase';

// 获取当前用户
export const getCurrentUser = () => {
const { data: { user } } = supabase.auth.getUser();
return user;
};

// 监听用户状态变化
export const onAuthStateChange = (callback) => {
return supabase.auth.onAuthStateChange(callback);
};

// 用户注销
export const logoutUser = async () => {
try {
const { error } = await supabase.auth.signOut();

if (error) {
throw error;
}
} catch (error) {
console.error('注销失败:', error.message);
throw error;
}
};

5. 更新用户资料

import supabase from './supabase';

// 更新用户资料
export const updateUserProfile = async (userData) => {
try {
const { data, error } = await supabase.auth.updateUser({
data: userData
});

if (error) {
throw error;
}

return data;
} catch (error) {
console.error('更新用户资料失败:', error.message);
throw error;
}
};

// 重置密码
export const resetPassword = async (email) => {
try {
const { data, error } = await supabase.auth.resetPasswordForEmail(email, {
redirectTo: 'http://localhost:3000/reset-password'
});

if (error) {
throw error;
}

return data;
} catch (error) {
console.error('重置密码失败:', error.message);
throw error;
}
};

数据库操作

1. SELECT操作

import supabase from './supabase';

// 获取所有记录
export const getAllRecords = async (tableName, columns = '*') => {
try {
const { data, error } = await supabase
.from(tableName)
.select(columns);

if (error) {
throw error;
}

return data;
} catch (error) {
console.error(`获取${tableName}记录失败:`, error.message);
throw error;
}
};

// 条件查询
export const getRecordsByCondition = async (tableName, conditions, columns = '*') => {
try {
let query = supabase.from(tableName).select(columns);

// 添加条件
if (conditions) {
Object.entries(conditions).forEach(([key, value]) => {
if (typeof value === 'object' && value !== null) {
// 处理操作符条件,如 { 'gt': 10 } 表示 greater than 10
Object.entries(value).forEach(([operator, val]) => {
switch (operator) {
case 'eq':
query = query.eq(key, val);
break;
case 'neq':
query = query.neq(key, val);
break;
case 'gt':
query = query.gt(key, val);
break;
case 'gte':
query = query.gte(key, val);
break;
case 'lt':
query = query.lt(key, val);
break;
case 'lte':
query = query.lte(key, val);
break;
case 'like':
query = query.like(key, val);
break;
case 'ilike':
query = query.ilike(key, val);
break;
case 'in':
query = query.in(key, val);
break;
default:
break;
}
});
} else {
// 默认为等于条件
query = query.eq(key, value);
}
});
}

const { data, error } = await query;

if (error) {
throw error;
}

return data;
} catch (error) {
console.error(`条件查询${tableName}记录失败:`, error.message);
throw error;
}
};

// 分页查询
export const getRecordsWithPagination = async (
tableName,
page = 1,
pageSize = 10,
columns = '*',
sortBy = null,
sortOrder = 'asc'
) => {
try {
let query = supabase.from(tableName).select(columns);

// 添加排序
if (sortBy) {
query = query.order(sortBy, { ascending: sortOrder === 'asc' });
}

// 添加分页
const offset = (page - 1) * pageSize;
query = query.range(offset, offset + pageSize - 1);

const { data, count, error } = await query.count();

if (error) {
throw error;
}

return {
data,
total: count,
page,
pageSize,
totalPages: Math.ceil(count / pageSize)
};
} catch (error) {
console.error(`分页查询${tableName}记录失败:`, error.message);
throw error;
}
};

2. INSERT操作

import supabase from './supabase';

// 插入单条记录
export const insertRecord = async (tableName, data) => {
try {
const { data: insertedData, error } = await supabase
.from(tableName)
.insert(data)
.select(); // 添加select()以返回插入的记录

if (error) {
throw error;
}

return insertedData[0]; // 返回插入的单条记录
} catch (error) {
console.error(`插入${tableName}记录失败:`, error.message);
throw error;
}
};

// 插入多条记录
export const insertMultipleRecords = async (tableName, dataArray) => {
try {
const { data: insertedData, error } = await supabase
.from(tableName)
.insert(dataArray)
.select(); // 添加select()以返回插入的记录

if (error) {
throw error;
}

return insertedData;
} catch (error) {
console.error(`批量插入${tableName}记录失败:`, error.message);
throw error;
}
};

3. UPDATE操作

import supabase from './supabase';

// 更新记录
export const updateRecord = async (tableName, id, data) => {
try {
const { data: updatedData, error } = await supabase
.from(tableName)
.update(data)
.eq('id', id)
.select(); // 添加select()以返回更新的记录

if (error) {
throw error;
}

return updatedData[0]; // 返回更新的单条记录
} catch (error) {
console.error(`更新${tableName}记录失败:`, error.message);
throw error;
}
};

// 条件更新
export const updateRecordsByCondition = async (tableName, conditions, data) => {
try {
let query = supabase.from(tableName).update(data);

// 添加条件
if (conditions) {
Object.entries(conditions).forEach(([key, value]) => {
query = query.eq(key, value);
});
}

const { data: updatedData, error } = await query.select();

if (error) {
throw error;
}

return updatedData;
} catch (error) {
console.error(`条件更新${tableName}记录失败:`, error.message);
throw error;
}
};

4. DELETE操作

import supabase from './supabase';

// 删除单条记录
export const deleteRecord = async (tableName, id) => {
try {
const { data: deletedData, error } = await supabase
.from(tableName)
.delete()
.eq('id', id)
.select(); // 添加select()以返回删除的记录

if (error) {
throw error;
}

return deletedData[0]; // 返回删除的单条记录
} catch (error) {
console.error(`删除${tableName}记录失败:`, error.message);
throw error;
}
};

// 条件删除
export const deleteRecordsByCondition = async (tableName, conditions) => {
try {
let query = supabase.from(tableName).delete();

// 添加条件
if (conditions) {
Object.entries(conditions).forEach(([key, value]) => {
query = query.eq(key, value);
});
}

const { data: deletedData, error } = await query.select();

if (error) {
throw error;
}

return deletedData;
} catch (error) {
console.error(`条件删除${tableName}记录失败:`, error.message);
throw error;
}
};

5. 高级查询

import supabase from './supabase';

// 连接查询(使用PostgreSQL的JOIN)
export const getJoinedRecords = async () => {
try {
const { data, error } = await supabase
.from('posts')
.select(`
id,
title,
content,
created_at,
author_id,
users ( id, email, name )
`);

if (error) {
throw error;
}

return data;
} catch (error) {
console.error('连接查询失败:', error.message);
throw error;
}
};

// 分组和聚合
export const getAggregatedData = async () => {
try {
// 注意:Supabase客户端库不直接支持GROUP BY,但可以使用rpc或raw来执行
const { data, error } = await supabase.rpc('get_posts_by_user');

// 或者使用raw查询
// const { data, error } = await supabase
// .raw('SELECT author_id, COUNT(*) as post_count FROM posts GROUP BY author_id');

if (error) {
throw error;
}

return data;
} catch (error) {
console.error('聚合查询失败:', error.message);
throw error;
}
};

// 使用存储过程(数据库函数)
export const callStoredProcedure = async (functionName, params = {}) => {
try {
const { data, error } = await supabase.rpc(functionName, params);

if (error) {
throw error;
}

return data;
} catch (error) {
console.error(`调用存储过程${functionName}失败:`, error.message);
throw error;
}
};

实时功能

1. 订阅数据库更改

import supabase from './supabase';

// 订阅表的所有更改
export const subscribeToTableChanges = (tableName, callback) => {
const subscription = supabase
.channel('table-changes')
.on(
'postgres_changes',
{ event: '*', schema: 'public', table: tableName },
(payload) => {
callback(payload);
}
)
.subscribe();

// 返回取消订阅的函数
return () => {
subscription.unsubscribe();
};
};

// 订阅特定事件的更改
export const subscribeToSpecificEvents = (tableName, events, callback) => {
const subscription = supabase
.channel('specific-events')
.on(
'postgres_changes',
{ event: events, schema: 'public', table: tableName },
(payload) => {
callback(payload);
}
)
.subscribe();

return () => {
subscription.unsubscribe();
};
};

// 订阅带条件的更改
export const subscribeToChangesWithCondition = (tableName, column, value, callback) => {
const subscription = supabase
.channel('conditional-changes')
.on(
'postgres_changes',
{
event: '*',
schema: 'public',
table: tableName,
filter: `${column}=eq.${value}`
},
(payload) => {
callback(payload);
}
)
.subscribe();

return () => {
subscription.unsubscribe();
};
};

2. 自定义通道和广播

import supabase from './supabase';

// 创建自定义通道
export const createCustomChannel = (channelName) => {
const channel = supabase.channel(channelName);
channel.subscribe();
return channel;
};

// 广播消息
export const broadcastMessage = (channel, eventName, message) => {
channel.send({
type: 'broadcast',
event: eventName,
payload: message
});
};

// 接收广播消息
export const listenForBroadcastMessages = (channel, eventName, callback) => {
channel.on('broadcast', { event: eventName }, ({ payload }) => {
callback(payload);
});
};

// 示例:聊天应用中的消息广播
const chatChannel = createCustomChannel('chat-room-1');

// 发送消息
export const sendChatMessage = (message) => {
broadcastMessage(chatChannel, 'new-message', message);
};

// 接收消息
export const receiveChatMessages = (callback) => {
listenForBroadcastMessages(chatChannel, 'new-message', callback);
};

3. Presence功能

import supabase from './supabase';

// 创建带有Presence的通道
export const createPresenceChannel = (channelName, userId, userData) => {
const channel = supabase.channel(channelName, {
config: { presence: { key: userId } }
});

// 追踪用户状态
channel.track(userData);

channel.subscribe();
return channel;
};

// 监听Presence更新
export const listenForPresenceUpdates = (channel, callback) => {
channel.on('presence', { event: 'sync' }, () => {
const presenceState = channel.presenceState();
const users = [];

// 处理presenceState,提取用户信息
Object.values(presenceState).forEach(userSet => {
userSet.forEach(user => {
users.push(user);
});
});

callback(users);
});

channel.on('presence', { event: 'join' }, ({ key, newPresences }) => {
callback(newPresences.map(p => p.payload));
});

channel.on('presence', { event: 'leave' }, ({ key, leftPresences }) => {
callback(leftPresences.map(p => p.payload));
});
};

// 示例:追踪在线用户
const userPresenceChannel = createPresenceChannel(
'online-users',
'user-123',
{ id: 'user-123', name: 'John Doe', avatar: 'https://example.com/avatar.jpg' }
);

// 监听在线用户变化
export const monitorOnlineUsers = (callback) => {
listenForPresenceUpdates(userPresenceChannel, callback);
};

存储服务

1. 上传文件

import supabase from './supabase';

// 上传文件
export const uploadFile = async (file, path = '') => {
try {
const fileName = `${Date.now()}-${file.name}`;
const storagePath = path ? `${path}/${fileName}` : fileName;

const { data, error } = await supabase
.storage
.from('public') // 使用存储桶名称
.upload(storagePath, file, {
cacheControl: '3600',
upsert: false
});

if (error) {
throw error;
}

// 获取文件URL
const { data: { publicUrl } } = supabase
.storage
.from('public')
.getPublicUrl(storagePath);

return {
path: data.path,
url: publicUrl,
fileName
};
} catch (error) {
console.error('文件上传失败:', error.message);
throw error;
}
};

// 上传图片
export const uploadImage = async (file, path = 'images') => {
try {
// 检查文件类型
if (!file.type.match('image.*')) {
throw new Error('只支持图片文件');
}

return await uploadFile(file, path);
} catch (error) {
console.error('图片上传失败:', error.message);
throw error;
}
};

// 批量上传文件
export const uploadMultipleFiles = async (files, path = '') => {
try {
const uploadPromises = Array.from(files).map(file =>
uploadFile(file, path)
);

const results = await Promise.all(uploadPromises);
return results;
} catch (error) {
console.error('批量上传文件失败:', error.message);
throw error;
}
};

2. 下载文件

import supabase from './supabase';

// 获取文件URL
export const getFileUrl = async (filePath, bucket = 'public') => {
try {
const { data: { publicUrl } } = supabase
.storage
.from(bucket)
.getPublicUrl(filePath);

return publicUrl;
} catch (error) {
console.error('获取文件URL失败:', error.message);
throw error;
}
};

// 下载文件内容
export const downloadFile = async (filePath, bucket = 'public') => {
try {
const { data, error } = await supabase
.storage
.from(bucket)
.download(filePath);

if (error) {
throw error;
}

return data;
} catch (error) {
console.error('文件下载失败:', error.message);
throw error;
}
};

// 获取文件元数据
export const getFileMetadata = async (filePath, bucket = 'public') => {
try {
const { data, error } = await supabase
.storage
.from(bucket)
.list(path.dirname(filePath), {
search: path.basename(filePath),
limit: 1
});

if (error) {
throw error;
}

return data.length > 0 ? data[0] : null;
} catch (error) {
console.error('获取文件元数据失败:', error.message);
throw error;
}
};

3. 删除文件

import supabase from './supabase';

// 删除文件
export const deleteFile = async (filePath, bucket = 'public') => {
try {
const { error } = await supabase
.storage
.from(bucket)
.remove([filePath]);

if (error) {
throw error;
}

return true;
} catch (error) {
console.error('文件删除失败:', error.message);
throw error;
}
};

// 批量删除文件
export const deleteMultipleFiles = async (filePaths, bucket = 'public') => {
try {
const { error } = await supabase
.storage
.from(bucket)
.remove(filePaths);

if (error) {
throw error;
}

return true;
} catch (error) {
console.error('批量删除文件失败:', error.message);
throw error;
}
};

边缘函数

1. 设置边缘函数

# 安装Supabase CLI
npm install -g supabase

# 登录Supabase
supabase login

# 初始化项目
supabase init

# 链接到已有的Supabase项目
supabase link --project-ref your-project-ref

# 创建边缘函数
supabase functions new hello-world

2. 编写边缘函数

// supabase/functions/hello-world/index.ts
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';

// 处理请求的函数
const handler = async (req: Request) => {
try {
// 获取请求参数
const { name } = await req.json();

// 创建Supabase客户端
const supabaseClient = createClient(
Deno.env.get('SUPABASE_URL') ?? '',
Deno.env.get('SUPABASE_ANON_KEY') ?? '',
{
global: { headers: { Authorization: req.headers.get('Authorization')! } },
}
);

// 查询数据库(示例)
const { data, error } = await supabaseClient
.from('users')
.select('id, email')
.limit(1);

if (error) {
throw error;
}

// 返回响应
return new Response(
JSON.stringify({
message: `Hello ${name}!`,
data: data
}),
{
headers: { 'Content-Type': 'application/json' },
status: 200,
}
);
} catch (error) {
return new Response(
JSON.stringify({ error: error.message }),
{
headers: { 'Content-Type': 'application/json' },
status: 400,
}
);
}
};

serve(handler);

3. 部署边缘函数

# 部署边缘函数
supabase functions deploy hello-world

# 部署带环境变量的边缘函数
supabase functions deploy hello-world --no-verify-jwt

4. 在客户端调用边缘函数

import supabase from './supabase';

// 调用边缘函数
export const callEdgeFunction = async (functionName, data = {}, options = {}) => {
try {
const { data: result, error } = await supabase
.functions
.invoke(functionName, {
body: JSON.stringify(data),
...options
});

if (error) {
throw error;
}

return result;
} catch (error) {
console.error(`调用边缘函数${functionName}失败:`, error.message);
throw error;
}
};

// 示例:调用hello-world函数
export const sayHello = async (name) => {
return callEdgeFunction('hello-world', { name });
};

行级安全(RLS)

1. 启用行级安全

可以通过Supabase控制台或SQL启用RLS:

使用控制台:

  1. 在Supabase控制台中,导航到"Table Editor"部分
  2. 选择要启用RLS的表
  3. 点击"Edit Table"按钮
  4. 在"RLS"选项卡中,启用"Row Level Security"
  5. 点击"Save Changes"按钮

使用SQL:

-- 启用RLS
ALTER TABLE users ENABLE ROW LEVEL SECURITY;

-- 启用RLS并强制策略(即使是超级用户也受限制)
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
ALTER TABLE posts FORCE ROW LEVEL SECURITY;

2. 创建安全策略

-- 允许用户只访问和修改自己的数据
CREATE POLICY "Users can view their own data" ON users
FOR SELECT USING (auth.uid() = id);

CREATE POLICY "Users can update their own data" ON users
FOR UPDATE USING (auth.uid() = id);

-- 允许用户只查看和管理自己的帖子
CREATE POLICY "Users can view their own posts" ON posts
FOR SELECT USING (auth.uid() = author_id);

CREATE POLICY "Users can insert their own posts" ON posts
FOR INSERT WITH CHECK (auth.uid() = author_id);

CREATE POLICY "Users can update their own posts" ON posts
FOR UPDATE USING (auth.uid() = author_id);

CREATE POLICY "Users can delete their own posts" ON posts
FOR DELETE USING (auth.uid() = author_id);

-- 允许匿名用户查看公共数据
CREATE POLICY "Public data is visible to everyone" ON public_data
FOR SELECT USING (true);

3. 测试RLS

import supabase from './supabase';

// 测试RLS限制
export const testRLS = async () => {
try {
// 尝试访问所有用户数据(应该只返回当前用户的数据)
const { data: users, error } = await supabase.from('users').select('*');

if (error) {
console.error('RLS测试失败:', error.message);
} else {
console.log('RLS测试成功,返回的数据:', users);
}
} catch (error) {
console.error('RLS测试出错:', error.message);
}
};

性能优化

1. 数据库索引优化

-- 创建单列索引
CREATE INDEX idx_posts_title ON posts USING btree (title);

-- 创建多列索引
CREATE INDEX idx_posts_author_id_created_at ON posts USING btree (author_id, created_at DESC);

-- 创建全文搜索索引
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE INDEX idx_posts_content_search ON posts USING gin (content gin_trgm_ops);

-- 查看索引使用情况
EXPLAIN ANALYZE SELECT * FROM posts WHERE title = 'Example Title';

2. 客户端优化

// 只选择需要的列
export const getOptimizedData = async () => {
const { data, error } = await supabase
.from('posts')
.select('id, title, created_at') // 只选择需要的列
.limit(10);

return data;
};

// 使用缓存
export const getCachedData = async (cacheKey, fetchFunction, ttl = 5 * 60 * 1000) => {
// 检查本地存储中的缓存
const cached = localStorage.getItem(cacheKey);
const now = Date.now();

if (cached) {
const { data, timestamp } = JSON.parse(cached);
// 如果缓存未过期,返回缓存的数据
if (now - timestamp < ttl) {
return data;
}
}

// 缓存过期或不存在,重新获取数据
const data = await fetchFunction();

// 存储新的缓存
localStorage.setItem(cacheKey, JSON.stringify({
data,
timestamp: now
}));

return data;
};

// 示例:使用缓存获取帖子列表
const getPostsWithCache = async () => {
return getCachedData('posts_list', async () => {
const { data } = await supabase
.from('posts')
.select('*')
.limit(20);
return data;
});
};

3. 实时连接优化

// 优化实时连接数量
export const optimizeRealtimeConnections = () => {
// 1. 合并相似的订阅
// 2. 只订阅必要的列
// 3. 使用条件过滤减少不必要的更新
// 4. 在不需要时取消订阅

// 示例:只订阅必要的列和带条件的更改
const subscription = supabase
.channel('optimized-channel')
.on(
'postgres_changes',
{
event: 'INSERT',
schema: 'public',
table: 'posts',
filter: 'category=eq.technology',
select: 'id, title, created_at'
},
(payload) => {
console.log('New technology post:', payload);
}
)
.subscribe();

// 返回取消订阅的函数,方便组件卸载时调用
return () => subscription.unsubscribe();
};

监控与调试

1. 启用日志

在Supabase控制台中,可以启用和查看各种日志:

  1. 导航到"Database" > "Logs"部分
  2. 选择要查看的日志类型(PostgreSQL日志、API日志等)
  3. 设置适当的时间范围和过滤器
  4. 查看日志内容

2. 使用PostgreSQL的EXPLAIN分析查询

import supabase from './supabase';

// 分析查询性能
export const analyzeQuery = async (query) => {
try {
const { data, error } = await supabase.raw(`EXPLAIN ANALYZE ${query}`);

if (error) {
throw error;
}

return data;
} catch (error) {
console.error('查询分析失败:', error.message);
throw error;
}
};

// 示例:分析帖子查询
const analyzePostsQuery = async () => {
return analyzeQuery('SELECT * FROM posts WHERE author_id = \'user-123\' ORDER BY created_at DESC');
};

3. 使用Supabase的状态端点

// 检查Supabase服务状态
export const checkSupabaseStatus = async () => {
try {
const response = await fetch('https://status.supabase.com/api/v2/status.json');
const status = await response.json();
return status;
} catch (error) {
console.error('检查状态失败:', error.message);
throw error;
}
};

最佳实践

1. 安全最佳实践

  • 始终启用行级安全(RLS)保护敏感数据
  • 使用参数化查询避免SQL注入攻击
  • 为JWT令牌设置适当的过期时间
  • 定期轮换数据库密码和API密钥
  • 限制数据库连接的IP范围
  • 对敏感数据进行加密存储

2. 数据库设计最佳实践

  • 遵循数据库规范化原则设计表结构
  • 为常用查询创建适当的索引
  • 使用外键约束维护数据完整性
  • 避免过多的表连接操作
  • 合理使用视图简化复杂查询
  • 定期运行VACUUM命令优化数据库性能

3. 性能优化最佳实践

  • 只查询必要的列和行
  • 使用分页限制返回的数据量
  • 实现客户端缓存减少重复请求
  • 对大型数据集使用异步处理
  • 合理使用边缘函数处理低延迟需求
  • 优化实时连接,避免不必要的订阅

4. 应用架构最佳实践

  • 将业务逻辑与数据访问逻辑分离
  • 使用中间件处理认证和授权
  • 实现错误处理和日志记录机制
  • 使用环境变量管理配置
  • 实现数据验证和清理机制
  • 考虑使用事务确保数据一致性

总结

Supabase是一个强大的开源后端平台,它将PostgreSQL数据库的强大功能与现代应用开发所需的工具和服务结合在一起。通过使用Supabase,开发者可以快速构建功能丰富的应用,而不必担心基础设施的管理。

Supabase的主要优势包括:完全开源、与PostgreSQL兼容、提供实时数据同步、自动生成API、强大的认证和存储功能等。它特别适合需要快速开发原型或构建中小型应用的开发者。

当然,与任何技术一样,使用Supabase也需要考虑其局限性,如对超大规模应用的支持、复杂查询的性能优化等。但总体而言,Supabase是一个值得考虑的后端解决方案,尤其是对于那些希望避免供应商锁定、喜欢开源技术的开发者来说。