PostgreSQL体系结构(下)

本文主要介绍Postgresql体系结构的内存区域和磁盘存储部分,欲知配置文件和服务进程部分,详情请看点击PostgreSQL体系结构(上)

1.3 内存区域
PostgreSQL内存区域主要分为共享内存区域与本地内存区域两部分。

在这里插入图片描述

共享内存为所有的 background process提供内存,由PostgreSQL服务器的所有进程使用;本地内存为每个backend process提供内存,由每个后端进程分配提供自己使用。

1.3.1 共享内存区域
在数据库系统中,主要关注磁盘IO, 大部分oltp工作负载都是随机IO,磁盘获取非常慢。PostgreSQL在查询前,会先查找共享内存的页,如果命中,就直接返回,避免从磁盘中查询,提升数据库的读写性能。

shared buffer : 数据页缓冲区(共享内存缓冲区),默认128MB。

将表或者索引的page从磁盘加载到shared buffer ,然后在shared buffer 操作。一个合理的shared_buffers 开始值是系统内存的 25%。依赖操作系统的高速缓冲区,如果设置更大,对大量写或改变的数据的处理,对max_wal_size也要做相应增加。

wal buffer : 预写日志缓冲区,默认值 -1。

用于还未写入磁盘的 WAL 数据的共享内存量,大约等于shared_buffers 的 1/32(3%左右),最小32kB

示例演示:

 wal_buffers = 64kB

      postgres=# \timing
      Timing is on.
      postgres=# insert into t select id as id,id as id2 from generate_series(1,10000000) as id;
      INSERT 0 10000000
      Time: 15700.448 ms (00:15.700)

wal_buffers = 1MB

      postgres=# insert into t select id as id,id as id2 from generate_series(1,10000000) as id;
      INSERT 0 10000000
      Time: 14574.135 ms (00:14.574)

wal_buffers = 10MB

      postgres=# insert into t select id as id,id as id2 from generate_series(1,10000000) as id;
      INSERT 0 10000000
      Time: 8982.655 ms (00:08.983)

 wal_buffers = 50MB

      postgres=# insert into t select id as id,id as id2 from generate_series(1,10000000) as id;
      INSERT 0 10000000
      Time: 8210.622 ms (00:08.211)

wal_buffers = 100MB

      postgres=# insert into t select id as id,id as id2 from generate_series(1,10000000) as id;
      INSERT 0 10000000
      Time: 8563.360 ms (00:08.563)

结论:实验证明,在10M~50M的时候性能达到最好,分配再多意义不大。

commit log : 为了并发控制所有事物的状态被保持而分配的区域。也就是说,在提交日志中,为并发控制机制保存所有事务的状态。PostgreSQL定义了四种事务状态 —— IN_PROGRESS, COMMITTED, ABORTED和SUB_COMMITTED,其中SUB_COMMITTED状态用于子事务,此处不讨论。

#define TRANSACTION_STATUS_IN_PROGRESS		0x00
#define TRANSACTION_STATUS_COMMITTED		0x01
#define TRANSACTION_STATUS_ABORTED		       0x02
#define TRANSACTION_STATUS_SUB_COMMITTED	       0x03

四种事务状态仅需两个bit即可记录。以一个块8KB为例,可以存储8KB*8/2 = 32K个事务的状态。内存中缓存CLOG的buffer 大小为Min(128,Max(4,NBuffers/512))。

CLOG在逻辑上是一个数组,由共享内存中一系列8K页面组成。数组下标对应事务txid,数组内容则为事务状态:
在这里插入图片描述

T1时刻:txid=200事务提交,对应状态从IN_PROGRESS改为COMMITED
T2时刻:txid=201事务回滚,对应状态从IN_PROGRESS改为ABORTED

当需要获取事务状态时,pg调用内部函数读取CLOG返回所请求事务状态。

当shutdown pg或Checkpoint运行时,CLOG数据会由内存写入pg_clog(PostgreSQL 10后叫pg_xact)目录中的文件。这些文件被命名为0000,0001,最大256KB。当PostgreSQL启动时,会加载这些文件用于初始化CLOG。

CLOG数据会不断增长,但并非所有数据都是必要的,清理过程也会定期清理掉不再需要的CLOG页面和文件。

1.3.2 本地内存区域
work_mem :被分配给每个用户用来设置在写入临时磁盘文件之前查询操作(sort和hash)可使用的最大内存容量。默认值4M,最小64kB。

