五月 9, 2026
摘要:在本教程中,您将学习如何在 PostgreSQL 中检查一个 B-tree 索引是否要重建。
目录
如果一个索引页满了,而 INSERT 操作需要插入新条目,PostgreSQL 可以拆分该页面。但如果 UPDATE 或 DELETE 移除了一个索引条目,随后的 VACUUM 操作只会让该页面留下一些膨胀空间(空洞)。虽然 PostgreSQL 会回收那些变得完全空的索引页,但它并不会费力去合并那些稀疏填充(半满半空)的页面。
评估是否应该重建 B 树索引
要衡量你的 B-tree 索引状况如何,请使用 pgstattuple 扩展。函数 pgstatindex() 可以提供关于 B-tree 索引的信息:
- avg_leaf_density:索引页被条目填充的平均百分比,或者说是 100 减去索引膨胀率。
- leaf_fragmentation:在相同索引层级上,物理位置紧随逻辑下一页(即右兄弟节点)之后的索引页百分比。它是衡量范围扫描不得不“向后跳转”频率的一个指标。
如果 avg_leaf_density 非常低,特别是如果它还在不断缩小,那就表明需要重建索引。至于较高的 leaf_fragmentation 值是否是重建索引的理由,很大程度上取决于你是否执行大规模的索引范围扫描。我认为在这两个指标中,avg_leaf_density 是更重要的衡量标准。
请注意,pgstatindex() 会扫描整个索引,因此肯定会消耗系统资源。如果你将其集成到监控系统中,不要每十分钟就运行一次探测。大概每周一次就足够了,最好是在数据库负载较低的时候进行。
检查 B 树索引
让我们创建一个包含一百万行的表,对该表禁用自动清理,以便能够演示 VACUUM 的效果。
CREATE TABLE large_table (id bigint)
WITH (autovacuum_enabled = off);
INSERT INTO large_table (id)
SELECT * FROM generate_series(1, 1000000);
CREATE INDEX large_idx ON large_table (id);
CREATE EXTENSION pgstattuple;
SELECT tree_level, deleted_pages, avg_leaf_density, leaf_fragmentation
FROM pgstatindex('large_idx');
tree_level | deleted_pages | avg_leaf_density | leaf_fragmentation
------------+---------------+------------------+--------------------
2 | 0 | 90.01 | 0
你可以看到索引处于健康状态。注意,尽管表并不算特别小,但索引深度只有三层(两层加根节点)。让我们删除三分之一的行:
DELETE FROM large_table WHERE id % 3 = 0;
SELECT tree_level, deleted_pages, avg_leaf_density, leaf_fragmentation
FROM pgstatindex('large_idx');
tree_level | deleted_pages | avg_leaf_density | leaf_fragmentation
------------+---------------+------------------+--------------------
2 | 0 | 90.01 | 0
什么也没变,因为索引条目仍然在原位。VACUUM 会改变这一点:
VACUUM large_table;
SELECT tree_level, deleted_pages, avg_leaf_density, leaf_fragmentation
FROM pgstatindex('large_idx');
tree_level | deleted_pages | avg_leaf_density | leaf_fragmentation
------------+---------------+------------------+--------------------
2 | 0 | 60.09 | 0
现在膨胀率正如预期那样增加了。没有任何索引页变空并被删除。leaf_fragmentation 没有变化,因为索引页仍然保持原始顺序。如果 PostgreSQL 删除并重用了一些空页,我们会在那里看到变化。
结论
无论你之前使用其他关系型数据库系统的经验如何,都要抵制住定期自动重建索引的诱惑。在重建索引之前,先用 pgstatindex() 测试一下这样做是否有意义。你会发现,通常情况下,根本没有必要重建。