前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Postgresql存储结构

Postgresql存储结构

作者头像
mingjie
发布2022-05-12 10:06:03
1.1K0
发布2022-05-12 10:06:03
举报
文章被收录于专栏:Postgresql源码分析

1 逻辑组织结构

如果阅读过手册一定听过postgresql cluster的概念,第一次听到这个概念可能都会有一些困惑。cluster在安装数据库时,由initdb工具生成,initdb后产生的pgdata文件夹可以理解为cluster的物理存储结构。数据库启动、停止时pg_ctl -D参数指定的文件夹即cluster文件夹,所以一个PG Server可以运行在一个PG Cluster上。

数据库对象这个概念在DBMS中的定义:

any defined object in a database that is used to store or reference data

在PG中数据库对象包括例如:堆表、索引、序列、函数等等

下图可以看到Cluster中可以创建多个数据库,每一个数据库中包含了表等其他数据库对象。在GP中schema是一个逻辑上的隔离概念,在实际存储中只是使用schema name对table name等做了区分。

在这里插入图片描述
在这里插入图片描述

注意:所有的数据库对象都会唯一的对应某一个数据库,这种隔离是逻辑上的,所以一个数据库的负载一定会影响到其他数据库。具体可以看后面的进程结构介绍。

2 物理组织结构

2.1 文件结构

现在来初始化一个cluster,使用initdb的指定,指定生成路径。

代码语言:javascript
复制
# initdb -D /home/mingjie.gmj/databases/data/pgdata8412

The files belonging to this database system will be owned by user "mingjie.gmj".
This user must also own the server process.

The database cluster will be initialized with locale "en_US.utf8".
The default database encoding has accordingly been set to "UTF8".
The default text search configuration will be set to "english".

Data page checksums are disabled.

creating directory /home/mingjie.gmj/databases/data/pgdata8412 ... ok
creating subdirectories ... ok
selecting default max_connections ... 100
selecting default shared_buffers ... 128MB
selecting dynamic shared memory implementation ... posix
creating configuration files ... ok
running bootstrap script ... ok
performing post-bootstrap initialization ... ok
syncing data to disk ... ok

WARNING: enabling "trust" authentication for local connections
You can change this by editing pg_hba.conf or using the option -A, or
--auth-local and --auth-host, the next time you run initdb.

Success. You can now start the database server using:

    pg_ctl -D /home/mingjie.gmj/databases/data/pgdata8412 -l logfile start

指定后tree观察PGDATA文件目录结构

代码语言:javascript
复制
.
|-- base
|   |-- 1
|   |   |-- 112
|   |   |-- 113
|   |   |-- 1247
|   |   |-- 1247_fsm
|   |   |-- 1247_vm
.........
|   |   |-- 828
|   |   |-- pg_filenode.map
|   |   `-- PG_VERSION
|   |-- 13157
|   |   |-- 112
|   |   |-- 113
|   |   |-- 1247
|   |   |-- 1247_fsm
|   |   |-- 1247_vm
.........
|   |   |-- 827
|   |   |-- 828
|   |   |-- pg_filenode.map
|   |   `-- PG_VERSION
|   `-- 13158
|       |-- 112
|       |-- 113
|       |-- 1247
|       |-- 1247_fsm
|       |-- 1247_vm
.........
|       |-- 827
|       |-- 828
|       |-- pg_filenode.map
|       `-- PG_VERSION
|-- global
|   |-- 1136
|   |-- 1136_fsm
|   |-- 1136_vm
.........
|   |-- 6114
|   |-- 6115
|   |-- pg_control
|   |-- pg_filenode.map
|   `-- pg_internal.init
|-- pg_commit_ts
|-- pg_dynshmem
|-- pg_hba.conf
|-- pg_ident.conf
|-- pg_logical
|   |-- mappings
|   |-- replorigin_checkpoint
|   `-- snapshots
|-- pg_multixact
|   |-- members
|   |   `-- 0000
|   `-- offsets
|       `-- 0000
|-- pg_notify
|   `-- 0000
|-- pg_replslot
|-- pg_serial
|-- pg_snapshots
|-- pg_stat
|-- pg_stat_tmp
|-- pg_subtrans
|   `-- 0000
|-- pg_tblspc
|-- pg_twophase
|-- PG_VERSION
|-- pg_wal
|   |-- 000000010000000000000001
|   `-- archive_status
|-- pg_xact
|   `-- 0000
|-- postgresql.auto.conf
`-- postgresql.conf

