Cloud Run 应用不停机时,如何安全更新 PostgreSQL 约80万条记录?
建议将更新拆成小批次,每批使用独立事务,由后台任务执行。应用可以继续提供服务,但仍可能出现锁等待或性能下降,需要根据实际负载调整批次大小和执行速度。
先区分锁等待与性能下降
PostgreSQL 的普通 SELECT 通常通过 MVCC 读取可见的数据版本,不会因为同一行正在执行 UPDATE 而等待行锁。其他事务更新、删除相同行,或通过 SELECT ... FOR UPDATE 锁定该行时,则可能需要等待。
即使查询没有被锁阻塞,大量更新也会消耗 CPU、磁盘 I/O 和 WAL 写入资源,并产生需要清理的旧行版本。索引维护、触发器和约束检查也可能增加开销。80 万条记录是否适合安全地在线更新,不能只看数量,还要考虑行大小、索引、更新逻辑和在线流量。
每批单独提交,限制锁持有时间
可以从每批几百条开始试跑,观察耗时后再调整。这个批次大小只是起点,并非通用最优值。
执行过程建议如下:
- 确定需要更新的条件、更新内容及主键范围。
- 按有索引的稳定主键排序,读取并更新一小批记录。
- 提交当前事务,再保存本批进度。
- 视数据库负载短暂等待,然后处理下一批。
- 遇到锁等待超时或死锁时,回滚当前批次,退避后重试。
不要把所有批次放进同一个外层事务,否则行锁仍可能一直持有到最后提交。使用主键游标推进,也能避免 OFFSET 越来越大带来的扫描开销。
分批更新的 SQL 示例
以下示例仅演示执行方式。假设 your_table 使用整数主键 id,needs_update 表示是否待处理,需要将 target_column 更新为指定值。表名、字段和条件都需要替换为实际业务逻辑。
BEGIN;
SET LOCAL lock_timeout = '1s';
SET LOCAL statement_timeout = '10s';
WITH batch AS (
SELECT id
FROM your_table
WHERE id > :last_id
AND id <= :upper_id
AND needs_update IS TRUE
ORDER BY id
LIMIT :batch_size
FOR UPDATE
), updated AS (
UPDATE your_table AS t
SET target_column = :new_value,
needs_update = FALSE
FROM batch AS b
WHERE t.id = b.id
RETURNING t.id
)
SELECT COUNT(*) AS updated_count,
MAX(id) AS next_last_id
FROM updated;
COMMIT;
:last_id、:upper_id、:batch_size 和 :new_value 是参数占位符,需要按所用数据库驱动的语法绑定,不能直接当作 PostgreSQL 原生变量运行。
执行前可以记录当前最大主键作为 upper_id,初始 last_id 按实际主键范围设置。每批提交成功后,再将返回的 next_last_id 保存为进度;如果事务失败,保留原游标并重试。
示例中的 1s 和 10s 用于说明超时控制,实际值需要结合正常请求耗时、批次耗时和业务容忍度调整。事务中发生错误后,应先执行 ROLLBACK,再开始下一次尝试。
上界只用于限定本次主键范围,不代表数据快照。如果处理过的记录后来再次满足更新条件,仍需要补扫。在线业务也会修改目标字段时,还必须明确覆盖规则,必要时加入状态或版本条件,避免后台任务覆盖用户刚提交的数据。
在 Cloud Run 中安排独立任务
应用不需要为了这次更新停止服务。可以让独立后台任务连接同一个数据库执行迁移,例如使用 Cloud Run Job;具体执行时限、重试和网络连接配置应按实际环境确认。
任务需要记录进度并支持重试,避免依赖一次长时间的 HTTP 请求。开始时使用单个执行者,并限制数据库连接数。增加并发可能加重数据库压力和锁竞争。
不要直接给上面的游标方案加上 SKIP LOCKED:如果跳过被锁住的记录后继续推进游标,这些记录可能遗漏。确实需要多个执行者时,应设计任务认领机制,或明确安排补扫。
根据线上指标调速并检查结果
运行期间关注接口延迟、错误率、数据库 CPU、磁盘 I/O、连接数和锁等待;有副本时,也要关注复制延迟。负载上升时,先减小批次或延长批次间隔,必要时暂停任务,之后从已提交进度继续。
大量更新后,留意 autovacuum 是否及时清理旧行版本,不要为了这次操作随意关闭它。执行前还应确认更新条件的访问路径,避免每批都扫描整张表。
完成后,按业务条件核对剩余待更新记录,检查字段值和总量是否符合预期。这个方案适合业务允许分批生效的情况。如果 80 万条记录必须同时切换到新状态,就需要额外设计版本切换等机制,不能直接采用逐批提交。
备注:内容仅供参考。
