PostgreSQL 空闲空间映射(FSM)深度解读

PostgreSQL 空闲空间映射(FSM)深度解读

PostgreSQL 数据库是以文件的方式储存数据,文件会存在DATABASE下,而这些文件一般包括表和索引(除了哈希索引)的数据以及相关的属性信息,存这些信息都有一个空闲空间映射(FSM)来保持对关系中可用空间的跟踪中,当创建表并且插入数据后,在autovacuum参数开启时并且触发vacuum后,或手动执行vacuum后,可以通过pg_relation_filepath系统表查看表的filepath保存的路径,并找到对应的该表对应的FSM文件,而FSM(Free Space map)是什么,有什么作用呢 ?

1.在PostgreSQL 中 FSM(Free Space Map,空闲空间映射)文件是用于跟踪表和索引中数据块空闲空间的关键结构。当插入新数据时PG需要找到表中或者索引中存在足够的空闲空间的数据块(默认的为8KB,wal_block_size参数查看)来保存数据,而FSM文件则记录了每个数据块的空闲空间量,数据库在插入新数据时,可以快的定位合适的快进行插入,可以减少磁盘IO的读取次数从而提高插入的效率。而当表的数据更新或者删除时便会产生数据块的空闲空间,FSM也会追踪这些空间用于后续插入的数据能够进行复用,从而避免频繁扩展新的数据块,减少硬件磁盘的碎片,提高查询的效率。使用PG的vacuum命令可以释放数据块内的空闲空间。

2.FSM 是使用1字节储存表的页面的内的空闲空间,储存的1字节是状态,范围是0-255共256种状态,计算的方式是page大小除以256,就能得到256个值,该值被称为CAT的值,CAT 值与FSM的关系映射如下:

外链图片转存中...(img-c29O9r0D-1761566540654)

3.当我们频繁的插入数据,频繁的删除和更新数据,FSM的状态的更新会比较频繁,同时伴随着vacuum的执行,需要高效的处理FSM的空闲空间映射,PG采用了FSM High Level(FSM 高层结构)的技术来高效的管理和查询数据块的空闲空间信息,从而被复用,FSM High Level内部采用的是二叉树结构,二叉树分为三层结构,结构的顺序为Level 2 、Level 1 、Level0,Level 2 是最顶层并且仅有一个页面,所以逻辑页号为0,可以表示为<2,0>,最多具有4000个Level1 的孩子节点,并且孩子节点的逻辑页号为<1,0>...<1,3999> 。因为每个页面都具有4000个孩子节点,所以Level 0 层保存的就会有 4000 * 4000 的页面节点,逻辑页号为<0,1>...<0,4000*4000>。

4.物理页号的由来为FSM的文件的大小除以页的大小(wal_block_size查看。默认为8K),得到的FSM的总页数,最大的物理页数显示未"总页数-1",例如12345_fsm 文件大小为 32768 字节(32KB),则总页数为 32768 / 8192 = 4,物理页号范围是 0~3。

5.PostgreSQL 在插入数据时,会先判断FSM中的空闲空间映射的关系,通过FSM 的High Level 树形索引加快查询的速度,因为每层都有相对应的索引,再加上High Level的体积较小,很容易倍内存缓存,在保存数据时也会最大的剪枝掉空闲空间不足的分支,仅保留候选节点来加快数据的储存速度。

代码案例:

sql 复制代码
fsm_vm_case=# create  table fsm_table(id int,name varchar(20));
CREATE TABLE
fsm_vm_case=# select pg_relation_filepath('fsm_table');
 pg_relation_filepath
----------------------
 base/16415/16419
(1 row)

-- 仅创建表后没有插入数据,没有FSM文件
fsm_vm_case=# \! du -sh /pgcc/pgdata16/base/16415/16419*
0	/pgcc/pgdata16/base/16415/16419

-- 插入一条数据后在没有执行vacuum时没有FSM相关的文件
fsm_vm_case=# insert into fsm_table values(1,'fmstest');
INSERT 0 1
fsm_vm_case=# \! du -sh /pgcc/pgdata16/base/16415/16419*
8.0K	/pgcc/pgdata16/base/16415/16419