上述文件结构的根目录即initdb生成的PGDATA文件夹,对应一个cluster的物理存储结构(BASE文件夹内部见下一节)

描述

PG_VERSION

一个包含PostgreSQL主版本号的文件

base

包含每个数据库对应的子目录

current_logfiles

记录日志记录收集器当前写入的日志文件

global

包含集簇范围的表的子目录,比如pg_database

pg_commit_ts

包含事务提交时间戳数据的子目录

pg_dynshmem

包含被动态共享内存子系统所使用的文件的子目录

pg_logical

包含用于逻辑复制的状态数据的子目录

pg_multixact

包含多事务(multi-transaction)状态数据的子目录(用于共享的行锁)

pg_notify

包含LISTEN/NOTIFY状态数据的子目录

pg_replslot

包含复制槽数据的子目录

pg_serial

包含已提交的可序列化事务信息的子目录

pg_snapshots

包含导出的快照的子目录

pg_stat

包含用于统计子系统的永久文件的子目录

pg_stat_tmp

包含用于统计信息子系统的临时文件的子目录

pg_subtrans

包含子事务状态数据的子目录

pg_tblspc

包含指向表空间的符号链接的子目录

pg_twophase

包含用于预备事务状态文件的子目录

pg_wal

包含 WAL (预写日志)文件的子目录

pg_xact

包含事务提交状态数据的子目录

postgresql.auto.conf

一个用于存储由ALTER SYSTEM 设置的配置参数的文件

postmaster.opts

一个记录服务器最后一次启动时使用的命令行参数的文件

postmaster.pid

一个锁文件,记录着当前的 postmaster 进程ID(PID)、集簇数据目录路径、postmaster启动时间戳、端口号、Unix域套接字目录路径(Windows上为空)、第一个可用的listen_address(IP地址或者*,或者为空表示不在TCP上监听)以及共享内存段ID(服务器关闭后该文件不存在)

2.2 普通表文件组织结构

对于每个数据库,在PGDATA/base里都有一个子目录对应, 子目录的名字为该数据库在pg_database里的 OID。

代码语言:javascript
复制
postgres=# select oid, datname, dattablespace from pg_database ;
  oid  |  datname  | dattablespace
-------+-----------+---------------
 13158 | postgres  |          1663
     1 | template1 |          1663
 13157 | template0 |          1663``


~/databases/data/pgdata8412/base][12]$ ll
total 20
drwx------ 2 mingjie.gmj users  4096 Apr 22 20:17 1
drwx------ 2 mingjie.gmj users  4096 Apr 22 20:17 13157
drwx------ 2 mingjie.gmj users 12288 Apr 22 20:17 13158

下面我们来创建一张表,我们可以通过两种方式找到表文件:

代码语言:javascript
复制
postgres=# create table tbl1 (id int, info text);
CREATE TABLE

postgres=# select relname, relfilenode from pg_class where relname='tbl1';
 relname | relfilenode
---------+-------------
 tbl1    |       16384
 
postgres=# SELECT pg_relation_filepath('tbl1');
 pg_relation_filepath
----------------------
 base/13158/16384
(1 row)

查询pg_class.relfilenode得到tbl1表的relfilenode=16384,对于普通表文件以表或索引的filenode号命名。

表在postgres库中创建,我们进入base/13158目录可以看到表文件:

代码语言:javascript
复制
ls -al  $PGDATA/base/13158/16384
-rw------- 1 mingjie.gmj users 8192 Apr 22 20:47 16384

