理解PostgreSQL的体系结构

PostgreSQL可能是开源关系数据库市场中最先进的数据库。1989年首次发布,从此有了大量的改进和功能增强。根据db-engine统计,截止到目前,PostgreSQl在最常使用的数据库中,排名第四。

我们将会讨论PostgreSQL的内部结构,体系结构以及各种组件之间的交互。这篇文章将作为我们后续的PostgreSQL DBA博客系列文章的起点和组成。

PostgreSQL体系结构

PostgreSQL的物理结构非常简单。它是由共享内存(shared memory)和少量的后台进程以及数据文件组成。
理解PostgreSQL的体系结构

共享内存(shared memory)

共享内存是指提供数据缓存和事务日志缓存的内存。在共享内存中最重要的组成部分是shared Buffer和WAL buffer(Write-Ahead Logging)

shared Buffer(共享缓冲区)

共享缓冲区的主要目的是最大限度的减少磁盘IO.为达到这个目的,必须要满足如下的这些要求

  • 需要快速访问非常大的缓冲区
  • 当多个用户同时访问并发生争用时,应该确保最小化争用。
  • 使用最频繁的数据块必须尽可能长时间的保存在缓冲区中。

WAL buffer(预写日志缓存)

WAL:Write-Ahead Logging 预写日志

WAL buffer 是一个临时存储数据更改的缓冲区,存储在WAL buffer 中的内容将在预定的时间点写入WAL文件。
从备份和恢复的角度,WAL buffer和WAL文件是非常重要的。

PostgreSQL 进程的类型

PostgreSQL有四种类型的进程

  • Postmaster(Daemon)进程
  • 后台进程(Background Process)
  • 后端进程(Backend Process)
  • 客户端进程(Client Process)

Postmaster 进程

Postgmaster进程是PostgreSQL启动的第一个进程。
负责实施恢复,初始化共享内存,并启动后台进程。
当客户端进程有链接请求时,负责创建后端进程。

理解PostgreSQL的体系结构

如果使用pstree命令检查进程之间的关系,你会发现Postmaster是所有进程的父进程。(pstree命令并不显示进程的名称,为了解释清晰,我增加了进程的名称和参数)

理解PostgreSQL的体系结构

后台进程(Background Process)

PostgreSQL操作所需的后台进程列表如下。

进程名称 角色作用
logger 将错误信息写入到日志文件中
checkpointer 每次触发检查点,缓冲区中修改过的数据会被写入到文件
writer 周期性的将缓冲区中修改过的数据写入到文件中
wal writer 将WAL缓冲区的数据写入到WAL文件中
Autovacuum launcher 如果参数配置为autovacuum开启,则负责创建autovacuum worker进程。 autovacuum守护进程的职责是对需要清理的表执行vacuum操作
archiver 当处于归档日志模式时,拷贝WAL文件到指定的目录
stats collector 收集会话执行信息(pg_stat_activity)和表的使用信息(pg_stat_all_tables)等数据库系统需要的统计信息

后端进程(Backend Process)

后端进程的最大数量取决于max_connections参数的设置,默认值是100。
后端进程执行用户的查询请求,然后传输结果。
执行查询操作需要一些特定的内存结构,我们称为本地内存。
与本地内存相关的主要参数

  • work_mem空间主要用来进行排序、位图操作、哈希连接、合并连接。默认值为4M。
  • Maintenance_work_mem空间主要用于Vacuum和创建索引。默认值为64M.
  • Temp_Buffers空间用户临时表,默认值为8M

客户端进程(Client Process)

客户端进程是指为每个后端用户连接分配的后台进程。通常,postmaster进程将创建一个子进程,该子进程专用于服务于用户连接。

数据库结构

当我们尝试理解PostgreSQL的数据库结构时,有如下这些重要的事情需要了解

与数据库相关的

  1. PostgreSQL由几个数据库组成,我们称之为数据库集群。
  2. 当initdb()被执行的时候,template0 , template1 , and 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表空间的物理位置是$PGDATAbase。
  5. pg_global表空间的物理位置是$PGDATAglobal。
  6. 一个表空间可以被多个数据库使用。此时,将在表空间目录中创建特定于数据库的子目录。
  7. 创建用户表空间将在$PGDATAtblspc目录中创建到用户表空间的符号链接。

与表相关

  1. 每个表对应三个文件

    • 一个是存储数据的文件,文件名是该表的OID
    • 一个是管理表空闲空间的文件,文件名为OID_fsm
    • 一个是管理表块可见性的文件。文件名是OID_vm
    • 索引没有_vm文件。也就是说,OID和OID_fsm由两个文件组成。

其他需要记住的

创建表和索引时的文件名是OID。OID和pg_class.relfilenode是相同的。
但是,当执行重写操作(Truncate、CLUSTER、Vacuum Full、REINDEX等)时,将更改受影响对象的relfilenode值,文件名也将更改为relfilenode值。您可以使用pg_relation_filepath ('< object name >')轻松地检查文件位置和名称。

实际操作

如果在initdb()之后查询pg_database视图,可以看到已经创建了template0、template1和postgres数据库。

postgres=# select oid,datname,datistemplate,datallowconn from pg_database order by 1;
  oid  |  datname  | datistemplate | datallowconn 
-------+-----------+---------------+--------------
     1 | template1 | t             | t
 13210 | template0 | t             | f
 13211 | postgres  | f             | t
 16394 | bucardo   | f             | t
