Supabase 实时数据库订阅
FreeGuideOnline
最新
2026-07-11
sql ALTER TABLE your_table_name REPLICA IDENTITY FULL;
> 注意:`REPLICA IDENTITY FULL` 会为每条更新在 WAL 日志中写入完整的旧行数据,可能会稍微增加 WAL 日志体积,请根据业务需求权衡。
### 2. 将表加入实时发布(Publication)
实时功能通过 PostgreSQL 的发布(Publication)`supabase_realtime` 来决定哪些表会生成变更事件。默认情况下,所有表都不会自动加入。你需要手动添加目标表:
```sql
ALTER PUBLICATION supabase_realtime ADD TABLE your_table_name;
如果你有很多表,也可以一次添加多张:
ALTER PUBLICATION supabase_realtime ADD TABLE table1, table2, table3;
或者允许未来新建的表自动加入(通过事件触发器,高级用法可参考官方文档)。
提示:每次变更结构后无需重复执行。只要表曾被加入该发布,后续的结构变更仍会生效。
在客户端订阅变更
Supabase 提供两种风格的实时订阅方式:
- 通道订阅(推荐,
channel()API):使用supabase.channel()创建通道并监听 PostgreSQL 更改,灵活性更高,支持多种过滤器和自定义逻辑。 - 简易订阅(旧版
from().on().subscribe()):直接在查询构建器上调用,语法简洁,适合单表简单监听。
我们以更现代的通道订阅为主进行讲解。
初始化 Supabase 客户端
无论在什么环境下,首先需要初始化客户端。
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(
'https://your-project.supabase.co',
'your-anon-key' // 或 service_role key,视安全需求而定
)
创建通道并订阅表变更
下面的示例演示如何订阅 messages 表的所有变更。
const channel = supabase
.channel('db-changes') // 通道名称,可自定义,同一客户端内不可重复
.on(
'postgres_changes',
{
event: '*', // 监听所有事件 (INSERT, UPDATE, DELETE)
schema: 'public',
table: 'messages'
},
(payload) => {
console.log('变更事件:', payload.eventType);
console.log('新数据:', payload.new);
console.log('旧数据:', payload.old);
}
)
.subscribe((status) => {
if (status === 'SUBSCRIBED') {
console.log('已成功订阅 messages 表');
}
})
event:可指定'INSERT'、'UPDATE'、'DELETE'或'*'。payload.new:包含变更后的行数据(INSERT 和 UPDATE 事件)。DELETE 事件中此字段为null。payload.old:包含变更前的行数据(UPDATE 和 DELETE 事件),INSERT 事件中为null。payload.eventType:字符串,明确本次事件类型。
仅监听特定行的变化(带过滤条件)
大多数场景下你不需要监听整张表,而是只关心某些特定条件的数据变化。可以在 on() 的选项中添加过滤器。
Supabase 的实时过滤器支持大部分 PostgREST 操作符,如 eq、neq、gt、lt、in 等。
示例:只订阅某位用户的消息
supabase
.channel('user-messages')
.on(
'postgres_changes',
{
event: 'INSERT',
schema: 'public',
table: 'messages',
filter: `user_id=eq.${userId}` // 字符串格式,类似 PostgREST 查询参数
},
(payload) => {
console.log('收到属于自己的新消息:', payload.new)
}
)
.subscribe()
你也可以组合多个过滤器:
filter: `user_id=eq.${userId}&room=eq.${roomId}`
多条件 OR 过滤器
如果你需要一次订阅满足多个条件(逻辑或)的变化,可以创建多个 on() 监听器,或者使用 in 操作符。
// 监听 status 为 'urgent' 或 'new' 的行
filter: `status=in.(urgent,new)`
取消订阅与资源清理
当组件卸载或不再需要监听时,必须取消订阅以释放连接。
// 取消整个通道及其所有监听
supabase.removeChannel(channel)
若想只移除通道内的某个监听,可以在创建时保存监听器引用,然后调用 .unsubscribe() —— 不过实践中直接移除整个通道更常见。
在前端框架中的集成实战(以 React 为例)
我们将通过一个简单的聊天消息实时展示,演示如何订阅新消息并在组件卸载时自动关闭订阅。
import { useEffect, useState } from 'react'
import { supabase } from './supabaseClient'
export default function ChatRoom({ roomId }) {
const [messages, setMessages] = useState([])
useEffect(() => {
// 获取历史消息(一次性)
const fetchMessages = async () => {
const { data } = await supabase
.from('messages')
.select('*')
.eq('room', roomId)
.order('created_at', { ascending: true })
setMessages(data || [])
}
fetchMessages()
// 订阅该房间的新 INSERT 消息
const channel = supabase
.channel(`room-${roomId}`)
.on(
'postgres_changes',
{
event: 'INSERT',
schema: 'public',
table: 'messages',
filter: `room=eq.${roomId}`
},
(payload) => {
// 将新消息追加到状态末尾
setMessages((prev) => [...prev, payload.new])
}
)
.subscribe()
// 组件卸载时清理
return () => {
supabase.removeChannel(channel)
}
}, [roomId])
return (
<div>
{messages.map((msg) => (
<div key={msg.id}>{msg.text}</div>
))}
</div>
)
}
后端监听场景(Node.js / Deno)
你同样可以在服务端利用实时订阅构建事件驱动架构,例如通知其他服务、更新缓存等。
// Node.js 服务端代码
const { createClient } = require('@supabase/supabase-js')
const supabase = createClient(SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY)
const channel = supabase
.channel('order-updates')
.on(
'postgres_changes',
{ event: 'INSERT', schema: 'public', table: 'orders' },
(payload) => {
// 执行业务逻辑:发送邮件、更新库存等
console.log('新订单:', payload.new)
}
)
.subscribe()
⚠️ 在服务端请使用
service_role密钥,绕过 RLS(行级安全)策略;注意密钥安全,不要暴露给客户端。
更新和删除事件的高级处理
实时数据同步经常需要处理数据修改和移除的场景。比如一个协作白板,需要根据 UPDATE 事件更新坐标,根据 DELETE 事件移除形状。
.on('postgres_changes', { event: 'UPDATE', schema: 'public', table: 'shapes' }, (payload) => {
const updatedShape = payload.new;
// 在本地画布中根据 id 更新形状
})
.on('postgres_changes', { event: 'DELETE', schema: 'public', table: 'shapes' }, (payload) => {
const removedId = payload.old.id;
// 从画布中移除对应形状
})