某些数据库操作会重新分配表文件,例如truncate(这也是truncate比delete快的原因)。

代码语言:javascript
复制
postgres=# select relname, relfilenode from pg_class where relname='tbl1';
 relname | relfilenode
---------+-------------
 tbl1    |       16384
(1 row)

postgres=# truncate tbl1;
TRUNCATE TABLE
postgres=# select relname, relfilenode from pg_class where relname='tbl1';
 relname | relfilenode
---------+-------------
 tbl1    |       16390
(1 row)

2.3 系统表文件组织结构

系统初始化后,没有创建任何表但是base/13158/下已经生成了很多表文件,这些文件就是当前数据库系统表,例如pg_class,注意系统表的relfilenode为0,可以使用隐藏列oid 或者 pg_relation_filepath函数找到表文件。

代码语言:javascript
复制
postgres=# select oid,relname, relfilenode from pg_class where relname='pg_class';
 oid  | relname  | relfilenode
------+----------+-------------
 1259 | pg_class |           0
 
postgres=# SELECT pg_relation_filepath('pg_class');
 pg_relation_filepath
----------------------
 base/13158/1259
(1 row)

系统表分两类,一类是每个数据库内独有的系统表,一类是所有数据库共享的系统表。为什么有共享表的?例如pg_database记录cluster所有数据库的信息,不需要每个数据库单独存储一份。共享系统表存储在$PGDATA/global/目录下。

代码语言:javascript
复制
postgres=# select oid,relname, relfilenode from pg_class where relname='pg_database';
 oid  |   relname   | relfilenode
------+-------------+-------------
 1262 | pg_database |           0

postgres=# SELECT pg_relation_filepath('pg_database');
 pg_relation_filepath
----------------------
 global/1262

在数据目录中可以看到*_fsm*_vm两种文件,两种文件和表文件是对应的,后面章节会有介绍。

代码语言:javascript
复制
ll | grep 1247
-rw------- 1 mingjie.gmj users  73728 Apr 22 20:47 1247
-rw------- 1 mingjie.gmj users  24576 Apr 22 20:17 1247_fsm
-rw------- 1 mingjie.gmj users   8192 Apr 22 20:47 1247_vm

# fsm文件:空闲空间映射表
# vm文件:可见性映射表

2.4 表空间

表空间可以理解为一个存放表文件的目录。表空间提供了表存储的灵活控制方式: 例如在当前磁盘快满时,可以在任意新挂载的文件系统上创建表空间,把表存储在新的目录中;一个频繁使用的表可以放在IO性能更好的磁盘上,比如SSD。很少使用的数据表可以存储在一个便宜、较慢的磁盘系统上。

使用表空间有两种方式:

  1. 创建表时指定表空间
  2. 创建数据库时指定表空间
在这里插入图片描述
在这里插入图片描述

创建表空间

代码语言:javascript
复制
CREATE TABLESPACE tablespace_name
    [ OWNER { new_owner | CURRENT_USER | SESSION_USER } ]
    LOCATION 'directory'
    [ WITH ( tablespace_option = value [, ... ] ) ]

创建表空间后,可以在$PGDATA/pg_tblspc找到表空间的符号链接,注意备份恢复后需要保证表空间的路径一致。

代码语言:javascript
复制
postgres=# create tablespace tmpspace location '/tmp/tmptsp';
CREATE TABLESPACE

postgres=# CREATE TABLE foo(i int) TABLESPACE tmpspace;
CREATE TABLE

postgres=# select oid,datname from pg_database where datname='postgres';
  oid  | datname
-------+----------
 13158 | postgres
 
postgres=# select oid,relfilenode from pg_class where relname='foo';
  oid  | relfilenode
-------+-------------
 46807 |       46807
(1 row)

postgres=# select pg_relation_filepath('foo');
            pg_relation_filepath
---------------------------------------------
 pg_tblspc/46803/PG_10_201707211/13158/46807
