文章目录

  • 前言
  • 1、逻辑和物理存储结构
    • 1.1 逻辑存储结构
    • 1.2 物理存储结构
      • 1.2.1 数据目录结构
      • 1.2.2 数据文件布局
      • 1.2.3 完整文件布局图
  • 2、进程结构
    • 2.1 守护进程与服务进程
    • 2.2 辅助进程
  • 3、内存结构
    • 3.1 本地内存
    • 3.2 共享内存
  • 4、总结

前言

PG官方指导手册

PostgreSQL数据库是由一系列位于文件系统上的物理文件组成,在数据库运行过程中,通过整套高效严谨的逻辑管理这些物理文件。通常将这些物理文件称为数据库,将这些物理文件、管理这些物理文件的进程、进程管理的内存称为这个数据库的实例。
在PostgreSQL的内部功能实现上,可以分为系统控制器、查询分析器、事务系统、恢复系统、文件系统这几部分。其中系统控制器负责接收外部连接请求,查询分析器对连接请求查询进行分析并生成优化后的查询解析树,从文件系统获取结果集或通过事务系统对数据做处理,并由文件系统持久化数据。本篇将简单介绍PostgreSQL的物理和逻辑结构,同时介绍PostgreSQL实例在运行周期的进程结构。

1、逻辑和物理存储结构

在PostgreSQL中有一个数据库集群(Database Cluster)概念,它是指由单个 PostgreSQL服务器实例管理的数据库集合,组成数据库集群的这些数据库使用相同的全局配置文件和监听端口、共用进程和内存结构,并不是指“一组数据库服务器构成的集群”,在 PostgreSQL中说的某一个数据库实例通常是指某个数据库集群。

1.1 逻辑存储结构

数据库集群是数据库对象的集合,在关系数据库理论中, 数据库对象是用于存储或引用数据的数据结构,表就是一个典型的例子,还有索引、序列、视图、函数等这些对象。
在 PostgreSQL中,数据库本身也是数据库对象,并且在逻辑上彼此分离,除数据库之外的其他数据库对象(例如表、索引等) 都属于它们各自的数据库,虽然它们隶属同一个数据库集群, 但无法直接从集群中的一个数据库访问该集群中的另一个数据库中的对象。
数据库本身也是数据库对象,一个数据库集群可以包含多个Database、多个User,每个Database以及Database中的所有对象都有它们的所有者:User。

下图显示了数据库集群的逻辑结构。


创建一个Database时会为这个Database创建一个名为public 的默认Schema,每个Database可以有多个Schema,在这个数据库中创建其他数据库对象时如果没有指定Schema,都会在 public这个Schema中。
Schema可以理解为一个数据库中的命名空间,在数据库中创建的所有对象都在Schema中创建,一个用户可以从同一个客户端连接中访问不同的Schema。不同的Schema中可以有多个相同名称的Table、Index、View、 Sequence、Function等数据库对象。

1.2 物理存储结构

数据库的文件默认保存在initdb时创建的数据目录中。在数据目录中有很多类型、功能不同的目录和文件,除了数据文件之外,还有参数文件、控制文件、数据库运行日志及预写日志等。

1.2.1 数据目录结构

数据目录用来存放PostgreSQL持久化的数据,通常可以将数据目录路径配置为PGDATA环境变量,查看数据目录有哪些子目录和文件的命令如下所示:

[postgres@pghost1 ~]$ tree -L 1 -d /pgdata/10/data
/pgdata/10/data
├── base
├── pg_tblspc
├── ...
├── ...
├── ...
├── pg_wal
└── global

表对数据目录中子目录和文件的用途进行了说明。

1.2.2 数据文件布局

数据目录中的base子目录是我们的数据文件默认保存的位置,是数据库初始化后的默认表空间。在讨论base目录之前,我们先了解两个基础的数据库对象:OID和表空间

1.OID

PostgreSQL中的所有数据库对象都由各自的对象标识符(OID)进行内部管理,它们是无符号的4字节整数。数据库对象和各个OID之间的关系存储在适当的系统目录中,具体取决于对象的类型。数据库的OID存储在pg_database系统表中, 可以通过如下代码查询数据库的OID:

