在日常的应用程序开发过程中,都离不开数据库的使用。今天就带大家走进非常有名的关系型数据库管理系统:PostgreSQL。

1 安装 PostgreSQL

在深入了解 PostgreSQL 前,先得在我们得主机上安装一下 PostgreSQL。

对于 macOS 用户非常简单,使用以下命令安装

1
brew install postgresql

一开始我也是这么安装的,它也可以支持升级新的 PostgreSQL,是非常方便的。但是这里我更加推荐一种安装方式,使用 Docker 来安装启动 PostgreSQL,因为它可以跨平台按需使用,更加方便且易于后期修改。
关于 Docker 的安装,可以在 Docker Desktop for Mac and Windows 安装对应的客户端软件。

Docker Desktop

1
docker run --name postgres -p 5432:5432 -v ~/Documents/docker-volumn/postgres:/var/lib/postgresql/data -e POSTGRES_PASSWORD=postgres -d postgres:12-alpine

另外可以去PostgreSQL 官方文档多多了解 PostgreSQL,丰富的知识点在等着你们学习,下图的黄色部分尤其是重点也是难点。

PostgreSQL Document

2 走进 PostgreSQL 内部

2.1 组织结构

假设我们是使用 macOS 的 brew 安装的 PostgreSQL。那么数据库的文件路径应该在 /usr/local/var/postgres 下。它的目录结构如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 文件
├── PG_VERSION 主版本号
├── pg_hba.conf
├── pg_ident.conf
├── postgresql.auto.conf
├── postgresql.conf 配置参数
├── postmaster.opts
└── postmaster.pid

# 目录
├── base 数据库文件
├── global
├── pg_commit_ts
├── pg_dynshmem
├── pg_logical
├── pg_multixact 多事务状态数据
├── pg_notify
├── pg_replslot
├── pg_serial
├── pg_snapshots
├── pg_stat
├── pg_stat_tmp
├── pg_subtrans 子事务状态数据
├── pg_tblspc
├── pg_twophase 两阶段事务的状态文件
├── pg_wal WAL 段文件
└── pg_xact 事务提交状态

数据库中表和索引都是文件形式存储在磁盘上,文件名为 oid,大小最大为 1 GB,超过则会创建一个新的文件。而文件的内部被划分成一个个页,而页的大小默认为 8 KB。

那么怎样才能找到对应的文件在我们磁盘的哪个位置呢?可以利用 SQL 查询得到我们想要的结果。

1
2
3
4
-- 找到数据库的 oid
select * from pg_database where datname = '数据库名';
-- 找到表或者索引的 oid 或者 relfilenode
select * from pg_class where relname = '表或索引名';

表相关的文件类型有三类,除了存储表数据的文件外,还有后缀为 _fsm 的是空闲空间映射文件,而 _vm 为可见性映射文件。值得注意的是索引文件是没有可见性映射文件的。

1
2
3
4
5
6
7
➜  16401 pwd
/usr/local/var/postgres/base/16401
➜ 16401 ls -la 33657*
# 表相关的文件
-rw------- 1 postgres postgres 811008 Mar 28 13:02 33657
-rw------- 1 postgres postgres 24576 Mar 28 13:02 33657_fsm
-rw------- 1 postgres postgres 8192 Mar 28 13:02 33657_vm

这里跟 MySQL 不同的一点是 MySQL 的页大小为 16 KB,而操作系统默认的页大小也为 4 KB。

2.2 堆表文件

PostgreSQL 内部采用堆元组存储数据本身,因此一个页上将会有多个元组(heap tuple),并使用元组标识符(tuple identifier,tid)来表示,其结构为(页号,行指针偏移量)

页结构

  • 头部数据
    • p d_lsn: 最近应用到本页面 XLog 记录的 LSN
    • pd_lower: 指向行指针的末尾,空闲空间开始的位置
    • pd_upper: 指向最新元组的位置,空闲空间结束的位置
  • 行指针
    • 长度为 15 bit
    • 有偏移量,长度和标志这三个属性
  • 堆元组数据
    • HeapTupleHeaderData
      • t_xmin:插入事务的 ID
      • t_xmax:删除或更新的事务的 ID
      • t_cid:命令 ID
      • t_ctid:当前元组或者更新元组的 TID
      • t_infomask2:属性和标志位
      • t_infomask:标志位
      • t_hoff:首部 + 位图 + 填充的长度
    • 空值位图
    • 用户数据

2.3 内存结构

  • 本地内存区域
    • work_mem:工作内存,用于排序命令使用
    • maintenance_work_mem:维护工作内存,主要用于自动清理和重新索引
    • temp_buffers:临时缓冲,用于临时表的使用
  • 共享内存区域
    • shared buffer pool:表和索引的数据会加载到这里
    • WAL buffer:存储事务日志
    • commit log:存储事务提交日志
    • 其他共享区域

缓冲区管理器

PostgreSQL 缓冲区管理器由缓冲表、缓冲区描述符和缓冲池组成,管理着共享内存和持久存储之间的数据传输。其维护缓冲区标签(数据库 oid,表 oid,文件类型分支号)到缓冲池页位置的关系,并采用时钟扫描算法(LFU 的一种变体)来进行页面置换,辅以后台的检查点进程和后台写入器进程来进行脏页刷盘。

  • 缓冲表层是一个散列表,存储着页面的缓冲区标签与缓冲区描述符的缓冲 ID(同时也是缓冲池位置) 之间的映射关系,提供共享模式和独占模式的分段式、轻量级锁
  • 缓冲区描述符层是一个由缓冲描述符组成的数组,每个缓冲区描述符与下层的缓冲池槽一一对应,保存着相关页面的元数据。为了加快分配过程,维护了一个空闲列表,用于快速分配下一个可用的缓存区描述符。并提供用于内容读写的共享模式和独占模式的内容锁和独占的 I/O 锁。
  • 缓冲池是一个每个槽为 8 KB,用来保存页面文件数据的一个数组。

2.4 多进程架构

PostgreSQL 采用的多进程的架构,通过以下命令可以找出所有的 postgre 进程。

1
2
3
4
5
6
7
8
9
10
11
12
13
# 先使用 ps 找到 postgres 的 ppid(上级父程序的 pid)
ps -ef |grep postgres
# 再列出所有的 postgre 的相关进程
➜ ~ pstree -p <ppid>
-+= 00001 root /sbin/launchd
\-+= 76776 zhihaozhang /usr/local/opt/postgresql/bin/postgres -D /usr/local/va
|--= 44288 postgres postgres: postgres mydb 127.0.0.1(51293) idle
|--= 76779 postgres postgres: checkpointer
|--= 76780 postgres postgres: background writer
|--= 76781 postgres postgres: walwriter
|--= 76782 postgres postgres: autovacuum launcher
|--= 76784 postgres postgres: stats collector
\--= 76785 postgres postgres: logical replication launcher

PostgreSQL 默认的最大客户端连接数为 100 个,由于多进程的缘故,与 MySQL 的线程连接模型相比,其内存压力更大。

3 索引

PostgreSQL 的索引有多种类型,其中比较常见的是 B 树,而 MySQL 则使用的是 B+ 树,这是两者的一大区别。

未完待续…


更多精彩内容请关注扫码

KnowledgeCollision 微信公众号

Knowledge Collision 激发思维碰撞,IDEA 丛生