(1 row)

pg_relation_filepath返回的地址pg_tblspc/46803/PG_10_201707211/13158/46807,PG使用pg_tblspc目录下的链接来找到对应的表空间,表文件的真正目录是:

代码语言:javascript
复制
ll /tmp/tmptsp/PG_10_201707211/13158/46807
-rw------- 1 mingjie.gmj users 0 Apr 23 10:55 /tmp/tmptsp/PG_10_201707211/13158/46807

其中PG_10_201707211是按照版本号定义的:

代码语言:javascript
复制
PG _ 'Major version' _ 'Catalogue version number'

注意:PostgreSQL使用符号连接来简化表空间的实现,意味着表空间只能在支持符号连接的系统上使用。

3 表文件组织结构

表文件内部默认由8KB的block组成,8K是数据库读写的基本单位。《PostgreSQL数据库内核分析》中是这样描述的:

在这里插入图片描述
在这里插入图片描述

每一个页面包括五个部分。

描述

PageHeaderData

24字节长。包含关于页面的一般信息,包括空闲空间指针。

ItemIdData

一个记录(偏移量,长度)对的数组,指向实际项。每个项 4 字节。

Free space

未分配的空间(空闲空间)。新项指针从这个区域的开头开始分配,新项从其结尾开始分配。

Items

实际的项本身。

Special space

索引访问模式相关的数据。不同的索引访问方式存放不同的数据。在普通表中为空。

页面的前24个字节组成PageHeaderData,在页头后面是4字节的ItemIdData即途中的LinpX,可以理解为指针指向实际的tuple存储位置(图中的TupleX),注意linp和tuple的存储顺序是相反的,linp从上到下生长,tuple从页面下到上生长。

PageHeaderData中定义了当前页面的信息:

代码语言:javascript
复制
typedef struct PageHeaderData
{
	/* XXX LSN is member of *any* block, not only page-organized ones */
	PageXLogRecPtr pd_lsn;		/* LSN: next byte after last byte of xlog
								 * record for last change to this page */
	uint16		pd_checksum;	/* checksum */
	uint16		pd_flags;		/* flag bits, see below */
	LocationIndex pd_lower;		/* offset to start of free space */
	LocationIndex pd_upper;		/* offset to end of free space */
	LocationIndex pd_special;	/* offset to start of special space */
	uint16		pd_pagesize_version;
	TransactionId pd_prune_xid; /* oldest prunable XID, or zero if none */
	ItemIdData	pd_linp[FLEXIBLE_ARRAY_MEMBER]; /* line pointer array */
} PageHeaderData;

x

类型

长度

描述

pd_lsn

PageXLogRecPtr

8 bytes

LSN: 最后修改这个页面的 WAL 记录最后一个字节后面的第一个字节

pd_checksum

uint16

2 bytes

页面校验码

pd_flags

uint16

2 bytes

标志位

pd_lower

LocationIndex

2 bytes

到空闲空间开头的偏移量

pd_upper

LocationIndex

2 bytes

到空闲空间结尾的偏移量

pd_special

LocationIndex

2 bytes

到特殊空间开头的偏移量

pd_pagesize_version

uint16

2 bytes

页面大小和布局版本号信息

pd_prune_xid

TransactionId

4 bytes

页面上最老未删除XMAX,如果没有则为0

页头后面是ItemIdData,从代码上可以看到这是一个4字节按位拆开使用的结构体。它记录了偏移量、属性位和tuple的长度。

代码语言:javascript
复制
typedef struct ItemIdData
{
	unsigned	lp_off:15,		/* offset to tuple (from start of page) */
				lp_flags:2,		/* state of item pointer, see below */
				lp_len:15;		/* byte length of tuple */
} ItemIdData;

Tuple的一般由几部分组成:

