← 中文资源

简体中文版本

限制数据库迁移的等锁时间

在迁移中设置 lock_timeout 与 statement_timeout,让 DDL 快速失败而不是排队阻塞整张表的读写。

阅读英文原文

概述

PostgreSQL 的 DDL 需要 AccessExclusiveLock。如果长事务持有冲突锁,ALTER 会进入锁队列等待,而后续 SELECT 又会排在等待的 ALTER 之后,因为锁大致按顺序授予。一条慢查询可能因此阻塞整张表。设置较短的 lock_timeout 可以让迁移在演变为故障前放弃。

步骤

  1. 在迁移文件第一条语句之前设置超时:
SET lock_timeout = '3s';
SET statement_timeout = '60s';
ALTER TABLE orders ADD COLUMN fulfillment_state text;
  1. 在部署脚本中按退避重试迁移,避免一次失败就需要人工重跑:
for attempt in 1 2 3 4 5; do
  psql "$DATABASE_URL" -v ON_ERROR_STOP=1 \
    -f migrations/0042_add_fulfillment_state.sql && exit 0
  sleep $((attempt * 10))
done
exit 1
  1. 故意制造阻塞验证:一个会话执行 BEGIN; SELECT * FROM orders LIMIT 1; SELECT pg_sleep(120);,另一个会话运行迁移。
  2. 让 DDL 尽量短:先加可空列,再分批回填,约束使用 NOT VALID 后单独 VALIDATE CONSTRAINT。

验证

预期迁移在约 3 秒后报 canceling statement due to lock timeout 并退出,阻塞会话不受影响;阻塞结束后重试成功。若每次都在同一时段超时,用 pg_blocking_pids 找到阻塞者,而不是无限加大超时。

注意事项

  • 迁移工具若为每个文件新开会话,SET 可能丢失,应把超时写入连接参数或每个文件开头。
  • lock_timeout 不会降低 SET NOT NULL 或全表重写的成本。
  • 重试要求 SQL 可重复执行,加列可用 IF NOT EXISTS,索引和约束需单独判断。
  • 不要在高流量时段手工执行长 DDL,应通过带超时和重试的部署流程。