SELECT oid,datname FROM pg_database WHERE datname = 'mydb'; oid | datname
------+---------
16384 | mydb
(1 row)

数据库中的表、索引、序列等对象的OID存储在pg_class 系统表中,可以通过如下代码查询获得这些对象的OID:

mydb=# SELECT oid,relname,relkind FROM pg_class WHERE relname ~ 'tbl'; oid   |            relname               | relkind
----------+-----------------------------------+--------- 16385 | tbl_id_seq                         | S 16387 | tbl                             | r 16396 | tbl_pkey                        | i 3455 | pg_class_tblspc_relfilenode_index | i (4 rows)

2.表空间

在PostgreSQL中最大的逻辑存储单位是表空间,数据库中创建的对象都保存在表空间中,例如表、索引和整个数据库都可以被分配到特定的表空间。在创建数据库对象时,可以指定数据库对象的表空间,如果不指定则使用默认表空间,也就是数据库对象的文件的位置。初始化数据库目录时会自动创建 pg_default和pg_global两个表空间。如下所示:

mydb=# \db
List of tablespaces Name |  Owner | Location
---------------+----------+---------- pg_default | postgres | pg_global  | postgres |
(2 rows)
  • pg_global表空间的物理文件位置在数据目录的global目录中,它用来保存系统表。
  • pg_default表空间的物理文件位置在数据目录中的base目录,是template0和template1数据库的默认表空间,我们知道创建数据库时,默认从template1数据库进行克隆,因此除非特别指定了新建数据库的表空间,默认使用template1的表空间,也就是pg_default。

除了两个默认表空间,用户还可以创建自定义表空间。使用自定义表空间有两个典型的场景:

  • 通过创建表空间解决已有表空间磁盘不足并无法逻辑扩展的问题;
  • 将索引、WAL、数据文件分配在性能不同的磁盘上,使硬件利用率和性能最大化。由于现在固态存储已经很普遍,这种文件布局方式反倒会增加维护成本。

要创建一个表空间,先用操作系统的postgres用户创建一 个目录,然后连接到数据库,使用CREATE TABLESPACE命令创建表空间,如下所示:

[postgres@pghost1 ~]$ mkdir -p /pgdata/10/mytblspc
[postgres@pghost1 ~]$ /usr/pgsql-10/bin/psql -p 1921 mydb
psql (10.2)
Type "help" for help.
mydb=# CREATE TABLESPACE myspc LOCATION '/pgdata/10/mytblspc';
CREATE TABLESPACE
mydb=# \db
List of tablespaces Name |  Owner | Location
---------------+----------+--------------------- myspc       | postgres | /pgdata/10/mytblspc pg_default | postgres | pg_global  | postgres |
(3 rows)

当创建新的数据库或表时,便可以指定刚才创建的表空间,如下所示:

mydb=# CREATE TABLE t(id SERIAL PRIMARY KEY, ival int) TABLESPACE myspc;
CREATE TABLE

由于表空间定义了存储的位置,在创建数据库对象时,会在当前的表空间目录创建一个以数据库OID命名的目录,该数据库的所有对象将保存在这个目录中,除非单独指定表空间。 例如我们一直使用的数据库mydb,从pg_database系统表查询它的OID,如下所示:

mydb=# SELECT oid,datname FROM pg_database WHERE datname = 'mydb'; oid | datname
----------+--------- 16384 | mydb
(1 row)

通过以上查询可知mydb的OID为16384,我们就可以知道 mydb的表、索引都会保存在$PGDATA/base/16384这个目录中,如下所示:

[postgres@pghost1 ~]$ ll /pgdata/10/data/base/16384/
-rw------- 1 postgres postgres 16384 Nov 28 21:22 3712
...
...
...
-rw------- 1 postgres postgres 8192 Nov 28 21:22 3764_vm

