PostgreSQL 大表字段回填最佳实践:高并发无锁更新 + 分批提交 + 完整进度显示
在实际生产环境中,我们经常会遇到这样的需求:
“表中某个字段有几十万乃至几百万行是 NULL,需要全部改成 0,
并且不能影响线上业务(QPS 高),不能锁表,不能长事务。”
听起来简单,但如果处理不当,会导致:
-
全表锁
-
长时间阻塞线上写入
-
大事务造成 WAL 量爆炸
-
表膨胀、autovacuum 跟不上
-
UPDATE 越跑越慢
-
线上接口 RT 飙升
这篇文章将结合 真实生产案例(QPS≈3000),讲解一个完全可落地、可长时间运行、不会锁表的 backfill 方案:
📌 小批更新 + 分批提交 + SKIP LOCKED + 部分索引 + 进度百分比 + 性能计时
这里,我有36万数据,每批只更新10个,等待0.5s,大约要更新15个小时,可以做参考
beta环境没有并发,1s可以更新1000个,但是到了Prod,10个数据都需要1.5s去更新,可以说是很耗时了
同时一个简单的update,居然需要这么多方式去做辅助更新,体现了我们从一开始设计字段时的重要性。
🚨 常见坑点(很多人不注意)
❌ 坑 1:直接 UPDATE 整表,轻则卡 10 分钟,重则死锁
UPDATE table_account SET is_reseller = 0 WHERE is_reseller IS NULL;
为什么危险?
-
扫全表(可能几千万行)
-
锁大量行
-
大量 dead tuple 导致 WAL 写放大
-
autovacuum 无法及时清理
-
一个大事务 commit 可能卡住几十秒甚至更长
❌ 坑 2:分批 SELECT…FOR UPDATE,但不加 SKIP LOCKED,会被业务锁住
FOR UPDATE -- ❌ 会等待别人的锁
业务正在更新某些行,你的回填脚本就会被锁住 → 整个 batch 卡住。
👉
