1. 题目
生产表持续接收订单写入,查询团队希望新增复合索引;另一张表的索引膨胀,需要重建。表规模很大,发布窗口只有低峰期,且不能阻塞普通读写。请给出可回滚、可观测的在线方案。
2. 约束与澄清
- 先确认 PostgreSQL 版本、表和索引类型、主从拓扑、磁盘余量及写入峰值。
- 区分
CREATE INDEX、CREATE INDEX CONCURRENTLY、REINDEX CONCURRENTLY的适用场景。 - 并发构建减少写锁影响,但会扫描表多次、占用 CPU/IO,并且不能放在事务块中。
- 先定义可接受的构建时长、锁等待、延迟和失败后的清理窗口。
3. 核心思路
普通建索引可能阻塞写入;并发建索引允许持续插入、更新和删除,但需要更长时间和更多资源。应先在影子环境用真实规模估算,生产执行时设置 locktimeout、statementtimeout 和资源监控。索引建成后还要确认规划器选择、回滚路径和副本一致性,不能把命令成功当作业务成功。
4. 参考流程
text
preflight:
verify_version_replicas_disk_and_query_shape()
estimate_scan_cost_on_shadow_copy()
reserve_maintenance_window_and_abort_thresholds()
build:
set lock_timeout = short
set statement_timeout = bounded
CREATE INDEX CONCURRENTLY idx_orders_customer_time
ON orders (customer_id, created_at DESC)
verify:
inspect_index_state_and_size()
EXPLAIN (ANALYZE, BUFFERS) representative_queries()
compare_write_latency_replica_lag_and_error_rate()重建时优先使用 REINDEX CONCURRENTLY;若失败留下无效的临时索引,先按文档识别并清理,再重新尝试。部署脚本要保证命名、幂等和告警可追踪,不能把并发 DDL 藏在普通事务迁移里。
5. 失败场景与取舍
并发构建可能因长事务、冲突快照或磁盘不足失败;失败对象可能保持无效状态,继续占用空间。构建期间 CPU、IO 和 WAL 增长会拖慢业务并拉大复制延迟,因此要限速或暂停。若无法接受构建成本,可以先优化查询、分区或使用在线迁移工具,但工具仍需验证触发器、回填、切换和回滚行为。
6. 验证与观测
- 记录索引状态、大小、构建耗时、锁等待、WAL、CPU/IO 和副本延迟。
- 对代表性查询比较执行计划、扫描行数、p95/p99 延迟和写入吞吐。
- 检查长事务、无效索引、重复索引及约束依赖。
- 观察多个完整业务高峰后再删除旧索引,并保留恢复脚本。
7. 常见误区
- 认为
CONCURRENTLY完全不拿锁,忽略短暂锁等待和资源竞争。 - 把
CREATE INDEX CONCURRENTLY放进事务块,导致命令直接失败。 - 只看建索引命令返回成功,不检查无效索引、复制延迟和实际计划。
- 在没有磁盘、WAL 和长事务预算时直接对超大表重建。
8. 面试评分点
能区分并发 DDL 语义
应说明普通和并发创建、重建索引的锁、扫描次数、事务限制与资源代价。
能规划生产前置检查
应检查版本、磁盘、长事务、复制拓扑、查询形状,并先用接近真实规模的数据估算。
能设计失败恢复
应处理无效索引、超时、磁盘不足和复制落后,说明清理、重试与回滚步骤。
能用业务指标验证
应比较执行计划、p95/p99、写入延迟、WAL、锁等待和副本延迟,而不是只看 DDL 返回码。