pgstattuple源码探究


本篇来自少辉,利用空闲时间深入研究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支持的类型:

- 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、调试验证

调试样例构建:

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);
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





请使用浏览器的分享功能分享到微信等