对于一个复杂查询, 可能会并行运行好几个排序或者哈希操作,每个操作都会被允许使用这个参数指定的内存量,然后才会开始写数据到临时文件。

同样,几个正在运行的会话可能并发进行这样的操作,因此被使用的总内存可能是work_mem 值的好几倍。在选择这个值时一定要记住这一点。ORDER BY、DISTINCT和归并连接都要用到排序操作。哈希连接、基于哈希的聚集以及基于哈希的IN子查询处理中都要用到哈希表。

maintenance_work_mem :在维护性操作(例如VACUUM、CREATE INDEX和ALTER TABLE ADD FOREIGN KEY)中使用的最大的内存量,默认64MB。

被用于记录垃圾tupleid,vacuum在进行表扫描时,记录ID占满了整个内存,会停止扫描表,开始INDEX的扫描,扫描INDEX时,实际上是从刚才内存中记录的这些tupleid来进行匹配。

temp_buffers :为每个数据库会话设置临时缓冲区的最大内存,仅用于访问临时表的会话本地缓冲。默认为8MB。
一个会话将按照temp_buffers 给出的限制根据需要分配临时缓冲区。如果在一个并不需要大量临时缓冲区的会话里设置一个大的数值, 其开销只是一个缓冲区描述符,或者说temp_buffers 每增加一则增加大概 64 字节。不过,如果一个缓冲区被实际使用,那么它就会额外消耗 8192 字节(或者BLCKSZ字节)。

1.4 磁盘存储
PostgreSQL磁盘存储主要分为程序目录和数据目录两部分。

1.4.1 程序目录
PostgreSQL程序目录即安装目录。存放数据库的可执行程序,比如客户端,启停,备份等命令,以及*.so的动态库文件等。

在这里插入图片描述

bin             二进制可执行文件
include         头文件目录
lib             动态库文件
share           文档以及配置模版文件

1.4.2 数据目录(物理结构)
PostgreSQL数据目录默认在/var/lib/pgsql/data下,支持使用环境变量$PGDATA管理。下图所示是数据目录的一级结构。

在这里插入图片描述

其中:

base/:            存储 database 数据(除了指定其他表空间的)
global/:          存储 cluster-wide 表格数据
pg_clog/:         存储事务提交的状态数据
pg_commit_ts/:    存储事务提交的时间戳数据
pg_dynshmem/:     存储动态共享内存子系统的文件
pg_logical/:      存储 logical decoding 状态数据
pg_multixact/:    存储多重事务状态数据的子目录(用于共享的行锁)
pg_notify/:       存储 LISTEN/NOTIFY 状态数据
pg_replslot/:     存储 replication slot 数据
pg_serial/:       存储 committed serializable transactions 信息
pg_snapshots/:    存储导出的 snapshots
pg_stat/:         存储统计子系统的永久文件
pg_stat_tmp/:     存储统计子系统的临时文件
pg_subtrans/:     存储子事务状态数据
pg_tblspc/:       存储指向表空间的符号链接
pg_twophase/:     存储 prepared transactions 的状态文件
PG_VERSION:       存储 postgresql 数据库的主版本号
pg_xlog/:         存储 WAL (Write Ahead Log) 文件
postgresql.conf:         postgresql 主配置文件
pg_hba.conf:             postgresql 认证配置文件
pg_ident.conf:           postgresql 用户映射配置文件
postgresql.auto.conf:    存储由 ALTER SYSTEM 设置的配置
postmaster.opts:         存储上一次启动该数据库时用到的命令
postmaster.pid:          锁文件,只有在 postgresql 服务运行时存在,存储当前 postmaster 的 PID,PGDATA,postmaster 启动时间,端口号,Unix-domain socket 目录,第一个有效的 listen_address,共享内存的 segment ID
  1. base目录

base目录存储用户创建的数据库文件,及隶属于用户数据库的所有关系,比如表、索引等。

base目录结构分为两级,第一级结构如下图所示,一级目录名是用户数据库对象的OID,1代表的是postgres数据库,一级目录内的二级子文件都是隶属于该数据库对象的关系,包括表、索引、视图等。

在这里插入图片描述

二级子文件如下图所示,存储着某个数据库内的所有关系,包括表、索引、视图等,这里以postgres数据库目录示例。二级子文件分为三大类,第一类是以关系OID命名的主数据文件,第二类是文件名以_fsm结尾的空闲空间映射文件,第三类是文件名以_vm结尾的可见性映射文件。

