一、Postgresql的内存结构
所谓的内存结构,就是Postgresql在运行时的内存构成,即有多少个进程,每个进程的作用是什么。我们先来看一张PG服务器的运行时内存结构图:
接下来,我们分别介绍了Postgresql各个进程的作用。
1.1 主进程PostMaster
postmaster进程是PG数据库最主要的管理进程,它的作用主要有两个:
(1)可以启动和关闭数据库的实例。
/usr/pgsql-11/bin/postmaster -D $PGDATA
如果是在centos7+下使用yum安装的postgresql,那么postgresql的启停会托管给systemctl,我们在/usr/lib/systemd/system/postgresql-11.service文件中就可以看到配置的ExecStart就是使用的上述命令:
实际上,postmaster 命令只是postgres命令的一个软链接,可以看到postmaster 的目标命令是postgres。如下所示,postmaster命令只是同目录postgres命令的一个软连接
[postgres@VM-115-39-centos /usr/pgsql-11/bin]ls -l postmaster lrwxrwxrwx 1 root root 8 Jul 25 2020 postmaster -> postgres
(2)为客户端的链接创建postgres子进程。
Postgresql针对每个客户端的链接都会fork一个子进程,如下所示,圈出部分就是客户端的一个远程链接而创建的postgres进程,所以Postgresql在处理客户端链接时成本还是很高的。
1.2 backgroud writer进程
bgwriter进程是将共享内存中的脏数据块写入到磁盘上的进程。这里,先解释下什么是脏数据块。当向数据库中插入或者更新数据时,往往不会马上将这些更新的数据写入到磁盘,而是直接在内存中进行更新,这样做主要是为了提高更新或者插入的效率。但是这也就引入了一个问题——内存中的数据和磁盘中已经持久化的数据是不一致的,我们就把这部分内存中的数据称为脏块。bgwriter进程的作用就是按照一定的周期,将脏块的数据写入到磁盘。这个更新的频率可以通过postgresql.conf配置文件进行配置,过高或者过低的频率都有问题,比如说过高的话,磁盘IO过于频繁,性能会降低;过低时,有可能造成内存不足,当有新的数据要写入或者要查询脏块中的数据时,就需要将数据先写入到内存中,必然会造成更新或者查询更慢。所以我们进行配置时要找到一个合适的频率。
1.3 wal writer进程
postgresql中的wal日志也叫预写日志,和oracle中的redo日志类似,执行数据更新时,先要将更新记录记录到磁盘上,主要是为了防止在数据库服务器突然宕机时,内存中的脏数据丢失,可以根据wal日志进行数据的恢复。wal日志的保存目录是在$PGDATA/pg_wal目录下,可以看到文件名逐渐递增,而且每个文件的大小都是16M,总的大小可配置,当达到了最大的值,新的Wal日志会覆盖旧的:
1.4 logger进程
logger进程可以收集包括postmaster进程在内的所有进程的stderr日志,这里需要注意是错误日志哦,并不是所有的日志它都会收集。它支持通过postgresql.conf文件修改日志相关的参数,可以设置最大值并循环覆盖,日志的默认目录在$PGDATA/logs/,像我本机默认的日志记录如下,每天一个日志文件,按照星期存储:
查看某个日志的内容,发现都是一些错误的信息:
1.5 checkpoint进程
和oracle类似,postgresql也有检查点进程checkpoint,检查点进程的主要作用就是,辅助bgwriter进行内存中脏数据块的写入。实际上,当bgwriter向磁盘中写入脏块时,是通过checkpoint进程实际写入的。bgwriter在扫描出脏块后,会发起写入的request,而checkpoint进程会读取这些request,将脏块批量写入磁盘,并且会记录已经写入后的脏块,从而保证下次只会写入新的脏块。
1.6 autovacuum launcher进程
该进程主要是两个作用:
(1)PG数据库中DELET操作,原来的数据并不会被立即删除。即使是Update的数据,也不会在旧的数据行上进行更新,而是新生成一行数据,将原来的数据行标记为删除状态。这就是为了PG数据库的共享锁和独占锁机制,只有在其它事务没有在读取这些旧数据时,它们才会被删除。而autovacuum进程的作用就是自动清理这些被标记为删除状态的数据。
(2)负责统计信息的收集,用于查询优化时的代价的估算。
1.7 state collector进程
该进程的主要作用就是数据的统计收集,比如在一个表上进行了多少次的插入、更新和删除操作,磁盘块的读写次数、行的读写次数等等。
1.8 logical replication launcher进程
用于逻辑复制,订阅者通过 logical replication launcher进程向发布者订阅表的更新并获取更新。
1.9 archiver进程
归档进程,所谓归档,就是在旧的WAL日志在覆盖前把其备份出来,Postgresql支持一种PITR(Point-In-Time-Recovery)技术,就是在数据库进行一次全量备份之后,该技术会将备份时间点之后的WAL日志归档备份,这样使用全量备份再加上WAL日志,就可以将数据库恢复到任意的时间点了。但是需要注意的是Postgresql默认的配置是不开发归档功能的,如果需要开放,要在postgresql.conf文件中设置archive_mode = on
1.10 共享内存
主要作为数据缓冲区,提高读写性能,作为WAL日志和Commit日志的缓冲区等等。
1.11 本地内存
- 临时缓冲区,用来访问临时表的本地缓冲区
- work_mem,作为内部排序操作和联合查询时合并链接、Hash链接的临时内存
- maintenance_work_mem,在维护性操作(比如VACUUM、CREATE INDEX)中使用的内存缓冲区。
二、Postgresql的逻辑结构
- 数据库簇
可以理解为一个数据库的容器,可以存放多个数据库实例(create database)。Postgresql的数据库是一个多租户的概念,租户之间互相隔离。
- 数据库
这个不用多解释了,使用create database可以创建数据库。
- 模式schema
模式是相同数据库下逻辑隔离的基本单位,可以通过create schema创建不同的模式,其实有点类似与oracle中的用户,oracle中不同用户可以创建不同的表,表名可以重复,其它用户访问别的用户的表时需要带上用户名,这样就实现了用户间的数据隔离,而Postgresql中使用模式达到这种数据隔离的目的。
test=# create schema myschema;
CREATE SCHEMA
test=# create table myschema.mytable(id INT,name varchar);
CREATE TABLE
test=# \d+ myschema.mytable
Table "myschema.mytable"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
--------+-------------------+-----------+----------+---------+----------+--------------+-------------
id | integer | | | | plain | |
name | character varying | | | | extended | |
三、Postgresql的目录结构
3.1 程序目录结构
- 跟目录,一般是在/usr/pgsql-<主版本>下
- bin/,存放了二进制的可执行程序,比如启停程序postgres,备份工具pg_dump、pg_restore等备份工具、psql客户端工具,都是在这里。
- lib/,存放了动态库文件,*.so
- share/,存放了文档和配置文档文件、以及扩展包的sql文件等等。
3.2 存储目录结构
- 跟目录,/var/lib/pgsql/<主版本号>/data
- postgresql.conf,数据库实例的配置文件,基本上所有的配置参数都在这个文件中
- postgresql.auto.conf,通过alter system修改的配置参数存放到该文件内。
- pg_ident.conf,ident认证方式的配置文件
- pg_hba.conf,认证相关的配置文件
- PG_VERSION,记录了数据库的主版本
- postmaster.opts,记录上数据库启动的命令行参数
[postgres@VM-115-39-centos ~/11/data]cat postmaster.opts /usr/pgsql-11/bin/postgres "-D" "/var/lib/pgsql/11/data/"
以上是主要的文件,接下来是几个主要的子目录
- base/,默认表空间的目录,下面有多个子目录,子目录的名称对应的数据库的OID相同
数据库的OID:
postgres=# select oid,datname from pg_database;
oid | datname
-------+-------------------------
13881 | postgres
1 | template1
13880 | template0
16385 | stock_analysis_data
17724 | stock_test_activiti
18121 | stock_analysis_activiti
18621 | test
18622 | test1
18623 | test2
base目录下的子目录:
[postgres@VM-115-39-centos ~/11/data]ls base/
13880 13881 16385 17724 18121 18621 18622 18623 pgsql_tmp
- global/,共享系统表的目录
- log/,logger进程存放的错误日志
- pg_commit_ts/,视图提交的时间戳数据
- pg_dynshmem/,动态共享内存子系统使用的文件
- pg_logical/,logical replication launcher进程进行逻辑复制的中间数据
- 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日志的存放目录
- pg_xact/,Commit Log的目录
接下来,我们思考一个问题:数据库中每个表里面的数据都是存到磁盘文件中的,那么这些文件的存储位置在哪呢?要想弄清楚这个问题,我们需要了解一个概念——relfilenode,当我们创建一个表或者索引时,Postgresql会为每个表和索引都分配一个relfilenode(实际上就是一个int类型的编号),可以通过pg_class查询到某个表的relfilenode。比如查询表fund的relfilenode:
stock_analysis_data=# select relnamespace,relname,relfilenode from pg_class where relname='fund';
2200 | fund | 17586
fund是数据库stock_analysis_data中的一个表,通过上面我们从pg_database查询数据库的OID得知,stock_analysis_data的OID是16385 ,而stock_analysis_data用的是默认的表空间,从上面的查询中可以看到,fund表的relfilenode为17586。一张表的数据文件目录为:
<默认的表空间目录>/<数据库 oid>/<relfilenode>[.顺序号]
其中,顺序号从1开始,当表或索引的大小超过1G时,顺序号加1,所以fund表对应的文件路径如下:
/var/lib/pgsql/11/data/base/16385/17586