首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >pgstattuple源码探究

pgstattuple源码探究

作者头像
NickYoung
发布2026-07-09 16:26:35
发布2026-07-09 16:26:35
90
举报

本篇来自少辉,利用空闲时间深入研究pgstattuple,给年轻优秀的DBA点赞。


一、pgstattuple介绍

pgstattuple 是 PostgreSQL 数据库的一个官方扩展模块,核心作用是统计表、索引或指定表分区的详细存储信息与元组(行)状态,帮助数据库管理员(DBA)分析表膨胀、索引健康度、存储效率等问题,为性能优化(如 VACUUM、REINDEX)和存储规划提供数据支撑。

官方文档说明[1]

F.33. pgstattuple — obtain tuple-level statistics

二、pgstattuple源码探究

1、整体架构

2、入口函数:pgstattuple()

这里可以看到存在两个入口,如果pgstattuple版本小于v1.5 将会走pgstattuple(),如果大于等于v1.5将会走pgstattuple_v1_5(),最大的区别就是:pgstattuple v1.5以下查询必须用Superuser用户,v1.5版本及其以上的不在需要Superuser用户执行。

pgstattuplebyid()同上所述

3、分发函数:pgstat_relation()

根据传入的类型执行不同的函数:

从这里我们也能看出pgstattuple支持的类型:

代码语言:javascript
复制
支持的关系类型 ## 完全支持
- RELKIND_RELATION - 普通表
- RELKIND_MATVIEW - 物化视图
- RELKIND_TOASTVALUE - TOAST表
- RELKIND_SEQUENCE - 序列
- BTREE_AM_OID - B树索引
- HASH_AM_OID - 哈希索引
- GIST_AM_OID - GiST索引

## 不支持(会报错)
- GIN_AM_OID - GIN索引
- SPGIST_AM_OID - SP-GiST索引
- BRIN_AM_OID - BRIN索引
- RELKIND_VIEW - 视图
- RELKIND_FOREIGN_TABLE - 外部表
- RELKIND_PARTITIONED_TABLE - 分区表
- RELKIND_PARTITIONED_INDEX - 分区索引

4、堆表统计函数:pgstat_heap()

对于普通表、物化视图、TOAST表、序列等将会走pgstat_heap()函数进行计算。 pgstattuple只支持传统的堆表,不支持其他存储格式(如列存储)

启动扫描,初始化快照视图,获取块数。之后开始正向扫描,检查元组可见性,如果可见就累计到活跃元组统计,如果不可见,就累计到死元组统计。 同时在扫描过程中计算空闲空间,为什么是block<=tuplock呢,原因是heap_getnext可能跳过空页面,所以不能只统计有元组的页面。

最后扫描剩下的所有块的空闲空间,停止扫描并释放资源,计算表的总长度。

5、索引统计函数:pgstat_index()

动态遍历索引,扫描索引块大小,同时调用函数完成页面扫描,BTREE、HASH、GIST索引扫描函数下面有介绍,释放资源返回结果。

pgstat_btree_page()、pgstat_hash_page()、pgstat_gist_page() 这三个函数过程相似,内部都会调用pgstat_index_page() 去计算

(1)BTREE统计函数:pgstat_btree_page()

从缓冲区获取页面数据,如果是新页面,直接加页面的大小,然后获取B树页面元数据,如果页面是删除或者半死页面,则直接算作空闲页面累加free,如果是叶子节点就调用pgstat_index_page() 计算

(2)HASH统计函数:pgstat_hash_page()

先从缓冲区获取实际页面数据,然后检查页面的完整性,之后获取页面元数据,如果完全空闲就直接加页面大小的free,如果是哈希桶页面或者溢出页面,就调用pgstat_index_page() 计算,其他页面类型跳过。

(3)GIST统计函数:pgstat_gist_page()

先检查gis页面完整性,然后获取实际数据,调用就调用pgstat_index_page() 计算,根节点和内部节点不做统计。

(4)PAGE统计函数:pgstat_index_page()

根据偏移量遍历页面状态,如果是死亡状态,死项目数量加1,累加死项目的字节长度

6、输出函数:build_pgstattuple_type()

最终输出环节,计算各种指标,并输出结果

7、调试验证

调试样例构建:

代码语言:javascript
复制
-- 创建带索引的表
CREATETABLE test_index (
    idSERIAL,
    user_id INT,
    actionTEXT,
    action_time TIMESTAMPDEFAULTCURRENT_TIMESTAMP,
    details JSONB
);

ALTERTABLE test_index SET (autovacuum_enabled = off);

-- 创建多个索引
CREATEINDEX idx_test_index_user_id ON test_index(user_id);
CREATEINDEX idx_test_index_action_time ON test_index(action_time);
CREATEINDEX idx_test_index_details ON test_index USING GIN(details);

-- 插入数据(150万行)
INSERTINTO test_index (user_id, action, details)
SELECT
    floor(random() * 10000)::int,
    (array['login', 'logout', 'purchase', 'view', 'click'])[floor(random() * 5 + 1)::int],
    jsonb_build_object(
        'ip', inet_client_addr()::text,
        'device', (array['mobile', 'desktop', 'tablet'])[floor(random() * 3 + 1)::int],
        'duration', floor(random() * 3600)
    )
FROM generate_series(1, 1500000);

-- 更新带索引的字段产生死元组
UPDATE test_index 
SET user_id = floor(random() * 10000)::int,
    action_time = CURRENT_TIMESTAMP - (random() * 30)::int * '1 day'::interval
WHEREid % 4 = 0;

(1)表调试

(2)索引调试

Reference

[1]

https://www.postgresql.org/docs/current/pgstattuple.html

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-10-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 PostgreSQL运维之道 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、pgstattuple介绍
  • 二、pgstattuple源码探究
    • 1、整体架构
    • 2、入口函数:pgstattuple()
    • 3、分发函数:pgstat_relation()
    • 4、堆表统计函数:pgstat_heap()
    • 5、索引统计函数:pgstat_index()
    • 6、输出函数:build_pgstattuple_type()
    • 7、调试验证
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档