存储结构
一、逻辑存储结构
逻辑存储结构通常指表、索引、视图、函数等逻辑对象(也称数据库对象)
创建一个 Database时会为这个Database创建一个名为public的默认Schema。每个Database可以有多个Schema,在这个数据库中创建其他数据库对象时如果没有指定Schema,都会在public这个Schema中。Schema可以理解为一个数据库中的命名空间,在数据库中创建的所有对象都在Schema中创建,一个用户可以从同一个客户端连接中访问不同的Schema。不同的Schema中可以有多个相同名称的Table、Index、View、Sequence,Function等数据库对象。
参考数据库对象
二、物理存储结构
物理存储结构表示数据库在物理层面上是如何存储的。
在初始化initdb
数据库集簇(database cluster)时会在指定目录下创建基础目录(即通常所说的数据目录,会被配置到环境变量PGDATA中)。所有相关组成数据库的物理文件的分布:
子目录/文件 | 描述 |
base | 默认的数据库目录 |
global | 数据库集簇中的所有表 |
pg_commmit_ts | 事务提交的时间戳目录 |
pg_dynshmem | 动态共享内存目录 |
pg_logical | 逻辑解码的状态数据 |
pg_multixact | 多个事务的状态目录 |
pg_notify | 包含系统发出的 LISTEN/NOTIFY 的目录 |
pg_replslot | 复制槽目录 |
pg_serial | 已提交的可序列化事务的信息目录 |
pg_snapshots | 快照目录(pg_export_snapshot函数导出的快照信息文件) |
pg_stat | 统计目录 |
pg_stat_tmp | 临时统计目录 |
pg_subtrans | 子事务的状态数据 |
pg_tblspc | 各个用户自建表空间指向的软链接符号目录 |
pg_twophase | 两阶段提交目录 |
pg_wal | wal segment 目录 |
pg_xact | 事务提交状态的目录 |
pg_hba.conf | 基于主机的认证配置文件,用于配置允许哪些IP的主机通过哪种方式访问数据库 |
pg_ident.conf | 基于主机的映射文件 |
PG_VERSION | 包含PostgreSQL主版本号 |
postgresql.auto.conf | 存储使用 |
postgresql.conf | 默认的数据库参数文件 |
postmaster.opts | 记录服务器上次启动时的使用的命令行参数 |
postmaster.pid | 当前数据库运行的进程文件 |
下面介绍一些常用的的目录及文件
1、base 目录
1.1 概述
base目录存储用户创建的数据库文件,及隶属于用户数据库的所有关系,比如表、索引等
1.2 一级目录
postgres=# SELECT oid,datname from pg_database;
oid | datname
-------+------------
13892 | postgres
16385 | maleah_test -- 自己建的数据库
1 | template1
13891 | template0
16408 | db_new_tsp -- 在自建的表空间里的数据库(下文有详细介绍)
(5 rows)
1.3 二级文件
存储某个数据库内的所有关系,包括表、索引、视图等。
二级子文件分为三大类:
- 以
oid
命名的主数据文件; - 以
_fsm
结尾的空闲空间映射文件; - 以
_vm
结尾的可见性映射文件。
-- 以 上图中的 1247 为例:1247是普通表 pg_type 的oid
postgres=# select oid,relname,relkind from pg_class where oid = 1247;
oid | relname | relkind
------+---------+---------
1247 | pg_type | r
(1 row)
--- relkind 值为 r:表示普通表
- 主数据文件(具体介绍请看第三节
堆表文件的内部布局
) - FSM(FREE SPACE MAP) - 空闲空间映射文件
- 存储相应表或索引文件中每个页面上的空闲空间信息。hash索引除外
- 详情可查看src/backend/storage/freespace/README
- 可使用扩展
pg_freespace
查看特定表或索引上的空闲空间信息。
maleah_test=> \c maleah_test postgres
You are now connected to database "maleah_test" as user "postgres".
maleah_test=# create extension pg_freespacemap ;
CREATE EXTENSION
maleah_test=# create table tab_fsm(id int,name varchar);
CREATE TABLE
maleah_test=# insert into tab_fsm select id,md5(id::varchar) from generate_series(1,10000) as id;
INSERT 0 10000
maleah_test=# select *,round(100*avail/8192 ,2) as "freespace ratio" from pg_freespace('tab_fsm') where avail !=0;
blkno | avail | freespace ratio
-------+-------+-----------------
83 | 5440 | 66.00
(1 row)
详情可查看官网:pg_freespacemap扩展
- VM(VISIBILITY MAP)可见性映射文件
- 存储页面的可见性信息,包含所有活跃事务的元组,也包含需要冻结的一些元组
- 可使用扩展
pg_freespace
查看
maleah_test=# create extension pg_visibility ;
CREATE EXTENSION
maleah_test=# create table tab_vm as select * from tab_fsm ;
SELECT 10000
-- 删除前
maleah_test=# select * from pg_visibility_map('tab_vm') where blkno = 0;
blkno | all_visible | all_frozen
-------+-------------+------------
0 | t | f
-- 删除 id<100 的元组
maleah_test=# delete from tab_vm where id<100;
DELETE 99
-- 删除后重新查看:all_visible t-->f,此时元组不可见
maleah_test=# select * from pg_visibility_map('tab_vm') where blkno = 0;
blkno | all_visible | all_frozen
-------+-------------+------------
0 | f | f
(1 row)
详情可查看 pg_visibility扩展
- 每一个堆元组都存储两位:如果第一位被设置,那么表示页面都是可见的,既然可见,那么该页面不包含任何清理的元组信息。索引扫描可以使用索引元组及逆行查询。如果第二位被设置,那么vacuum将会跳过所有的页面访问,提高vacuum的速度。
/* src/backend/access/heap/visibilitymap.c */
The visibility map is a bitmap with two bits (all-visible and all-frozen)per heap page.
- A set all-visible bit means that all tuples on the page areknown visible to all transactions, and therefore the page doesn’t need tobe vacuumed.
- A set all-frozen bit means that all tuples on the page arecompletely frozen, and therefore the page doesn’t need to be vacuumed evenif whole table scanning vacuum is required (e.g. anti-wraparound vacuum).The all-frozen bit must be set only when the page is already all-visible.
- 索引没有可见性映射文件,只有空闲空间映射文件
2、global 目录
2.1 概述
存储pg_control及数据库集群维度的数据库及其关系,非客户维度的数据
maleah_test=# select oid,relname,relkind from pg_class where oid = 6115 ;
oid | relname | relkind
------+-------------------------------+---------
6115 | pg_subscription_subname_index | i
(1 row)
2.2 pg_control文件
pg_control文件记录数据库集群控制信息,包括initdb初始化、WAL和checkpoint的信息
3、pg_wal 目录
3.1 概述
pg_wal是WAL机制中的wal日志存储目录
PG 版本 | 目录名 |
<10 | pg_xlog |
>=10 | pg_wal |
3.2 WAL(write ahead log) 机制
数据库重做日志。从名字上看也可以简单的理解为,在数据写入磁盘前先记录日志。
3.3 文件结构
- wal segment 文件(wal段文件)
- 存储着数据库行记录明细,每一条记录明细都是服务于数据库恢复操作的,确保前后数据一致。首先针对数据的任意一次修改操作均被记录在wal段文件中,包括insert、update和delete,其次系统的一些管理行为也会被记录在wal段文件中,例如事务提交和vacuum等行为。
- 文件名:26个十六进制数由三部分组成
LSN(Log Sequnce Number)日志序列号:记录 wal 日志的绝对位置,是一个不断增长的8字节(64 bit )的长数字
- 注意区分数据库log日志文件和wal文件。从内容上:数据库日志是记录数据库层面上的操作,比如记录启动数据库等操作(记录日志的内容可自行定义),而 针对数据的任意一次修改操作均被记录在wal段文件中。从作用上:数据库日志-当系统发生错误时可以从 log 中查找 error,并解决问题;而wal日志用于数据库备份恢复及流复制等,与Oracle中的Redo Log功能相同。
- 文件大小:默认16MB,可以在
initdb
时通过--wal-segments
选项来配置wal文件大小
- .history
.history文件内容包括原.history文件,当前时间线切换记录和切换原因,作用于数据库的时间点恢复行为。当数据库引擎从多个时间线的备份中恢复时,数据库从.history文件中找到从pg_control的start_timeline到指定的recovery_target_timeline间的所有wal段文件进行恢复。 - archive_status 目录
archive_status是wal段文件的备份目录,包括.ready和.done文件。超出wal_keep_segments数目限制的wal日志会在archive_status目录内被标记,归档操作完成后被进一步移除。
- .ready
.ready是同名wal段文件在archive_status目录内的标记文件,代表该wal段文件可被归档。wal段文件在数据目录中的存储文件数量是有上限的,可以通过wal_keep_size 参数来约束,因此数据库引擎在wal段文件个数达到上限后会在archive_status目录内增加可移除的wal段文件的标记文件,文件名是原wal段文件名后增加.ready后缀,等待归档工具进行归档。 - .done
.done是同名wal段文件在archive_status目录内的标记文件,代表该wal段文件已被归档,可以被清理。数据库引擎默认通过archive_command命令对.ready文件进行归档,归档成功与否取决于archive_command命令返回true还是false,当archive_command返回true时,代表与.ready文件同名的wal段文件已被归档,引擎再将该文件的扩展名重命名为.done,等待数据库引擎在下一次的checkpoint时进一步清理原wal段文件。
4、pg_xact 目录
4.1 概述
事务提交日志存储目录,默认256KB
4.2 commit log
事务提交日志存储数据库的单个事务运行状态。Commit Log由共享内存中一组8KB的page组成,每个page包含一列数组,每个数组元素包含XID和该事物的实时状态。当page不足时,创建新的page来存储新的事务。
5、配置文件
- postgresql.conf
数据库初始化时默认的配置文件 - postgresql.auto.conf
存储使用ALTER SYSTEM
修改的配置参数,优先级别高于postgresql.conf
文件 - pg_hba.conf
pg_hba.conf文件负责客户端的连接和认证,起到防火墙的作用。格式:TYPE DATABASE USER ADDRESS METHOD
三、表空间的布局
3.1 默认的表空间
在PG中最大的逻辑存储单位是表空间。
数据库默认的表空间由两个:pg_default
和pg_global
pg_default
表口昂间的物理文件位置在数据目录的base
目录,是template0 和 template1 数据库的默认表空间。
postgres=# select oid,spcname from pg_tablespace;
oid | spcname
------+------------
1663 | pg_default
1664 | pg_global
(2 rows)
postgres=# create table tab_default_tsp(id int,name varchar);
CREATE TABLE
-- reltablespace=0,表示使用的是默认表空间
postgres=# select oid,relname,relfilenode,reltablespace from pg_class where relname = 'tab_default_tsp';
oid | relname | relfilenode | reltablespace
-------+-----------------+-------------+---------------
16391 | tab_default_tsp | 16391 | 0
(1 row)
- 所有创建的数据库,如果没有明确指定表空间,那么使用默认的表空间
pg_default
。用于管理整个数据库集群的表默认被存储在pg_global
表空间中。
create database maleah_test owner maleah;
postgres=# SELECT db.oid,db.datname,tsp.spcname from pg_database db inner join pg_tablespace tsp on db.dattablespace=tsp.oid where db.datname = 'maleah_test';
oid | datname | spcname
-------+----------+------------
16385 | maleah_test | pg_default
(1 row)
- 用于管理整个数据库集群的表(系统表)默认被存储在
pg_global
表空间中;pg_global
表空间的物理位置为$PGDATA\global
目录。 pg_global
不能被当作默认的表空间
3.2 用户创建的表空间
执行CREATE TABLESPACE
语句会在指定的目录下创建表空间,在该目录下还会创建版本特定的子目录。版本特定的命名方式为**PG_主版本号_目录版本号
**
用户创建的表空间对应一个目录
--# 在 /soft/pg14/data6000/maleah_tsp_test 目录下创建了 maleah_tsp_test 表空间
postgres=# create tablespace maleah_tsp_test owner maleah location '/soft/pg14/data6000/maleah_tsp_test';
CREATE TABLESPACE
--# 新建的表空间对应的 oid = 16397
postgres=# select oid,spcname from pg_tablespace;
oid | spcname
-------+--------------
1663 | pg_default
1664 | pg_global
16397 | maleah_tsp_test
(3 rows)
--# 在目录下创建了版本特定的子目录-PG_14_202107181
postgres=# \! ls -l /soft/pg14/data6000/maleah_tsp_test
total 0
drwx------. 2 postgres postgres 6 Jan 20 17:20 PG_14_202107181
--# $PGDATA/pg_tblspc 目录下有与新建表空间oid相同的符号链接指向实际的表空间目录
postgres=# \! ls -l $PGDATA/pg_tblspc
total 0
lrwxrwxrwx. 1 postgres postgres 32 Jan 20 17:20 16397 -> /soft/pg14/data6000/maleah_tsp_test
--#1. 在新建的表空间 maleah_tsp_test 下创建 tab_new_tsp 表
postgres=# create table tab_new_tsp(id int,name varchar) tablespace maleah_tsp_test;
CREATE TABLE
---# tab_new_tsp 表的所属表空间为 maleah_tsp_test
postgres=# select oid,relname,relfilenode,reltablespace from pg_class where relname = 'tab_new_tsp';
oid | relname | relfilenode | reltablespace
-------+-------------+-------------+---------------
16403 | tab_new_tsp | 16403 | 16397
(1 row)
---# 使用 pg_relation_filepath 函数查找该表对应的目录
postgres=# select pg_relation_filepath('tab_new_tsp');
pg_relation_filepath
---------------------------------------------
pg_tblspc/16397/PG_14_202107181/13892/16403
(1 row)
--#2. 在新建的表空间 maleah_tsp_test 下创建 db_new_tsp 数据库
create database db_new_tsp owner maleah tablespace maleah_tsp_test;
---# 数据库对应的 oid=16408
postgres=# select oid,datname,datdba,dattablespace from pg_database where datname = 'db_new_tsp';
oid | datname | datdba | dattablespace
-------+------------+--------+---------------
16408 | db_new_tsp | 16384 | 16397
(1 row)
---# 在版本特定的子目录下创建相应的数据库目录
postgres=# \! ls -l $PGDATA/maleah_tsp_test/PG_14_202107181 | grep 16408
drwx------. 2 postgres postgres 8192 Jan 20 17:47 16408
四、表和索引相关文件的布局
4.1 数据库相关知识
- 任何的新初始化数据库都有一个统一的存放目录 base 用来存放默认的数据库:postgres、template1、template0。
postgres=# SELECT oid,datname,datdba from pg_database ;
oid | datname | datdba
-------+------------+--------
13892 | postgres | 10
1 | template1 | 10
13891 | template0 | 10
16408 | db_new_tsp | 16384
(4 rows)
postgres=# \! ls -l $PGDATA/base
total 48
drwx------. 2 postgres postgres 8192 Jan 12 18:02 1
drwx------. 2 postgres postgres 8192 Jan 12 18:02 13891
drwx------. 2 postgres postgres 8192 Jan 20 17:26 13892
- 当创建数据库时不指定
TEMPLATE
,将使用默认模板数据库template1
- 在数据库中有一个特殊的数据类型叫做
oid
(object identifier)对象标识符,每个数据库都有对应的唯一的 oid 值。整个数据库结构和数据库对象都是通过oid
来管理的。
4.2 表在数据库中的管理
创建一张表 tab_test,oid=16414
postgres=# \c maleah_test maleah
You are now connected to database "maleah_test" as user "maleah".
maleah_test=> create table tab_test (id int,name varchar);
CREATE TABLE
maleah_test=> select oid,relname,relfilenode from pg_class where relname = 'tab_test';
oid | relname | relfilenode
-------+----------+-------------
16414 | tab_test | 16414
(1 row)
maleah_test=> select pg_relation_filepath('tab_test');
pg_relation_filepath
----------------------
base/16385/16414
(1 row)
- 默认不会给创建的空表分配空间,只分配一个文件名称
maleah_test=> \! ls -l $PGDATA/base/16385/16414
-rw-------. 1 postgres postgres 0 Jan 20 18:24 /soft/pg14/data6000/base/16385/16414
- 在第一次执行
INSERT
操作后,将会分配一个页面的大小(8K)
maleah_test=> insert into tab_test values(1,'maleah');
INSERT 0 1
maleah_test=> \! ls -l $PGDATA/base/16385/16414
-rw-------. 1 postgres postgres 8192 Jan 20 18:32 /soft/pg14/data6000/base/16385/16414
- 表在内部通过
oid
管理,外部通过refilenode
管理
注:表的 oid 不一定等于 refilenode,如下
# 对表进行 truncate 操作
maleah_test=> truncate TABLE tab_test ;
TRUNCATE TABLE
# 表的 oid 不等于 filenode,表的 filenode 的值发生改变
maleah_test=> select oid,relname,relfilenode from pg_class where relname = 'tab_test';
oid | relname | relfilenode
-------+----------+-------------
16414 | tab_test | 16419
(1 row)
# 查询表的存储位置 - 以 filenode 为名的文件
maleah_test=> select pg_relation_filepath('tab_test');
pg_relation_filepath
----------------------
base/16385/16419
(1 row)
# 此时表的 oid和 filenode 的目录下的文件都为空
maleah_test=> \! ls -l $PGDATA/base/16385/16414
-rw-------. 1 postgres postgres 0 Jan 20 18:34 /soft/pg14/data6000/base/16385/16414
maleah_test=> \! ls -l $PGDATA/base/16385/16419
-rw-------. 1 postgres postgres 0 Jan 20 18:34 /soft/pg14/data6000/base/16385/16419
# 插入数据
maleah_test=> insert into tab_test values(1,'maleah');
INSERT 0 1
# 以 oid 命名的文件大小仍为0,filenode命名为文件大小为 8K
maleah_test=> \! ls -l $PGDATA/base/16385/16414
-rw-------. 1 postgres postgres 0 Jan 20 18:34 /soft/pg14/data6000/base/16385/16414
maleah_test=> \! ls -l $PGDATA/base/16385/16419
-rw-------. 1 postgres postgres 8192 Jan 20 18:35 /soft/pg14/data6000/base/16385/16419
- 表默认单个文件大小为1G。如果超过1G,将会使用
relfilenode.编号
(编号从1开始)来命名新的文件,直到新文件填满1G,以此类推。也可以在编译时使用--eith-segsize
来指定
五、堆表文件的内部布局
在PostgreSQL中,
- page - 保存在磁盘中的块
- buffer - 内存中的块
- relation - 表和索引
- tuple - 行
数据的读写是以 page
为最小单位,每个page
默认大小 8kB
5.1 堆表文件的页面布局
表的页面包含三种类型的数据:
- 堆元组(heap tuples) :堆元组就是数据记录本身。它们从页面底部开始依序堆叠
- 行指针(line pointer):每个行指针占4个字节,保存着指向堆元组的指针。它们也被称为项目指针(item pointer)。行指针简单地组织为一个数组,扮演了元组索引的角色。每个索引项从1开始依次编号,称为偏移号(offset number)。当向页面中添加新元组时,一个相应的新行指针也会被放入数组中,并指向新添加的元组。
- 首部数据(header data):页面的起始位置分配了由结构
PageHeaderData
定义的首部数据。它的大小为24个字节,包含关于页面的元数据。该结构的主要成员变量为:
成员变量 | 描述 |
pd_lsn | 该页面最近一次变更所写入XLOG记录对应的LSN。它是一个8字节无符号整数,62位,保存两个32位的值,高位为xlogid,低位记录偏移量 |
pd_checksum | 页面的校验和 |
pd_flags | 标记位(数据页面中的格式化的空间或未格式化的空间) |
pd_lower | 空闲空间的起始位置 |
pd_upper | 空间空间的结束位置 |
pd_special | 特殊空间的偏移量 |
pd_pagesize_version | 页面的大小以及页面的版本编号 |
pd_prune_xid | 页面中最老元组的xid |
/* @src/include/storage/bufpage.h */
在 bufpage.h 文件中查看标准的页面定义
5.2 使用 pg_filedump 工具分析
maleah_test=# select pg_relation_filepath('tab_test');
pg_relation_filepath
----------------------
base/16385/16451
(1 row)
maleah_test=# select * from tab_test ;
id | name
----+------------
1 | maleah
1 | PostgreSQL
(2 rows)
-- 显示格式化的块内容转储
[postgres@node1 pg_filedump]$ ./pg_filedump -d $PGDATA/base/16385/16451 | tail -n 10
1f90: 00000000 00000000 00000000 00000000 ................
1fa0: 00000000 00000000 00000000 00000000 ................
1fb0: 00000000 00000000 fd020000 00000000 ................
1fc0: 00000000 00000000 02000200 02091800 ................
1fd0: 01000000 17506f73 74677265 53514c00 .....PostgreSQL.
1fe0: fc020000 00000000 00000000 00000000 ................
1ff0: 01000200 02091800 01000000 096c6d6a .............maleah
标签:存储,PostgreSQL,postgres,数据库,oid,pg,test,maleah,结构
From: https://blog.51cto.com/u_13482808/7372211