-- 手动执行vacuum后会出现FSM文件和vm文件
fsm_vm_case=# vacuum fsm_table;
VACUUM
fsm_vm_case=# \! du -sh /pgcc/pgdata16/base/16415/16419*
8.0K	/pgcc/pgdata16/base/16415/16419
24K	/pgcc/pgdata16/base/16415/16419_fsm
8.0K	/pgcc/pgdata16/base/16415/16419_vm

-- 查看FSM的信息需要使用到freespacemap插件
postgres=# create extension pg_freespacemap;
CREATE EXTENSION

-- 在一行数据的情况下查看FSM的保存的信息
fsm_vm_case=# select * from fsm_table;
 id |  name
----+---------
  1 | fmstest
(1 row)
-- 返回的blkno和avail
fsm_vm_case=# select * from pg_freespace('fsm_table');
 blkno | avail
-------+-------
     0 |  8096
(1 row)

-- 当插入到2万行数据后,执行vacuum后显示,blkno表示使用了178个物理块,avail表示178个物理块还有1216的字节用于插入数据。
fsm_vm_case=# insert into fsm_table select random()*10,(random()*10)::int from generate_series(1,20000);
INSERT 0 20000
fsm_vm_case=# vacuum analyze fsm_table ;
VACUUM
fsm_vm_case=# select * from pg_freespace('fsm_table') where avail > 0;
 blkno | avail
-------+-------
   178 |    1216
(1 row)

-- 再次插入了20条数据,执行vacuum 后,发现在178的物理块中仅剩480字节的可用空间了。
fsm_vm_case=# insert into fsm_table select random()*10,(random()*10)::int from generate_series(1,20);
INSERT 0 20
fsm_vm_case=# vacuum analyze fsm_table ;
VACUUM
fsm_vm_case=# select * from pg_freespace('fsm_table') where avail > 0;
 blkno | avail
-------+-------
   178 |   480
(1 row)

-- 使用表的大小除以物理块的大小约等于8192(因为178物理块没有插入满所以有偏差)
fsm_vm_case=# \dt+ fsm_table
                                      List of relations
 Schema |   Name    | Type  |  Owner   | Persistence | Access method |  Size   | Description
--------+-----------+-------+----------+-------------+---------------+---------+-------------
 public | fsm_table | table | postgres | permanent   | heap          | 1464 kB |
(1 row)

fsm_vm_case=# select 1464 / 178.0;
      ?column?
--------------------
 8.2247191011235955
(1 row)

-- 使用pgstattuple插件可以详细的观察表或索引的空间使用细节
fsm_vm_case=# create extension pgstattuple;
CREATE EXTENSION
fsm_vm_case=# select * from pgstattuple('fsm_table');
-[ RECORD 1 ]------+--------
table_len          | 1466368
tuple_count        | 40441
tuple_len          | 1215234
tuple_percent      | 82.87
dead_tuple_count   | 0
dead_tuple_len     | 0
dead_tuple_percent | 0
free_space         | 5472
free_percent       | 0.37

fsm_vm_case=# select count(*) from fsm_table;
-[ RECORD 1 ]
count | 40441
相关推荐
这个DBA有点耶2 小时前
AI写的SQL跑崩了生产库,这锅谁背?
数据库·人工智能·程序员
镜舟科技2 小时前
Databricks 再提 LTAP,AI 时代的数据底座为何重回大一统叙事?
数据库·架构·agent
Databend3 小时前
从湖仓升级为 Agent 时代的数据控制面,Snowflake 和 Databricks 有哪些布局
大数据·数据库·agent
ClouGence6 小时前
SQL Server CDC 能放到 Always On 备库读吗?一文讲透原理与实践
数据库·sql server
先吃饱再说1 天前
存储的进化:从 MySQL 到浏览器缓存,数据到底住在哪?
数据库
Nturmoils1 天前
字段太多看不全,ksql 的展开模式和输出控制怎么用
数据库·后端
Databend1 天前
Agent 轨迹分析与归因的数据工程实践
大数据·数据库·agent
这个DBA有点耶1 天前
SQL改写进阶:标量子查询的“隐形代价”与消除实战
数据库·mysql·架构
smallyoung1 天前
数据库乐观锁深度解析:MySQL、PostgreSQL 实战 + Spring Boot 集成指南
数据库·mysql·postgresql
parade岁月1 天前
MySQL JOIN解析:朴实无华但食之有味
数据库·后端