在这里插入图片描述

主数据文件存储隶属于对应数据库下的数据库关系文件,包括数据、索引等,客户最重要的业务数据便是存储在主数据文件中。

当关系文件大小低于RELSEG_SIZE × BLCKSZ时,数据库引擎创建名称为pg_class.relfilenode的单文件,反之会切分为名称如pg_class.relfilenode.segno的多个文件。单个关系文件内部被划分为默认8K固定大小的多个page并存储在磁盘上,8K可以在initdb时通过BLCKSZ参数修改配置。主数据文件写入时,会先将元组数据从行指针数组的底部开始堆叠,直到空间耗尽。

用户通过SQL查询到的单行数据记录对应单个元组(tuple),因为MVCC机制的原因,元组可能是无法查询到旧版本数据,也可能是活跃的新版本数据,旧版本数据会在未来的某个时刻被清理。当查询没有命中索引触发顺序扫描时,数据库引擎顺序扫描page的行指针读取到元组,反之如果命中B树索引,引擎会通过索引文件的元组,通过索引键的TID值读取到元组。

  下图是主数据文件的层级结构。

在这里插入图片描述

  下表格是上图所示page内部结构的元数据信息。

在这里插入图片描述

  下表格是上图所示tuple内部结构的元数据信息。

在这里插入图片描述

FSM是空闲空间映射文件,记录着heap和index的每个page的空闲空间信息,有利于快速定位到有充足空闲空间的page以便存储tuple,如果没有定位到则需要扩展新page。除了Hash Index文件没有FSM文件,其他heap和index都需要FSM文件。

总体上,FSM采用3-4级多叉树的结构组织FSM page,单个FSM page内部采用完全二叉树的结构进行管理,高级别FSM page的叶子节点关联低级别的FSM page,低级别FSM Page的叶子节点存储着heap、index page的可用空间数目,而非叶子结点依次存储叶子节点的最大可用空间数目,每个节点占用1个字节。
在这里插入图片描述

VM是可见性映射文件,记录着每个heap page的可见性信息,因此index page并没有vm文件。一方面它可以提高vacumn的执行效率,另一方面通过vm文件可以感知到page内的元组是否全部可见,如果全部可见的话,查询引擎查询索引元组直接获取到数据即可,不必再访问数据元组检查可见性,减少了回表次数,极大提升了查询的效率。

VM采用位图的结构存储可见性信息,每个heap page只在vm文件中存储2位,第一位代表元组是否全部可见,第二位代表元组是否全部被冻结。

#define VISIBILITYMAP_ALL_VISIBLE  0x01
#define VISIBILITYMAP_ALL_FROZEN   0x02
  1. global目录

global目录存储pg_control及数据库集群维度的数据库及其关系,非客户维度的数据,例如pg_database、pg_class等。目录内的文件结构和base是一致的。global目录文件结构如下图所示。

在这里插入图片描述

pg_internal.init用于缓存系统表,加快系统表读取速度(每个用户创建的数据库目录下也有同名文件)。

pg_filenode.map用于将当前目录下系统表的OID与具体文件名进行硬编码映射(每个用户创建的数据库目录下也有同名文件)。

pg_control用于记录数据库集群控制全局控制信息,包括initdb初始化、WAL和checkpoint的信息。

全局系统表文件:数字命名的文件,用于存储系统表的内容。它们在pg_class里的relfilenode都为0,是靠pg_filenode.map将OID与文件硬编码映射。(注:不是所有的系统表的relfilenode都为0)

  1. pg_wal目录

pg_wal是WAL机制中的wal日志存储目录。PG10及之后的高版本改目录名为pg_wal,10之前目录名称是pg_xlog。

WAL(Write-Ahead-Logging)机制:日志先行机制。数据变更优先写入日志文件,事务失败则变更记录被忽略,事务成功再选择合适时机写入数据文件,数据的刷盘速度慢于日志刷盘速度。当数据库系统崩溃后,引擎会从上一次成功的checkpoint点开始依次重放wal记录,如果LSN>pd_lsn则重放wal记录,反之跳过,确保数据记录恢复到崩溃前的状态。

在这里插入图片描述

