在PostgreSQL数据库管理和优化工作中,准确掌握表及其相关对象的空间占用情况是性能调优的基础。pg_relation_size、pg_table_size、pg_indexes_size和pg_total_relation_size这四个函数构成了PostgreSQL空间分析的核心工具集,它们分别从不同维度揭示了表的存储结构。
pg_relation_size函数是空间分析的基础工具,它支持多种调用方式:
sql复制-- 基本用法(等效于'main'分支)
SELECT pg_relation_size(16384);
-- 指定文件类型查询
SELECT
pg_relation_size(16384, 'main') AS data_file,
pg_relation_size(16384, 'fsm') AS fsm_file,
pg_relation_size(16384, 'vm') AS vm_file,
pg_relation_size(16384, 'init') AS init_file;
各参数对应的物理文件:
注意:在PostgreSQL 9.6及以下版本中,'vm'参数对应的是'visibilitymap',高版本简化为'vm'
pg_table_size函数返回的是表本身(不含索引)占用的总空间,其计算逻辑为:
sql复制pg_table_size =
pg_relation_size(oid, 'main') + -- 主数据文件
pg_relation_size(oid, 'fsm') + -- 空闲空间映射
pg_relation_size(oid, 'vm') + -- 可见性映射
pg_relation_size(oid, 'init') + -- 初始化分支
pg_relation_size(toast_oid) -- TOAST表空间
TOAST表是PostgreSQL为解决大字段存储而设计的专用结构,当单行数据超过TOAST_TUPLE_THRESHOLD(默认2KB)时,大字段值会被压缩或线外存储到TOAST表中。
pg_indexes_size专注于表索引的空间占用:
sql复制-- 查询单个表的索引空间
SELECT pg_indexes_size(16384);
-- 查询schema下所有表的索引空间
SELECT sum(pg_indexes_size(c.oid))
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE n.nspname = 'public';
索引空间通常占表总空间的20%-50%,在OLTP系统中可能更高。B-tree索引的膨胀率需要特别关注,定期执行REINDEX可以优化空间使用。
pg_total_relation_size提供最全面的空间统计,其计算公式为:
sql复制pg_total_relation_size =
pg_table_size(oid) + -- 表本体空间
pg_indexes_size(oid) -- 所有索引空间
这个函数是评估表真实存储成本的黄金标准,特别适合容量规划场景。
sql复制SELECT
relname AS table_name,
pg_size_pretty(pg_relation_size(oid)) AS data_size,
pg_size_pretty(pg_indexes_size(oid)) AS index_size,
pg_size_pretty(pg_total_relation_size(oid)) AS total_size,
round(100 * pg_indexes_size(oid) /
nullif(pg_total_relation_size(oid), 0), 2) AS index_ratio
FROM pg_class
WHERE relkind = 'r' -- 只查询普通表
ORDER BY pg_total_relation_size(oid) DESC
LIMIT 10;
识别可能的空间异常:
sql复制-- 查找高膨胀表(数据文件与实际数据量不匹配)
SELECT
relname,
pg_size_pretty(pg_relation_size(oid)) AS physical_size,
pg_size_pretty(pg_table_size(oid)) AS table_size,
pg_size_pretty(pg_total_relation_size(oid)) AS total_size,
n_live_tup AS live_rows
FROM pg_class
JOIN pg_stat_user_tables ON relname = tablename
WHERE pg_relation_size(oid) / nullif(n_live_tup, 0) > 1000 -- 每行平均超过1KB
ORDER BY pg_relation_size(oid) / nullif(n_live_tup, 0) DESC;
基于空间使用的维护策略:
sql复制-- 生成VACUUM维护建议
SELECT
relname,
pg_size_pretty(pg_total_relation_size(oid)) AS size,
n_dead_tup AS dead_rows,
round(100 * n_dead_tup::float / nullif(n_live_tup + n_dead_tup, 0), 2) AS dead_ratio
FROM pg_class
JOIN pg_stat_user_tables ON relname = tablename
WHERE n_dead_tup > 1000 OR
pg_total_relation_size(oid) > 100 * 1024^2 -- 大于100MB的表
ORDER BY dead_ratio DESC;
当pg_table_size与pg_relation_size差异显著时,说明TOAST存储占比较大:
sql复制-- 检查大字段分布
SELECT
column_name,
data_type,
pg_size_pretty(sum(pg_column_size(column_name::text))) AS approx_size
FROM information_schema.columns
WHERE table_name = 'large_table'
GROUP BY column_name, data_type
ORDER BY sum(pg_column_size(column_name::text)) DESC;
优化方案:
索引膨胀处理方案:
sql复制-- 识别膨胀索引
SELECT
indexrelname,
pg_size_pretty(pg_relation_size(indexrelid)) AS index_size,
pg_size_pretty(pg_total_relation_size(indrelid)) AS table_size,
round(100 * pg_relation_size(indexrelid) /
nullif(pg_total_relation_size(indrelid), 0), 2) AS index_ratio
FROM pg_indexes
WHERE schemaname = 'public'
ORDER BY pg_relation_size(indexrelid) DESC
LIMIT 10;
处理建议:
对于分区表,需要聚合所有子分区的空间:
sql复制WITH partitions AS (
SELECT inhrelid::regclass AS partition
FROM pg_inherits
WHERE inhparent = 'parent_table'::regclass
)
SELECT
pg_size_pretty(sum(pg_total_relation_size(partition))) AS total_size,
pg_size_pretty(avg(pg_total_relation_size(partition))) AS avg_partition_size,
count(*) AS partition_count
FROM partitions;
创建历史空间使用表:
sql复制CREATE TABLE table_growth_monitor (
monitor_time timestamp PRIMARY KEY,
table_stats jsonb
);
-- 定期执行(通过pg_cron等工具)
INSERT INTO table_growth_monitor
SELECT
now(),
jsonb_object_agg(
relname,
jsonb_build_object(
'total_size', pg_total_relation_size(oid),
'data_size', pg_relation_size(oid),
'index_size', pg_indexes_size(oid)
)
)
FROM pg_class
WHERE relkind = 'r' AND relnamespace = 'public'::regnamespace;
当各函数计算结果不符合预期时,检查以下方面:
sql复制SELECT relname, reltoastrelid
FROM pg_class
WHERE oid = 16384;
空间计算函数需要扫描物理文件,在大表上可能较慢。在繁忙的生产环境中:
交叉验证pg_class中的存储统计:
sql复制SELECT
relpages AS heap_pages,
reltuples AS estimated_rows,
pg_relation_size(oid)/8192 AS actual_pages,
relpages - pg_relation_size(oid)/8192 AS diff
FROM pg_class
WHERE oid = 'my_table'::regclass;
作为PostgreSQL的衍生版本,瀚高数据库在存储架构上保持兼容,但需注意: