PostgreSQL可能是开源关系数据库市场中最先进的数据库。1989年首次发布,从此有了大量的改进和功能增强。根据db-engine统计,截止到目前,PostgreSQl在最常使用的数据库中,排名第四。
我们将会讨论PostgreSQL的内部结构,体系结构以及各种组件之间的交互。这篇文章将作为我们后续的PostgreSQL DBA博客系列文章的起点和组成。
PostgreSQL体系结构
PostgreSQL的物理结构非常简单。它是由共享内存(shared memory)和少量的后台进程以及数据文件组成。
共享内存(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启动的第一个进程。
负责实施恢复,初始化共享内存,并启动后台进程。
当客户端进程有链接请求时,负责创建后端进程。
如果使用pstree命令检查进程之间的关系,你会发现Postmaster是所有进程的父进程。(pstree命令并不显示进程的名称,为了解释清晰,我增加了进程的名称和参数)
后台进程(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的数据库结构时,有如下这些重要的事情需要了解
与数据库相关的
- PostgreSQL由几个数据库组成,我们称之为数据库集群。
- 当initdb()被执行的时候,template0 , template1 , and postgres 数据库将被创建。
- template0 , template1是用户创建数据库时的模板数据库,其中包含了系统字典表。
- 在执行了initdb()后,template0 , template1中的表是相同的。然而,template1数据库能够创建用户所需的数据库对象。
- 用户数据库是通过克隆template1数据库创建的。
与表空间相关
- 在执行完initdb()后,表空间pg_default和pg_global则立即被创建。
- 创建表时如果不指定表空间,则表默认存储在pg_default表空间。
- 数据库集群级别管理的表存储在pg_global表空间中。
- pg_default表空间的物理位置是$PGDATAbase。
- pg_global表空间的物理位置是$PGDATAglobal。
- 一个表空间可以被多个数据库使用。此时,将在表空间目录中创建特定于数据库的子目录。
- 创建用户表空间将在$PGDATAtblspc目录中创建到用户表空间的符号链接。
与表相关
-
每个表对应三个文件
- 一个是存储数据的文件,文件名是该表的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
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
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做了如下这些事
- 收集表和索引统计信息
- 重新组织表
- 清理表和索引无用的数据块
- 由记录XID冻结,以防止XID循环
1和2通常是DBMS管理所必需的。但是3和4是必要的,因为PostgreSQL MVCC特性