(4 rows)
  • 通过datistemplate列,您可以看到template0和template1数据库是用于创建用户数据库模板的数据库。
  • datlowconn列指示是否可以访问数据库。不能访问template0数据库,同时数据库的内容也不能更改。
  • 为模板提供两个数据库的原因是,template0数据库是初始状态模板,template1数据库是用户添加的模板。
  • postgres数据库是使用template1数据库创建的默认数据库。如果在连接时没有指定数据库,则将连接到postgres数据库。
  • 数据库位于$PGDATA/base目录下。目录名是数据库的OID号。
[postgres@localhost base]$ ll 
总用量 48
drwx------. 2 postgres postgres 8192 7月  11 14:20 1
drwx------. 2 postgres postgres 8192 6月  28 13:49 13210
drwx------. 2 postgres postgres 8192 7月  11 14:20 13211
drwx------. 2 postgres postgres 8192 7月  11 14:21 16394

创建用户数据库

用户数据库由克隆template1数据库创建。要验证这一点,请在template1数据库中创建一个用户表T1。创建mydb01数据库之后,检查T1表是否存在。

postgres=# \c template1
You are now connected to database "template1" as user "postgres".
template1=# create table t1 (c1 integer);
CREATE TABLE
template1=# \c postgres
You are now connected to database "postgres" as user "postgres".
postgres=# create database mydb01;
CREATE DATABASE
postgres=# \c mydb01
You are now connected to database "mydb01" as user "postgres".
mydb01=# \d
        List of relations
 Schema | Name | Type  |  Owner   
--------+------+-------+----------
 public | t1   | table | postgres

理解PostgreSQL的体系结构

pg_default 表空间

如果在initdb()之后查询pg_tablespace,可以看到已经创建了pg_default和pg_global表空间。

postgres=# select oid,* from pg_tablespace;
  oid  |  spcname   | spcowner | spcacl | spcoptions 
-------+------------+----------+--------+------------
  1663 | pg_default |       10 |        | 
  1664 | pg_global  |       10 |        | 
 16999 | tbs_test   |       10 |        | 
(3 rows)

pg_default表空间的位置是$PGDATAbase。这个目录中有一个按数据库OID划分的子目录

[postgres@localhost base]$ ls -l $PGDATA/base
总用量 60
drwx------. 2 postgres postgres 8192 7月  12 14:57 1
drwx------. 2 postgres postgres 8192 6月  28 13:49 13210
drwx------. 2 postgres postgres 8192 7月  11 14:20 13211
drwx------. 2 postgres postgres 8192 7月  11 14:21 16394
drwx------. 2 postgres postgres 8192 7月  12 14:59 17008

理解PostgreSQL的体系结构

pg_global 表空间
pg_global表空间是用于存储要在“数据库集群”级别管理的数据的表空间。

  • 例如,与pg_database表类型相同的表无论是否从任何数据库访问,都提供相同的信息。
  • pg_global表空间的位置是$PGDATAglobal。

创建用户表空间

postgres=# create tablespace myts01 location '/data01';
CREATE TABLESPACE
postgres=# select oid,* from pg_tablespace;
  oid  |  spcname   | spcowner | spcacl | spcoptions 
-------+------------+----------+--------+------------
  1663 | pg_default |       10 |        | 
  1664 | pg_global  |       10 |        | 
 16999 | tbs_test   |       10 |        | 
 17010 | myts01     |       10 |        | 
(4 rows)

$PGDATA/pg_tblspc目录中的符号链接指向表空间目录。

[postgres@localhost ~]$ ls -l $PGDATA/pg_tblspc
总用量 0
lrwxrwxrwx. 1 postgres postgres 37 7月  12 13:39 16999 -> /usr/local/pgsql/data/tablespace_data
lrwxrwxrwx. 1 postgres postgres  7 7月  12 15:15 17010 -> /data01
[postgres@localhost ~]$ 

更改表空间位置

PostgreSQL在创建表空间时指定一个目录。因此,如果目录所在的文件系统已满,则不能再存储数据。要解决这个问题,可以使用卷管理器。但是,如果不能使用卷管理器,可以考虑更改表空间位置。操作顺序如下。

[postgres@localhost 13211]$ pg_ctl stop
waiting for server to shut down.... done
server stopped

[root@localhost 13210]# cp -rp /data01/PG* /data02

[root@localhost pg_tblspc]# chown postgres.postgres /data02

[root@localhost pg_tblspc]# ll
总用量 0
lrwxrwxrwx. 1 postgres postgres  7 7月  12 15:15 17010 -> /data01

[root@localhost pg_tblspc]# rm 17010

[root@localhost pg_tblspc]# ln -s /data02 17010

[postgres@localhost 13211]$ pg_ctl start

Note: 表空间在使用分区表的环境中也非常有用。因为可以为每个分区表使用不同的表空间,所以可以更灵活地处理文件系统容量问题。

什么是Vacuum?

Vacuum做了如下这些事

  1. 收集表和索引统计信息
  2. 重新组织表
  3. 清理表和索引无用的数据块
  4. 由记录XID冻结,以防止XID循环

1和2通常是DBMS管理所必需的。但是3和4是必要的,因为PostgreSQL MVCC特性

上一篇:PostgreSQL准备和部署


下一篇:Enable point-in-time-recovery in PostgreSQL