3.数据文件命名
在数据库中创建对象,例如表、索引时首先会为表和索引分配段。在PostgreSQL中,每个表和索引都用一个文件存储,新创建的表文件以表的OID命名,对于大小超出1GB的表数据文件,PostgreSQL会自动将其切分为多个文件来存储,切分出的文件用OID.<顺序号>来命名。但表文件并不是总是“OID.< 顺序号>”命名,实际上真正管理表文件的是pg_class表中的 relfilenode字段的值,在新创建对象时会在pg_class系统表中插入该表的记录,默认会以OID作为relfilenode的值,但经过几次VACUUM、TRUNCATE操作之后,relfilenode的值会发生变化。举例如下:

mydb=# SELECT oid,relfilenode FROM pg_class WHERE relname = 'tbl'; oid | relfilenode
----------+------------- 16387 | 16387
(1 row)
mydb=# \! ls -l /pgdata/10/data/base/16384/16387*
-rw------- 1 postgres postgres 8192 Mar 26 22:22 /pgdata/10/data/base/16384/16387

在默认情况下,tbl表的OID为16387,relfilenode也是 16387,表的物理文件为“/pgdata/10/data/base/16384/16387”。 依次TRUNCATE清空tbl表的所有数据,如下所示:

mydb=# TRUNCATE tbl;
TRUNCATE TABLE
mydb=# CHECKPOINT;
CHECKPOINT
mydb=# \! ls -l /pgdata/10/data/base/16384/16387*
ls: cannot access /pgdata/10/data/base/16384/16387*: No such file or directory

通过上述操作之后,tbl表原先的物理文件“/pgdata/10/data/base/16384/16387”已经不存在了,那么tbl表的数据文件是哪一个?

 postgres@160.40:1922/mydb=# select oid,relfilenode from pg_class where relname = 'tbl'; oid | relfilenode ----------+------------- 16387 | 24591
(1 row)
postgres@160.40:1922/mydb=# \! ls -l /pgdata/10/data/base/16384/24591*
-rw------- 1 postgres postgres 0 Apr 2 21:24 /pgdata/10/data/base/16384/24591

如上所示,再次查询pg_class表得知tbl表的数据文件已经成为“/pgdata/10/data/base/16384/24591”,它的命名规则为 .<顺序号>。 在tbl测试表中写入一些测试数据,如下所示:

mydb=# insert into tbl (ival,description,created_time) select (random()*(2*10^9)):: integer as ival,substr('abcdefghijklmnopqrstuvwxyz',1,(random()*26)::integer) as description,date(generate_series(now(), now() + '1 week', '1 day')) as created_time from generate_series(1,2000000);
INSERT 0 16000000

查看表的大小,如下所示:

mydb=# SELECT pg_size_pretty(pg_relation_size('tbl'::regclass));pg_size_pretty
---------------- 1068 MB
(1 row)

通过上述命令看到tbl表的大小目前为1068MB,执行一些 UPDATE操作后再次查看数据文件,如下所示:

/mydb=# \! ls -lh /pgdata/10/data/base/16384/24591*
-rw------- 1 postgres postgres 1.0G Apr 7 08:44 /pgdata/10/data/base/16384/24591
-rw------- 1 postgres postgres 383M Apr 7 08:44 /pgdata/10/data/base/16384/24591.1
-rw------- 1 postgres postgres 376K Apr 7 08:44 /pgdata/10/data/base/16384/24591_fsm
-rw------- 1 postgres postgres 8.0K Apr 7 08:44 /pgdata/10/data/base/16384/24591_vm

如前文所述,数据文件的命名规则为.<顺序号>,tbl表的大小超过1GB,tbl表的relfilenode为24591,超出1GB之外的数据会按每GB切割,在文件系统中查看时就是名称为24591.1的数据文件。在上述输出结果中,后缀为_fsm和 _vm的这两个表文件的附属文件是空闲空间映射表文件和可见性映射表文件。空闲空间映射用来映射表文件中可用的空间,可见性映射表文件跟踪哪些页面只包含已知对所有活动事务可见的元组,它也跟踪哪些页面只包含未被冻结的元组。
下图显示了PostgreSQL数据目录、表空间以及文件的结构概貌。

4.表文件内部结构

在PostgreSQL中,将保存在磁盘中的块称为Page,而将内存中的块称为Buffer,表和索引称为Relation,行称为Tuple, 数据的读写是以Page为最小单位,每个Page默认大小为8kB,在编译PostgreSQL时指定的BLCKSZ大小决定 Page的大小。每个表文件由多个BLCKSZ字节大小的Page组成,每个Page包含若干Tuple。对于I/O性能较好的硬件,并且以分析为主的数据库,适当增加BLCKSZ大小可以小幅提升数据库性能。

如下面的Page内部结构图。

PageHeader描述了一个数据页的页头信息,包含页的一些元信息。它的结构及其结构指针PageHeader的定义如下:

  • pd_lsn:在ARIES Recovery Algorithm的解释中,这个lsn 称为PageLSN,它确定和记录了最后更改此页的xlog记录的 LSN,把数据页和WAL日志关联,用于恢复数据时校验日志文 件和数据文件的一致性;pd_lsn的高位为xlogid,低位记录偏移 量;因为历史原因,64位的LSN保存为两个32位的值。
  • pg_flags:标识页面的数据存储情况。
  • pd_special:指向索引相关数据的开始位置,该项在数据 文件中为空,主要是针对不同索引。
  • pd_lower:指向空闲空间的起始位置。
  • pd_upper:指向空闲空间的结束位置。
  • pd_pagesize_version:不同的PostgreSQL版本的页的格式 可能会不同。
  • pd_linp[1]:行指针数组,即Page内部结构图中的Item1, Item2,…,Itemn,这些地址指向Tuple的存储位置。

如果一个表由一个只包含一个堆元组的页面组成。该页面 的pd_lower指向第一行指针,并且行指针和pd_upper都指向第一个堆元组。当第二个元组被插入时,它被放置在第一个元组之后。第二行指针被压入第一行,并指向第二个元组。 pd_lower更改为指向第二行指针,pd_upper更改为第二个堆元 组。此页面中的其他头数据(例如,pd_lsn、pg_checksum、 pg_flag)也被重写为适当的值。

当从数据库中检索数据时有两种典型的访问方法,顺序扫描和B树索引扫描。顺序扫描通过扫描每个页面中的所有行指针顺序读取所有页面中的所有元组。B树索引扫描时,索引文件包含索引元组,每个元组由索引键和指向目标堆元组的TID组成。如果找到了正在查找的键的索引元组,PostgreSQL使用获取的TID值读取所需的堆元组。

每个Tuple包含两部分的内容,一部分为 HeapTupleHeader,用来保存Tuple的元信息,如Tupe内部结构图所示,包含该Tuple的OID、xmin、cmin等;另一部分为HeapTuple,用来保存Tuple的数据。

1.2.3 完整文件布局图

2、进程结构

PostgreSQL是一用户一进程的客户端/服务器的应用程序。数据库启动时会启动若干个进程,其中有postmaster(守护进程)、postgres(服务进程)、syslogger、checkpointer、 bgwriter、walwriter等辅助进程。

2.1 守护进程与服务进程

首先从postmaster(守护进程)说起。postmaster进程的主要职责有:

  • 数据库的启停。
  • 监听客户端连接。 -
  • 为每个客户端连接fork单独的postgres服务进程。
  • 当服务进程出错时进行修复。
  • 管理数据文件。
  • 管理与数据库运行相关的辅助进程。

当客户端调用接口库向数据库发起连接请求,守护进程 postmaster会fork单独的服务进程postgres为客户端提供服务, 此后将由postgres进程为客户端执行各种命令,客户端也不再需要postmaster中转,直接与服务进程postgres通信,直至客户端断开连接,如下图所示。

PostgreSQL使用基于消息的协议用于前端和后端(服务器和客户端)之间通信。通信都是通过一个消息流进行,消息的第一个字节标识消息类型,后面跟着的四个字节声明消息剩下部分的长度,该协议在TCP/IP和Unix域套接字上实现。服务器作业之间通过信号和共享内存通信,以保证并发访问时的数据完整性,如下图所示。

2.2 辅助进程

