千万级表直接DELETE WHERE易卡死,因MVCC机制导致行锁、WAL膨胀、触发器/外键校验耗时剧增;Swap方案通过CREATE TABLE AS SELECT+RENAME原子切换,规避长事务与锁表风险。

为什么不能直接 DELETE WHERE 条件删千万行
直接 DELETE FROM orders WHERE status = 'archived' 在千万级表上极易卡死:InnoDB 或 PostgreSQL 的 MVCC 机制会为每行生成新版本、写 WAL、膨胀事务日志,同时持有行锁或页锁,导致其他查询被阻塞。更糟的是,如果表有外键或触发器,每次删除还要校验关联表——嵌套校验会让耗时从分钟级飙升到小时级。
Swap 方式核心思路:CREATE TABLE AS SELECT + RENAME
不改原表、不删数据、不锁长事务,而是用“以空间换时间+原子切换”的方式安全落地。关键在于只保留要留下的数据,把旧表当临时备份。
- 先确保目标字段(如
created_at、status)有高效索引,否则SELECT扫描太慢 - 用
CREATE TABLE orders_new AS SELECT * FROM orders WHERE status != 'archived'构建干净新表(注意:不带约束、索引、默认值) - 手动补全结构:
ALTER TABLE orders_new ADD PRIMARY KEY (id)、重建索引、加外键、设默认值 - 原子切换:
BEGIN; ALTER TABLE orders RENAME TO orders_old; ALTER TABLE orders_new RENAME TO orders; COMMIT; - 确认无误后,再异步
DROP TABLE orders_old(别在主事务里干这事)
容易踩的坑:约束、权限、序列和统计信息
Swap 后最常出问题的不是数据,而是元数据断层:
-
CREATE TABLE ... AS SELECT不复制DEFAULT、CHECK、NOT NULL约束,必须显式ALTER TABLE ... ALTER COLUMN ... SET NOT NULL补上 - 外键引用原表?切换后需重跑
ALTER TABLE child_table ADD FOREIGN KEY (order_id) REFERENCES orders(id) - 自增序列(
SERIAL)不会自动绑定到新表,得ALTER SEQUENCE orders_id_seq OWNED BY orders.id - 权限丢失:新表默认只有创建者权限,要用
GRANT SELECT, INSERT ON orders TO app_user重新赋权 - 别忘了
ANALYZE orders,否则查询计划器还按老表统计信息走,可能选错索引
什么情况下 Swap 比 TRUNCATE + INSERT 更合适
当你需要保留部分数据(比如只删 80%,留 20%),又不想让业务长时间等待 DELETE 锁表时,Swap 是唯一稳妥选择。TRUNCATE 只能清空整表,而分批 DELETE 容易因主从延迟、锁竞争、WAL 压力失控崩盘。Swap 把高风险操作拆成可验证的离线步骤,真正影响业务的时间仅限于最后那几毫秒的 RENAME。
但要注意:Swap 需要双倍磁盘空间,且期间不能有对原表的 DDL(如加列),否则 RENAME 可能失败。线上执行前,务必在从库或影子库上预演一遍完整流程。










