概述
PostgreSQL 的 DDL 需要 AccessExclusiveLock。如果长事务持有冲突锁,ALTER 会进入锁队列等待,而后续 SELECT 又会排在等待的 ALTER 之后,因为锁大致按顺序授予。一条慢查询可能因此阻塞整张表。设置较短的 lock_timeout 可以让迁移在演变为故障前放弃。
步骤
- 在迁移文件第一条语句之前设置超时:
SET lock_timeout = '3s';
SET statement_timeout = '60s';
ALTER TABLE orders ADD COLUMN fulfillment_state text;
- 在部署脚本中按退避重试迁移,避免一次失败就需要人工重跑:
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
- 故意制造阻塞验证:一个会话执行
BEGIN; SELECT * FROM orders LIMIT 1; SELECT pg_sleep(120);,另一个会话运行迁移。 - 让 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,应通过带超时和重试的部署流程。