除了守护进程postmaster和服务进程postgres外, PostgreSQL在运行期间还需要一些辅助进程才能工作,这些进程包括:

  • background writer:也可以称为bgwriter进程,bgwriter进程很多时候都是在休眠状态,每次唤醒后它会搜索共享缓冲池 找到被修改的页,并将它们从共享缓冲池刷出。
  • autovacuum launcher:自动清理回收垃圾进程。
  • WAL writer:定期将WAL缓冲区上的WAL数据写入磁 盘。
  • statistics collector:统计信息收集进程。
  • logging collector:日志进程,将消息或错误信息写入日 志。
  • archiver:WAL归档进程。
  • checkpointer:检查点进程。

下图显示了服务器端进程与辅助进程和postmaster守护进程的关系。

3、内存结构

PostgreSQL的内存分为两大类:本地内存和共享内存,另外还有一些为辅助进程分配的内存等,下面简单介绍本地内存和共享内存的概貌。

3.1 本地内存

本地内存由每个后端服务进程分配以供自己使用,当后端服务进程被fork时,每个后端进程为查询分配一个本地内存区域。本地内存由三部分组成:work_mem、 maintenance_work_mem和temp_buffers。

  • work_mem:当使用ORDER BY或DISTINCT操作对元组 进行排序时会使用这部分内存。
  • maintenance_work_mem:维护操作,例如VACUUM、 REINDEX、CREATE INDEX等操作使用这部分内存。
  • temp_buffers:临时表相关操作使用这部分内存。

3.2 共享内存

共享内存在PostgreSQL服务器启动时分配,由所有后端进 程共同使用。共享内存主要由三部分组成:

  • shared buffer pool:PostgreSQL将表和索引中的页面从持久存储装载到这里,并直接操作它们。
  • WAL buffer:WAL文件持久化之前的缓冲区。
  • CommitLog buffer:PostgreSQL在Commit Log中保存事务的状态,并将这些状态保留在共享内存缓冲区中,在整个事务处理过程中使用。

下图显示了内存的结构概貌。

4、总结

本篇从全局角度简单讨论了PostgreSQL数据库的文件存储,介绍了构成数据库的参数文件、数据文件布局,以及表空间、数据库、数据库对象的逻辑存储结构,简单介绍了PostgreSQL的守护进程、服务进程和辅助进程,介绍了客户端与数据库服务器连接交互方式,从数据库目录到表文件到最小的数据块,从大到小逐层分析了重要的数据文件。通过这些简单介绍,只能够窥探到PostgreSQL体系的冰山一角, PostgreSQL体系结构中的每一个知识点都有足够丰富的内容, 值得深入学习。PostgreSQL有着多年的技术沉淀,清晰的代码结构,通过源代码深入学习可以事半功倍。

