Supabase数据删除 #
一、基础删除 #
1.1 使用SQL删除 #
sql
-- 删除单条记录
DELETE FROM users WHERE id = 1;
-- 删除并返回
DELETE FROM users WHERE id = 1 RETURNING *;
-- 删除多条记录
DELETE FROM users WHERE status = 'inactive';
-- 删除所有记录
DELETE FROM users;
1.2 使用客户端删除 #
typescript
// 删除单条记录
const { data, error } = await supabase
.from('users')
.delete()
.eq('id', 1)
.select()
// 删除多条记录
const { data, error } = await supabase
.from('products')
.delete()
.eq('category', 'discontinued')
.select()
1.3 删除注意事项 #
text
重要提示
├── delete()必须配合过滤条件使用
├── 默认删除所有匹配的记录
├── 建议使用.eq('id', xxx)限制范围
└── 使用.select()返回删除的数据
二、条件删除 #
2.1 各种过滤条件 #
typescript
// 按ID删除
const { data } = await supabase
.from('posts')
.delete()
.eq('id', postId)
// 按多个条件删除
const { data } = await supabase
.from('comments')
.delete()
.eq('post_id', postId)
.lt('created_at', '2024-01-01')
// 删除特定用户的文章
const { data } = await supabase
.from('posts')
.delete()
.eq('author_id', userId)
.eq('id', postId)
2.2 SQL条件删除 #
sql
-- 多条件删除
DELETE FROM products
WHERE category = 'discontinued' AND stock = 0;
-- 使用子查询删除
DELETE FROM products
WHERE id IN (
SELECT product_id
FROM order_items
GROUP BY product_id
HAVING COUNT(*) = 0
);
-- 使用EXISTS删除
DELETE FROM products p
WHERE NOT EXISTS (
SELECT 1 FROM order_items o
WHERE o.product_id = p.id
);
2.3 批量删除 #
typescript
// 删除指定ID列表
const { data, error } = await supabase
.from('products')
.delete()
.in('id', [1, 2, 3, 4, 5])
.select()
// 删除日期范围内的数据
const { data, error } = await supabase
.from('logs')
.delete()
.lt('created_at', '2023-01-01')
.select()
三、软删除 #
3.1 实现软删除 #
sql
-- 添加软删除字段
ALTER TABLE users ADD COLUMN deleted_at TIMESTAMPTZ DEFAULT NULL;
ALTER TABLE users ADD COLUMN is_deleted BOOLEAN DEFAULT FALSE;
-- 创建索引
CREATE INDEX idx_users_deleted ON users(deleted_at) WHERE deleted_at IS NULL;
3.2 软删除操作 #
typescript
// 软删除
const { data, error } = await supabase
.from('users')
.update({
deleted_at: new Date().toISOString(),
is_deleted: true
})
.eq('id', 1)
.select()
3.3 恢复软删除 #
typescript
// 恢复
const { data, error } = await supabase
.from('users')
.update({
deleted_at: null,
is_deleted: false
})
.eq('id', 1)
.select()
3.4 永久删除软删除数据 #
typescript
// 永久删除已软删除的数据
const { data, error } = await supabase
.from('users')
.delete()
.not('deleted_at', 'is', null)
.select()
3.5 使用视图过滤软删除 #
sql
-- 创建视图
CREATE VIEW active_users AS
SELECT * FROM users WHERE deleted_at IS NULL;
-- 使用视图查询
SELECT * FROM active_users;
四、级联删除 #
4.1 外键级联设置 #
sql
-- 创建表时设置级联删除
CREATE TABLE comments (
id BIGSERIAL PRIMARY KEY,
post_id BIGINT REFERENCES posts(id) ON DELETE CASCADE,
content TEXT NOT NULL
);
-- 修改现有外键
ALTER TABLE comments
DROP CONSTRAINT comments_post_id_fkey,
ADD CONSTRAINT comments_post_id_fkey
FOREIGN KEY (post_id) REFERENCES posts(id) ON DELETE CASCADE;
4.2 级联删除选项 #
text
外键删除选项
├── CASCADE: 删除父记录时自动删除子记录
├── SET NULL: 删除父记录时将外键设为NULL
├── SET DEFAULT: 删除父记录时设为默认值
├── RESTRICT: 阻止删除有子记录的父记录
└── NO ACTION: 与RESTRICT类似,延迟检查
4.3 手动级联删除 #
typescript
// 手动删除关联数据
async function deletePost(postId: number) {
// 删除评论
await supabase
.from('comments')
.delete()
.eq('post_id', postId)
// 删除文章
const { data, error } = await supabase
.from('posts')
.delete()
.eq('id', postId)
.select()
return { data, error }
}
4.4 使用事务级联删除 #
sql
-- 创建级联删除函数
CREATE OR REPLACE FUNCTION delete_user_cascade(user_id UUID)
RETURNS VOID AS $$
BEGIN
-- 删除用户的所有文章评论
DELETE FROM comments WHERE user_id = user_id;
-- 删除用户的所有文章
DELETE FROM posts WHERE author_id = user_id;
-- 删除用户资料
DELETE FROM profiles WHERE id = user_id;
-- 最后删除用户
DELETE FROM auth.users WHERE id = user_id;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
typescript
// 调用级联删除函数
const { error } = await supabase.rpc('delete_user_cascade', {
user_id: userId
})
五、TRUNCATE #
5.1 清空表 #
sql
-- 清空表数据
TRUNCATE TABLE logs;
-- 重置自增序列
TRUNCATE TABLE logs RESTART IDENTITY;
-- 级联清空关联表
TRUNCATE TABLE posts CASCADE;
-- 清空多个表
TRUNCATE TABLE logs, sessions, cache;
5.2 TRUNCATE vs DELETE #
| 特性 | TRUNCATE | DELETE |
|---|---|---|
| 速度 | 快 | 慢 |
| 触发器 | 不触发 | 触发 |
| WHERE | 不支持 | 支持 |
| 回滚 | 部分支持 | 完全支持 |
| 序列重置 | 支持 | 不支持 |
| 返回值 | 无 | 可返回删除行 |
5.3 客户端清空表 #
typescript
// 使用RPC清空表
const { error } = await supabase.rpc('truncate_table', {
table_name: 'logs'
})
sql
CREATE OR REPLACE FUNCTION truncate_table(table_name TEXT)
RETURNS VOID AS $$
BEGIN
EXECUTE format('TRUNCATE TABLE %I', table_name);
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
六、删除前检查 #
6.1 检查关联数据 #
typescript
async function safeDeletePost(postId: number) {
// 检查是否有评论
const { count } = await supabase
.from('comments')
.select('*', { count: 'exact', head: true })
.eq('post_id', postId)
if (count && count > 0) {
throw new Error(`Cannot delete post with ${count} comments`)
}
// 安全删除
const { data, error } = await supabase
.from('posts')
.delete()
.eq('id', postId)
.select()
return { data, error }
}
6.2 使用事务确保一致性 #
sql
-- 创建安全删除函数
CREATE OR REPLACE FUNCTION safe_delete_post(post_id BIGINT)
RETURNS JSON AS $$
DECLARE
comment_count INTEGER;
BEGIN
-- 检查评论数量
SELECT COUNT(*) INTO comment_count
FROM comments WHERE post_id = post_id;
IF comment_count > 0 THEN
RETURN json_build_object(
'success', false,
'error', 'Post has comments',
'comment_count', comment_count
);
END IF;
-- 删除文章
DELETE FROM posts WHERE id = post_id;
RETURN json_build_object('success', true);
END;
$$ LANGUAGE plpgsql;
七、批量删除优化 #
7.1 分批删除 #
typescript
async function batchDelete(table: string, batchSize = 1000) {
let deleted = 0
while (true) {
const { data, error } = await supabase
.from(table)
.delete()
.lt('created_at', '2023-01-01')
.limit(batchSize)
.select('id')
if (error) throw error
if (!data?.length) break
deleted += data.length
console.log(`Deleted ${deleted} records`)
}
return deleted
}
7.2 SQL分批删除 #
sql
-- 使用DO块分批删除
DO $$
DECLARE
deleted_count INTEGER;
BEGIN
LOOP
DELETE FROM logs
WHERE created_at < '2023-01-01'
LIMIT 10000;
GET DIAGNOSTICS deleted_count = ROW_COUNT;
EXIT WHEN deleted_count = 0;
COMMIT;
END LOOP;
END $$;
八、删除触发器 #
8.1 删除前触发器 #
sql
-- 创建触发器函数
CREATE OR REPLACE FUNCTION before_user_delete()
RETURNS TRIGGER AS $$
BEGIN
-- 记录删除操作
INSERT INTO user_deletion_log (user_id, email, deleted_at)
VALUES (OLD.id, OLD.email, NOW());
RETURN OLD;
END;
$$ LANGUAGE plpgsql;
-- 创建触发器
CREATE TRIGGER on_user_delete
BEFORE DELETE ON users
FOR EACH ROW
EXECUTE FUNCTION before_user_delete();
8.2 删除后触发器 #
sql
-- 创建触发器函数
CREATE OR REPLACE FUNCTION after_post_delete()
RETURNS TRIGGER AS $$
BEGIN
-- 更新用户文章计数
UPDATE profiles
SET post_count = post_count - 1
WHERE id = OLD.author_id;
RETURN OLD;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER on_post_delete
AFTER DELETE ON posts
FOR EACH ROW
EXECUTE FUNCTION after_post_delete();
九、错误处理 #
9.1 常见错误 #
typescript
const { data, error } = await supabase
.from('users')
.delete()
.eq('id', 1)
if (error) {
switch (error.code) {
case '23503':
console.error('外键约束违反,存在关联数据')
break
case '42501':
console.error('权限不足')
break
case 'PGRST116':
console.error('未找到匹配记录')
break
default:
console.error('删除失败:', error.message)
}
}
十、实战示例 #
10.1 删除用户账号 #
typescript
async function deleteUserAccount(userId: string) {
// 1. 删除用户数据
const { error: profileError } = await supabase
.from('profiles')
.delete()
.eq('id', userId)
if (profileError) throw profileError
// 2. 删除用户文章
const { error: postsError } = await supabase
.from('posts')
.delete()
.eq('author_id', userId)
if (postsError) throw postsError
// 3. 删除认证用户
const { error: authError } = await supabaseAdmin.auth.admin.deleteUser(userId)
if (authError) throw authError
return { success: true }
}
10.2 清理过期数据 #
typescript
async function cleanupExpiredData() {
const thirtyDaysAgo = new Date()
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30)
// 删除过期会话
const { error: sessionsError } = await supabase
.from('sessions')
.delete()
.lt('expires_at', new Date().toISOString())
// 删除旧日志
const { error: logsError } = await supabase
.from('logs')
.delete()
.lt('created_at', thirtyDaysAgo.toISOString())
return { success: !sessionsError && !logsError }
}
10.3 删除购物车 #
typescript
async function clearCart(userId: string) {
const { data, error } = await supabase
.from('cart_items')
.delete()
.eq('user_id', userId)
.select()
if (error) throw error
return data
}
十一、删除最佳实践 #
11.1 安全删除检查清单 #
text
删除前检查
├── 确认删除条件正确
├── 检查外键约束
├── 考虑使用软删除
├── 备份重要数据
├── 考虑级联影响
└── 记录删除操作
11.2 删除策略选择 #
text
删除策略
├── 软删除
│ ├── 可恢复
│ ├── 保留历史
│ └── 适合用户数据
│
├── 硬删除
│ ├── 节省空间
│ ├── 彻底清除
│ └── 适合临时数据
│
└── 归档
├── 数据迁移
├── 长期保存
└── 适合历史数据
十二、总结 #
删除操作要点:
| 操作 | 方法 |
|---|---|
| 删除单条 | delete().eq(‘id’, x) |
| 批量删除 | delete().in(‘id’, […]) |
| 条件删除 | delete().eq(…).lt(…) |
| 软删除 | update({ deleted_at: … }) |
| 清空表 | TRUNCATE TABLE |
| 级联删除 | ON DELETE CASCADE |
下一步,让我们学习认证系统!
最后更新:2026-03-28