专栏内容:
postgresql内核源码分析
手写数据库toadb
并发编程
个人主页:我的主页
座右铭:天行健,君子以自强不息;地势坤,君子以厚德载物.
postgresql 数据库服务运行时,数据在磁盘上是如何存储的呢?这就涉及到了存储架构。
在文件系统中,我们可以看到以目录和文件为形式的存储单元,这是物理存储架构,
这些目录和文件实际上有一定的联系和组织形式,比如最外层目录就是集群数据目录,每个数据库会有一个目录,这就是逻辑存储架构。
逻辑存储架构,维护着物理磁盘文件的组织形式,物理存储架构是具体的磁盘文件的呈现方式。
在逻辑上,数据库有几层组织管理命名空间
集簇->表空间tablespace->数据库database->模式schema
其中 模式是数据库内核中通过数据字典来区分,所以前三项都是通过存储架构的组织,来实现物理上的空间独立。
这在前面内核分析文章中也提到,表文件的定位,也是通过tablespace/database/relation三级来唯一标识。
对于集簇这个概念,通过initdb创建的就是集簇,也就是数据存入的目录,在数据库服务启动时需要指定,它通常叫做PGDATA。
有表,索引,还有对应的vm,fsm文件,都是按照命名空间的层级目录来存储
由集簇级层级的空间管理,在集簇目录下有公共目录存放
如表空间文件,数据字典文件,模版库,运行日志等文件,都是集簇层级的空间进行管理,在集簇目录下有各自的目录
postgresql.conf
pg_hba.conf和pg_ident.conf
如版本文件,运行信息文件等,都在PGDATA根目录下
- 通常用PGDATA来引用(用的是可以定义它的环境变量的名字)。PGDATA的一个常见位置是/var/lib/pgsql/data。由不同数据库实例所管理的多个集簇可以在同一台机器上共存。
- 在表或者索引超过 1GB 之后,它就被划分成1G大小的段。
第一个段的文件名和文件节点相同;随后的段被命名为 filenode.1、filenode.2等等。这样的安排避免了在某些有文件大小限制的平台上的问题(实际上,1GB只是默认的段尺寸。段尺寸可以在编译PostgreSQL时使用配置选项–with-segsize进行调整)。原则上,空闲空间映射和可见性映射分支也可以要求多个段,但实际上这很少发生。
- 每个数据库都会有一个单独的目录,其中存放该库的表文件。
先初始化一个全新的数据库集簇,下面初始化并启动数据库
# 初始化postgres数据库集簇 /opt/postgres/bin/initdb -D pgtest -W # 启动数据库,监听端口指定为 8888 /opt/postgres/bin/pg_ctl -D pgtest -l logfile -o "-p 8888" start # 以命令行客户端,登陆数据库,指定端口和数据库 /opt/postgres/bin/psql -p 8888 -d postgres
下面是initdb完成后,新建了一个表空间后的集簇目录结构,
然后为了看到一些文件类型,建了临时表,unlogged表,以及索引;
中间省略了一些表的文件列表,保留了目录层级和关键的文件。
-- 表空间 create tablespace tblspc_test location '/mnt/sda1/data/pgdata/pgtblspc'; -- 普通表 create table tbl_account(id integer, name varchar, address varchar, tel varchar); -- 临时表 会话退出后就会删除 create temporary table tmptbl_test(id int, c_id int); -- unlogged 表,不会记录WAL,恢复时数据全部丢失 create unlogged table unlogtbl_test(c_id int ,consumer varchar); create index on unlogtbl_test (c_id);
经过上面的场景构造之后,现在来看一下数据库集簇下的文件和目录层次
[senllang@hatch pgdata]$ tree pgtest pgtest ├── base │ ├── 1 │ │ ├── 112 │ │ ├── 113 │ │ ├── 1247 │ │ ├── 1247_fsm │ │ ├── 1247_vm │ │ ├── 1249 ...... │ │ ├── 827 │ │ ├── 828 │ │ ├── pg_filenode.map │ │ ├── pg_internal.init │ │ └── PG_VERSION │ ├── 4 │ │ ├── 112 │ │ ├── 113 │ │ ├── 1247 │ │ ├── 1247_fsm │ │ ├── 1247_vm ...... │ │ ├── 6238 │ │ ├── 6239 │ │ ├── 826 │ │ ├── 827 │ │ ├── 828 │ │ ├── pg_filenode.map │ │ └── PG_VERSION │ └── 5 │ ├── 112 │ ├── 113 │ ├── 1247 │ ├── 1247_fsm │ ├── 1247_vm │ ├── 16403 │ ├── 16403_init │ ├── 16406 │ ├── 16406_init │ ├── 16407 │ ├── 16407_init │ ├── 16408 │ ├── 16408_init │ ├── t3_16409 │ └── t3_16412 ...... │ ├── 827 │ ├── 828 │ ├── pg_filenode.map │ ├── pg_internal.init │ └── PG_VERSION ├── global │ ├── 1213 │ ├── 1213_fsm │ ├── 1213_vm │ ├── 1214 │ ├── 1232 │ ├── 1233 │ ├── 1260 │ ├── 1260_fsm │ ├── 1260_vm ...... │ ├── pg_control │ ├── pg_filenode.map │ └── pg_internal.init ├── pg_commit_ts ├── pg_dynshmem ├── pg_hba.conf ├── pg_ident.conf ├── pg_logical │ ├── mappings │ ├── replorigin_checkpoint │ └── snapshots ├── pg_multixact │ ├── members │ │ └── 0000 │ └── offsets │ └── 0000 ├── pg_notify ├── pg_replslot ├── pg_serial ├── pg_snapshots ├── pg_stat ├── pg_stat_tmp ├── pg_subtrans │ └── 0000 ├── pg_tblspc │ └── 16388 -> /mnt/sda1/data/pgdata/pgtblspc ├── pg_twophase ├── PG_VERSION ├── pg_wal │ ├── 000000010000000000000001 │ └── archive_status ├── pg_xact │ └── 0000 ├── postgresql.auto.conf ├── postgresql.conf ├── postmaster.opts └── postmaster.pid
文件/目录 | 描述 |
---|---|
PG_VERSION | 一个包含PostgreSQL主版本号的文件 |
base | 包含每个数据库对应的子目录的子目录 |
current_logfiles | 记录当前被日志收集器写入的日志文件的文件 |
global | 包含集簇范围的表的子目录,比如pg_database |
pg_commit_ts | 包含事务提交时间戳数据的子目录 |
pg_dynshmem | 包含被动态共享内存子系统所使用的文件的子目录 |
pg_logical | 包含用于逻辑复制的状态数据的子目录 |
pg_multixact | 包含多事务(multi-transaction)状态数据的子目录(用于共享的行锁) |
pg_notify | 包含LISTEN/NOTIFY状态数据的子目录 |
pg_replslot | 包含复制槽数据的子目录 |
pg_serial | 包含已提交的可序列化事务信息的子目录 |
pg_snapshots | 包含导出的快照的子目录 |
pg_stat | 包含用于统计子系统的永久文件的子目录 |
pg_stat_tmp | 包含用于统计信息子系统的临时文件的子目录 |
pg_subtrans | 包含子事务状态数据的子目录 |
pg_tblspc | 包含指向表空间的符号链接的子目录 |
pg_twophase | 包含用于预备事务状态文件的子目录 |
pg_wal | 包含 WAL (预写日志)文件的子目录 |
pg_xact | 包含事务提交状态数据的子目录 |
postgresql.auto.conf | 一个用于存储由ALTER SYSTEM 设置的配置参数的文件 |
postmaster.opts | 一个记录服务器最后一次启动时使用的命令行参数的文件 |
postmaster.pid | 一个锁文件,记录着当前的 postmaster 进程ID(PID)、集簇数据目录路径、postmaster启动时间戳、端口号、Unix域套接字目录路径(Windows上为空)、第一个可用的listen_address(IP地址或者*,或者为空表示不在TCP上监听)以及共享内存段ID(服务器关闭后该文件不存在) |
在表空间中创建了一个临时表,一个普通表,还有一个数据库
-- 创建数据库,指定存储的表空间 create database test tablespace tblspc_test ; -- 在指定表空间创建临时表 create temporary table tmptbl_test1(id int, c_id int) tablespace tblspc_test; -- 在指定表空间创建普通表 create table tbl_salary(id integer, level float, performance float) tablespace tblspc_test;
查看表空间目录中文件,目录层次和文件列表如下
[senllang@hatch pgdata]$ tree pgtblspc/ pgtblspc/ └── PG_16_202306141 ├── 16389 │ ├── 112 │ ├── 113 │ ├── 1247 │ ├── 1247_fsm │ ├── 1247_vm │ ├── 1249 │ ├── 1249_fsm │ ├── 1249_vm ...... │ ├── 826 │ ├── 827 │ ├── 828 │ ├── pg_filenode.map │ └── PG_VERSION └── 5 ├── 16395 └── t3_16413
可以看到,在表空间的目录下,是一个目录 PG_16_202306141,命名以PG开头,然后是数据库版本,再加创建的日期。
用户定义的表空间都在PGDATA/pg_tblspc目录里面有一个符号连接,它指向物理的表空间目录(就是在CREATE TABLESPACE命令里指定的那个目录)。
这个符号连接是用表空间的 OID 命名的。
在物理表空间目录中有一个名称取决于PostgreSQL服务器版本的子目录,例如PG_16_202306141(使用该子目录的原因是后续版本的数据库可以使用CREATE TABLESPACE指定相同的目录位置而不会造成冲突)。
在这个版本相关的子目录中,为每个在这个表空间里有元素的数据库都有一个子目录, 以数据库的OID命名。该目录里的表和索引遵循文件节点命名模式。
初始化集群后,有两个默认的表空间,pg_default和pg_global,当我们没有指定表空间时,创建的数据库,表等都是存放在pg_default表空间。
pg_default不需要通过pg_tblspc来访问,而是对应于PGDATA/base。类似地,pg_global表空间也不通过pg_tblspc访问,而是对应于PGDATA/global。
对于集簇里的每个数据库,在PGDATA/base里都有一个子目录对应,子目录的名字为该数据库在 pg_database里的 OID。
这个子目录是该数据库文件的缺省位置;特别值得一提的是,该数据库的系统目录存储在此。
pg_relation_filepath()函数显示任何关系的完整路径(相对于PGDATA)。
它可以作为记住上面这么多规则的替代方法。
但是记住该函数只给出关系的主分支的第一个段的名称 — 你也许需要追加一个段号或_fsm、_vm、_init来找到与该关系相关的所有文件。
每个表和索引都存储在独立的文件里。
对于普通表,这些文件以表或索引的filenode号命名,它可以在pg_class.relfilenode中找到。
对于临时表,文件名的形式为tBBB_FFF,其中BBB是创建该文件的后台的后台ID,FFF是文件节点号。
在每种情况下,在主文件(a/k/a 主分支)之外,每个表和索引有一个空闲空间映射,它存储表中可用空闲空间的信息。
空闲空间映射存储在一个文件中,该文件以节点号加上后缀_fsm命名。
表还有一个可见性映射,存储在一个该文件以节点号加上后缀为_vm的文件中,它用于跟踪哪些页面已知含有非死亡元组。
不被日志记录的表和索引,也就是unloged table 还有第三个分支,即初始化分支,它存储在该文件以节点号加上后缀为_init的分支文件中。
初始化分支是一个适当类型的空表或空索引。
当一个不被日志记录的表由于崩溃必须被重置为空时,初始化分支被随着主分支复制,而任何其他分支则被擦除(它们会在需要时自动被重建)。
如果一个表的列中可能存储相当大的项,那么该表就会有个与之相关联的TOAST表,
它用于存储无法保留在在表行中的域值的线外存储。
如果表有TOAST表,该表的pg_class.reltoastrelid链接到它的TOAST表;
临时文件(用于如排序不能放在内存中的数据等操作)被创建在PGDATA/base/pgsql_tmp中,如果临时文件被指定在一个非pg_default表空间中则它们会被创建在该表空间的pgsql_tmp子目录中。临时文件的名称的形式为pgsql_tmpPPP.NNN,其中PPP是其所属后端的PID,而NNN用于区别该后端的不同临时文件。
非常感谢大家的支持,在浏览的同时别忘了留下您宝贵的评论,如果觉得值得鼓励,请点赞,收藏,我会更加努力!
作者邮箱:study@senllang.onaliyun.com
如有错误或者疏漏欢迎指出,互相学习。
注:未经同意,不得转载!