wal segment 文件存储着数据库行记录明细,每一条记录明细都是服务于数据库恢复操作的,确保前后数据一致。首先针对数据的任意一次修改操作均被记录在wal段文件中,包括insert、update和delete,其次系统的一些管理行为也会被记录在wal段文件中,例如事务提交和vacuum等行为。

wal segment 文件命名形如00000046 00000000 000000F3,文件名共24位,前8位是timeline,中间8位是logid,后8位是logseg,logseg的前6位始终是0,后2位是lsn的前2位。根据wal段文件名的最后2位,wal记录根据对应的LSN分别记录在不同的wal段文件中。
在这里插入图片描述

.history文件内容包括原.history文件,当前时间线切换记录和切换原因,作用于数据库的时间点恢复行为。当数据库引擎从多个时间线的备份中恢复时,数据库从.history文件中找到从pg_control的start_timeline到指定的recovery_target_timeline间的所有wal段文件进行恢复。

archive_status是wal segment 文件的备份目录,包括.ready和.done文件。超出wal_keep_segments数目限制的wal日志会在archive_status目录内被打标,归档操作完成后被进一步移除。

.ready是同名wal segment 文件在archive_status目录内的标记文件,代表该wal segment 文件可被归档。wal segment 文件在数据目录中的存储文件数量是有上限的,一般通过wal_keep_segments参数来约束,因此数据库引擎在wal segment 文件个数达到上限后会在archive_status目录内增加可移除的wal segment 文件的标记文件,文件名是原wal segment 文件名后增加.ready后缀,等待归档工具进行归档。

.done是同名wal segment 文件在archive_status目录内的标记文件,代表该wal segment 文件已被归档,可以被清理。数据库引擎默认通过archive_command命令对.ready文件进行归档,归档成功与否取决于archive_command命令返回true还是false,当archive_command返回true时,代表与.ready文件同名的wal段文件已被归档,引擎再将该文件的扩展名重命名为.done,等待数据库引擎在下一次的checkpoint时进一步清理原wal segment 文件。

  1. pg_xact目录

pg_xact是事务提交日志(Commit Log)的存储目录,事务提交日志默认256KB,文件名形如NNNN,系统初始化后从0000开始递增至FFFF。PG 10及之后的高版本改目录名为pg_xact,10之前目录名称是pg_clog。

在这里插入图片描述

Commit Log : 事务提交日志存储数据库的单个事务运行状态。Commit Log由共享内存中一组8KB的page组成,每个page包含一列数组,每个数组元素包含XID和该事物的实时状态。当page不足时,创建新的page来存储新的事务。

1.4.3 数据目录(逻辑结构)
想要理解PostgreSQL的数据库逻辑结构,需要先了解一些重要的概念。

数据库相关概念:

  1. 一套PostgreSQL程序称之为一个数据库群集;

  2. 当initdb初始化命令执行后,template0 , template1 , 和postgres数据库被创建;

  3. template0和template1数据库是创建用户数据库时使用的模版数据库,他们包含系统元数据表;

  4. initdb初始化刚完成后,template0和template1数据库中的表是一样的。但是template1数据库可以根据用户需要创建对象;

  5. 用户创建的数据库是通过克隆template1数据库来创建的。

表空间相关概念:

  1. initdb初始化后马上创建pg_default和pg_global表空间;

  2. 建表时如果没有指定特定的表空间,表默认被存在pg_default表空间中;

  3. 用于管理整个数据库集群的表默认被存储在pg_global表空间中;

  4. pg_default表空间的物理位置为$PGDATA/base目录;

  5. pg_global表空间的物理位置为$PGDATA/global目录;

  6. 一个表空间可以被多个数据库同时使用。此时,每一个数据库都会在表空间路径下创建为一个新的子路径;

  7. 创建一个用户表空间会在$PGDATA/pg_tblspc目录下面创建一个软连接,连接到表空间制定的目录位置。

表相关概念:

  1. 每个表有三个数据文件;

  2. 一个文件用于存储数据,文件名是表的OID;

  3. 一个文件用于管理表的空闲空间,文件名是OID_fsm;

  4. 一个文件用于管理表的块是否可见,文件名是OID_vm;

  5. 索引没有_vm文件,只有OID和OID_fsm两个文件。

