内容提要
实测 PostgreSQL 17 多租户单租户删除:级联删除与逐表删除耗时相同,瓶颈在外键触发器,需为外键列建索引。删除不阻塞其他租户读写,但阻塞该租户写入并阻碍 vacuum。批量删除应按执行计划选批次,过大反而全表扫描更慢。删除后数据库不缩小,空间留给新租户,数据仍残留在 WAL、备份、审计日志中。分区 DROP 最快,但需先删引用分区,且加排他锁,应设 lock_timeout。
延伸解读
外键索引:删除性能的关键
文章实测显示,删除租户时外键触发器是主要瓶颈。即使先删子表,PostgreSQL 仍会为每张发票触发检查,若无索引则每次检查都全表扫描。例如,删除2000张发票的租户,无索引时耗时21.8秒,有索引仅0.12秒。因此,务必为所有外键列建立索引,否则删除操作可能极慢。
批量删除:计划决定批次大小
批量删除可缩短事务,但批次过大会适得其反。文章测试中,批次从1000增至5000时,总耗时从1.7秒升至3.0秒,因执行计划从索引扫描变为全表扫描。建议用EXPLAIN ANALYZE确定批次大小,并随数据增长重新评估,避免凭感觉设置。
空间回收与数据残留
删除租户后数据库文件不会缩小,空间留给新租户,但索引可能增长。VACUUM FULL可回收空间,但需排他锁,期间表不可读。此外,数据仍残留在WAL、备份、审计日志等中,DELETE仅使数据在数据库中不可达。需根据保留策略告知租户数据存留位置。
分区DROP:快速但需谨慎
分区DROP速度极快,但有两个陷阱:必须先删除引用分区,否则外键约束会阻止操作;且DROP和DETACH会获取整个分区表的ACCESS EXCLUSIVE锁,阻塞其他租户查询。建议设置lock_timeout并重试,且操作应在独立事务中完成。
Q&A
在 PostgreSQL 中删除一个租户,级联删除和逐表删除哪个更快?
两者耗时相同。实测中,删除最大租户(60,000 张发票)级联删除耗时 1,342–1,351 毫秒,逐表删除耗时 1,341–1,357 毫秒。瓶颈在外键触发器,而非删除方式。
删除租户时为什么需要为外键列建索引?
因为 PostgreSQL 会为每个被删除的父行触发外键检查,如果没有索引,每次检查都会扫描子表,导致删除极慢。例如,删除 2,000 张发票的租户,无索引时耗时 21.8 秒,有索引时仅 0.12 秒。
删除一个租户会阻塞其他租户的读写吗?
不会直接阻塞其他租户的读写。删除只锁定被删除的行和租户行。其他租户的插入和查询正常进行。但针对被删除租户的写入会等待删除事务提交,然后因外键约束失败。此外,长事务会阻碍 vacuum 清理死行,影响所有租户。
批量删除租户数据时,批次大小应该如何选择?
应通过 EXPLAIN ANALYZE 分析一个批次的执行计划来确定,而不是凭感觉。批次过大会导致计划变为全表扫描,反而更慢。例如,批次 5,000 时总耗时 3.0 秒,而批次 1,000 时仅 1.7 秒。且计划翻转点随数据增长而变化,需定期重新评估。
删除租户后,数据库文件会缩小吗?空间能回收吗?
不会。VACUUM 不会将空间归还操作系统,数据库文件大小不变。空间可被新租户重用,但索引可能增长。只有 VACUUM FULL 能回收空间,但会持有排他锁重写表,期间无法读取,通常不值得。
DELETE 后租户数据真的从磁盘上消失了吗?
没有。VACUUM 仅标记空间可重用,不会擦除旧字节。数据仍残留在 WAL、备份、审计日志、导出文件、搜索索引和缓存中。只有 VACUUM FULL 会重写数据文件,但旧文件删除不等于磁盘块被擦除。
使用分区 DROP 删除租户有什么优缺点?
优点:极快(毫秒级),立即释放磁盘空间,WAL 写入少。缺点:需先删除引用分区,且 detach/drop 分区会获取 ACCESS EXCLUSIVE 锁,阻塞其他租户查询。建议设置 lock_timeout 并重试。