PG系列6-PostgreSQL的体系架构相关推荐

  1. 项目体系架构设计——基于Spark平台的协同过滤实时电影推荐系统项目系列博客(四)

    系列文章目录 初识推荐系统--基于Spark平台的协同过滤实时电影推荐系统项目系列博客(一) 利用用户行为数据--基于Spark平台的协同过滤实时电影推荐系统项目系列博客(二) 项目主要效果展示--基 ...

  2. Spark系列之Spark体系架构

    title: Spark系列 第四章 Spark体系架构 4.1 Spark核心功能 Alluxio 原来叫 tachyon 分布式内存文件系统 Spark Core提供Spark最基础的最核心的功能 ...

  3. 【 PG 入门系列 】PostgreSQL的入门简介(一)

    PostgreSQL的入门简介(一) 1. 唠嗑两句先 1.1. 社区网址 1.2. 创作缘由 1.3. 知识点普及 2. 数据库简介 2.1. 数据库整体排名 2.2. PG的起源 2.3. PG的 ...

  4. DB2 Vs MySQL系列 | 体系架构对比

    前些日子,我们做了DB2 VS MySQL的数据类型的对比,今天我们将体系架构的对比分享给大家,让大家对这两类数据库有更深刻的认识. 相关阅读: DB2迁移至MySQL的最佳实践 MySQL与DB2的 ...

  5. SaaS系列介绍之十三: SaaS系统体系架构

    1 系统体系架构设计 软件开发中系统体系架构决定了一个系统稳定性.健壮性.可扩展性.兼容性和可用性,它是系统的灵魂.体系架构是架构师所关注的核心.良好的体系架构是系统成功的开端,否则,再好的代码与设计 ...

  6. 嵌入式处理器的体系架构与内核详解

    嵌入式处理器的体系架构与内核详解 当我们谈及嵌入式处理器的体系架构时,一般都是想到Intel的X86架构和ARM公司的ARM架构.X86架构和ARM架构最大的不同点就是使用的指令集不同,前者使用的CI ...

  7. Linux 之八 完整嵌入式 Linux 环境、(交叉)编译工具链、CPU 体系架构、嵌入式系统构建工具

      最近,工作重心要从裸机开发转移到嵌入式 Linux 系统开发,由于之前对嵌入式 Linux 环境并不是很了解,因此,第一步就是需要了解如何搭建一个完整的嵌入式 Linux 环境.现在将学习心得记录 ...

  8. oracle multi read,解读Oracle12.2体系架构:Filesystem与Multitenant

    导读 全面解读Oracle 12.2体系架构图系列.本期的内容由两部分组成,一是数据库实例与文件系统的访问:二是多租户解决方案. 数据库实例与文件系统 几个重要的进程和内存组件 RVWR:Recove ...

  9. mysql vacuum_PostgreSQL DBA快速入门(四) - 体系架构

    PostgreSQL在开源关系型数据库市场是最先进的数据库.他的第一个版本在1989年发布,从那时开始,他得到了很多扩展.根据db-enginers上的排名情况,PostgreSQL目前在数据库领域排 ...

  10. [转]OpenContrail 体系架构文档

    OpenContrail 体系架构文档 英文原文:http://opencontrail.org/opencontrail-architecture-documentation/ 翻译者:@KkBLu ...

最新文章

  1. 加大基础研究投入 给科技创新注入“强心剂”
  2. 【Flutter】HTTP 网络操作 ( 引入 http 插件 | 测试网站 | Get 请求 | Post 请求 | 将响应结果转为 Dart 对象 | Future 异步调用 )
  3. 新农人谋定新理念-农业大健康·李孟:“玩”出农业新花样
  4. MySQL锁机制(myisam表所与innoDB锁)
  5. boost::container实现双端队列选项的测试程序
  6. Scrapy 框架【学习笔记01】
  7. HTML+CSS+JS实现canvas仿ps橡皮擦刮卡效果
  8. 51nod1812树的双直径(换根树DP)
  9. echarts 柱状图 不要平均占据_扎心!工资跑不赢房价!平均薪资4127元/月,配得上8字头的九江?...
  10. 用Siri显示二维码, iOS11, INGetVisualCodeIntent
  11. win10和win7两个计算机相连,详解win10两台电脑网线直连的操作方法
  12. ATTCK 1一个烂尾的学习记录
  13. 我们小时候可没这么牛的露天电影
  14. Thymeleaf模板(全程案例详解)
  15. 6年主导3个项目,我终于成了别人眼中的大神
  16. 网络安全系列-三十一: 网络攻防之红队快速入门
  17. 菜鸡学Unity 之 了解 Unity 中的五个视图窗口
  18. 欧氏空间位姿与变换矩阵的转换
  19. python期末复习总结
  20. c语言换零钱程序,换零钱问题。将一元钱换成1分,2分,或5分的零钱有多少换法。vb编程...

热门文章

  1. opencv形态学-开操作和闭操作
  2. duplicate designator is not allowedC/C++(2906)
  3. vue2.0生命周期数据共享
  4. vivo NEX3史上最强旗舰机!瀑布屏+骁龙855+44W+5G,对战华为
  5. ThreeJS - 动态更换fbx模型的某个子Mesh现有的纹理贴图为指定的纹理贴图
  6. 2020-03-19
  7. SAP 玻璃原片单位问题处理
  8. androidStudio Lambda表达式的引入
  9. 【C++】Vscode 中使用CMakeLists(推荐)构建C++项目:项目结构、各个文件夹的作用、从编码到debug
  10. OceanBase集群手动部署