表和索引创建时文件名是OID,此时的OID和pg_class.relfilenode的值是一样的。不管怎样,当我们执行重写操作时(truncate,cluster,vacuum full,reindex等),被修改对象的relfilenode值也会被修改,文件名也会随着reffilenode值一起改变。我们可以通过pg_relation_filepath(‘<object_name>’)视图很容易的检查文件位置和名称。

示例演示:

postgres=# create table ansel_try(id int ,name character varying(32) ,PRIMARY KEY(id));
CREATE TABLE
postgres=# 
postgres=# \d ansel_try
                    Table "public.ansel_try"
 Column |         Type          | Collation | Nullable | Default 
--------+-----------------------+-----------+----------+---------
 id     | integer               |           | not null | 
 name   | character varying(32) |           |          | 
Indexes:
    "ansel_try_pkey" PRIMARY KEY, btree (id)

postgres=# select pg_relation_filepath('ansel_try');
 pg_relation_filepath 
----------------------
 base/13580/108943
(1 row)

postgres=# select pg_relation_filepath('ansel_try_pkey');
 pg_relation_filepath 
----------------------
 base/13580/108946
(1 row)


postgres=# select oid,relfilenode from pg_class where  relname='ansel_try' ;
  oid   | relfilenode 
--------+-------------
 108943 |      108943
(1 row)


postgres=# select oid,relfilenode from pg_class where  relname='ansel_try_pkey' ;
  oid   | relfilenode 
--------+-------------
 108946 |      108946 
(1 row)

postgres=# truncate table ansel_try ;
TRUNCATE TABLE

postgres=# select oid,relfilenode from pg_class where  relname='ansel_try' ;
  oid   | relfilenode 
--------+-------------
 108943 |      108948
(1 row)

postgres=# select oid,relfilenode from pg_class where  relname='ansel_try_pkey' ;
  oid   | relfilenode 
--------+-------------
 108946 |      108949
(1 row)

在这里插入图片描述

PostgreSQL数据库集簇逻辑结构
Tablespace 是数据库最大的存储空间,Tablespace 分配方式由Extent(区)决定。数据库在逻辑上分成多个存储单元,表空间用作把逻辑上相关的结构放在一起,一个或多个表空间组成数据库逻辑关系。

Database :表空间的存储单元(关联表的集合),按照数据结构来组织、存储和管理数据的仓库,同一个实例下,不同Database 是不能相互访问的,即独立的。每个Database 都有一个或多个不同的 API 用于创建,访问,管理,搜索和复制所保存的数据。

Schema:类似于表的集合,数据库创建后,默认会有一个Public名称的Schema模式。同一个数据库,可以有多个Schema模式,不同模式下的表是可以相互访问,即可共享的;不同模式下,表名可以是一样,也就是表在模式下是独立,它可隔离多个用户之间相同名称的对象。

User用户:postgres用户是默认创建的超级管理员;每个数据库都有一个OWNER用户,每个用户可以OWNER多个数据库。

PostgreSQL 没有undo回滚段段,只存在数据段与索引段两种,采用了自动段空间的管理方式:每个段有一个段头页,也是段的第一个页;每个段中每个区的第一页称为一级位图页;每个段中第一区的第二个页称为二级位图页,其管理着512个一级位图页位置与使用情况,每个段存在1016个二级位图页。

Extent(区):把数据文件中 8 个连续的 Page 构成的空间称为一个 Extent。Extent 是数据库进行数据文件空间分配/释放的基本单位。每个表、索引、序列对象都是由若干个区组成。数据文件被创建后,除自动保留部分区作为控制区外,其他区全部处于未分配状态。表、索引、序列对象的所有数据都存放在 Extent 中,当向这些 Extent 中插入数据时,若该 Extent 的所有页面都已占满,系统就会自动在所属表空间的数据文件中寻找一个尚未分配的区,并将其状态修改为数据区。

在 PostgreSQL 中,将保存在磁盘中的块称为 Page,而将内存中的块称为 Buffer,表和索引称为 Relation,行称为 Tuple。数据的读写是以 Page 为最小单位,每个 Page 默认大小为 8k,在编译 PostgreSQL 时指定的 BLCKSZ 大小决定 Page 的大小。每个表文件由多个 BLCKSZ 字节大小的 Page 组成,每个 Page 包含若干 Tuple。

声明
因小编个人水平有限,专栏中难免存在错漏之处,请勿直接复制文档中的参数、命令或方法应用于线上环境中操作。

在这里插入图片描述

更多精彩内容,欢迎关注微信公众号在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值