The overall structure of a heap tuple looks like: fixed fields (HeapTupleHeaderData struct) nulls bitmap (if HEAP_HASNULL is set in t_infomask) alignment padding (as needed to make user data MAXALIGN'd) object ID (if HEAP_HASOID is set in t_infomask) user data fields

Tuple存储在从未分配空间末尾开始从后向前的空间里。实际结构取决于表的内容。所有表行都用同样方法构造,都会有一个定长的头部HeapTupleHeaderData:

代码语言:javascript
复制
struct HeapTupleHeaderData
{
	union
	{
		HeapTupleFields t_heap;
		DatumTupleFields t_datum;
	}			t_choice;

	ItemPointerData t_ctid;		/* current TID of this or newer tuple (or a
								 * speculative insertion token) */

	/* Fields below here must match MinimalTupleData! */

	uint16		t_infomask2;	/* number of attributes + various flags */

	uint16		t_infomask;		/* various flag bits, see below */

	uint8		t_hoff;			/* sizeof header incl. bitmap, padding */

	/* ^ - 23 bytes - ^ */

	bits8		t_bits[FLEXIBLE_ARRAY_MEMBER];	/* bitmap of NULLs */

	/* MORE DATA FOLLOWS AT END OF STRUCT */
};

typedef struct HeapTupleFields
{
	TransactionId t_xmin;		/* inserting xact ID */
	TransactionId t_xmax;		/* deleting or locking xact ID */

	union
	{
		CommandId	t_cid;		/* inserting or deleting command ID, or both */
		TransactionId t_xvac;	/* old-style VACUUM FULL xact ID */
	}			t_field3;
} HeapTupleFields;

HeapTupleHeaderData存储的信息是实现MVCC机制的基础。

类型

长度

描述

t_xmin

TransactionId

4 bytes

插入XID标志

t_xmax

TransactionId

4 bytes

删除XID标志

t_cid

CommandId

4 bytes

插入和/或删除CID标志(覆盖t_xvac)

t_xvac

TransactionId

4 bytes

VACUUM操作移动一个行版本的XID

t_ctid

ItemPointerData

6 bytes

当前版本的TID或者指向更新的行版本

t_infomask2

uint16

2 bytes

一些属性,加上多个标志位

t_infomask

uint16

2 bytes

多个标志位

t_hoff

uint8

1 byte

到用户数据的偏移量

当通过表扫描或者索引拿到了tuple后,看起来只是拿到了一些乱码,必须使用表结构信息对数据进行切分才会有意义,表结构信息保存在pg_attribute系统表中。标识域位置的关键值是attlen和attalign。

attlen 对于一个固定尺寸的类型,typlen是该类型内部表示的字节数。对于一个变长类型,typlen为负值。-1表示一个“varlena”类型(具有长度字),-2表示一个以空值结尾的C字符串。 attalign typalign是当存储此类型值时要求的对齐性质 https://www.postgresql.org/docs/10/catalog-pg-type.html

4 表数据读取

顺序扫描 seqscan页面会顺序加载进入缓冲区,在缓冲区内扫描进程按照linp指针顺序扫描tuple。 PG顺序扫描的优化叫做同步扫描,即多进程并发扫描时,对同一张表后面的进程优先从其他进程正在扫描的位置开始扫描,避免缓冲区已经置换出去,增加大量IO(具体见《PostgreSQL数据库内核分析3.4.1》)

在这里插入图片描述
在这里插入图片描述

索引扫描 btree索引块会优先加载进入内存,在索引中定位到具体的数据页面和偏移量,然后加载指定的页面进入内存,按偏移量读取数据。可以看到IO操作大大减少了,也不需要进行数页面扫描(代价是索引块、页面的扫描)。

在这里插入图片描述
在这里插入图片描述
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-04-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 逻辑组织结构
  • 2 物理组织结构
    • 2.1 文件结构
      • 2.2 普通表文件组织结构
        • 2.3 系统表文件组织结构
          • 2.4 表空间
          • 3 表文件组织结构
          • 4 表数据读取
          相关产品与服务
          对象存储
          对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档