1前言
一直都想写关于Postgresql实现原理方面的文章,由于这是一个工作量巨大的工作,平时还要工作谋生,很难抽出
时间
来写,希望能够坚持下来。Postgresql作为历史最悠久的开源
数据库
管理
系统
,有关它的详细信息可以到
http://www.postgresql.org/
去查阅,这里不再赘述。在MysqL被
Oracle
抓在手中以后,MysqL变得前途未卜,在这种情况下,Postgresql显得越发地重要。
Postgresql作为脱胎于学术研究的数据库管理系统,源代码结构非常地清晰,易于理解,相比之下,MysqL的代码结构就要乱很多(个人之见,MysqL的粉丝不要喷我)。
Postgresql的源代码的规模已经非常地大,
文件
众多。初学者如果直接去看代码,必然如坠五里雾中,难以理解,所以我的文章着重讲述源代码背后的实现原理,而不是详细地叙述每个函数的输入输出以及每一行代码的作用。让读者能从宏观上把握系统结构,循着系统的主线去阅读源代码要容易地多。
这将是一系列的连载文章,闲言少叙,让我们先从Postgresql的进程结构说起。
2 Postgresql进程结构
Postgresql与Oracle类似,是多进程结构的数据库。在Postgresql中主要有postmaster,postgres,vacuum,bgwriter,pgarch,walwriter,pgstat等进程,下面先简单介绍一下每个进程的作用,然后再详细介绍每个进程的实现原理。
(1)postmaster
负责在启动数据库的时候创建共享
内存
并初始化各种内部数据结构,如锁表,数据库缓冲区等,该进程在数据库中只有一个。在数据库启动以后负责监听
用户
请求,创建postgres进程来为用户服务。这一点与Oracle的TNS listener进程类似。
(2)postgres
负责执行用户发出的所有sql语句,该进程在数据库中可能有多个,Oracle中叫shadow process。
(3)vacuum
负责清除数据库中无用的历史数据(已经被删除或更新的记录)。更新优化器的统计信息,确保产生可以接受的查询计划,该进程在数据库可能有多个,Oracle无此种类型进程。
(4)bgwriter
负责将数据缓冲区中已被更新的数据库写入数据库物理数据文件中,Oracle中对应的进程叫DBWR,该进程在数据库中只有一个。
(5)pgarch
负责将系统产生的redo log复制到其他外部存储介质中, Oracle中对应的进程叫Archiver,该进程在数据库中只有一个。
(6)walwriter
负责将系统产生的redo log 写到redo log 文件中(在pg_xlog目录下),Oracle中对应的进程叫LGWR,该进程在数据库中只有一个。
(7)pgstat
负责收集数据库运行中的统计信息,如一个表上面进行了多少次插入与更新操作,该进程在数据库中只有一个,51); font-family:Arial; font-size:14px; line-height:26px">2.1 Postmaster进程
Postmaster进程是Postgresql启动以后创建的第一个进程,所有的其他进程都是由Postmaster创建的。与Postmaster进 程相关的大部分代码都在src/backend/postmaster/postmaster.c文件中。Postmaster进程的入口函数是 PostmasterMain。
Postmaster进程在系统启动时首先进行一些初始化工作,创建共享内存和信号量等数据结构,创建pgstat等后台进程,然后调用 ServerLoop函数进入监听客户端连接请求的状态,ServerLoop函数的代码是一个无限循环的结构,正常的情况下,Postmaster进程 永远都不会退出ServerLoop函数。ServerLoop函数的主要功能是在收到客户端连接请求的情况下,创建一个新的postgres进程来为客 户端服务。
因为所有的其他进程都由Postmaster进程创建,所以它们都是Postmaster的子进程。当一个postgres、bgwriter、 walwriter或vacuum进程因为非正常原因崩溃以后,Postmaster进程要进行一些清理工作,释放它们占用的内部资源,或者重新启动这些 进程。postmaster.c中的reaper函数就是负责执行这个任务的。
2.2 Postgres进程
Postgres进程负责执行客户端发出的所有的sql语句及自定义函数。与Postgres进程相关的代码在src/backend/tcop/postgres.c文件中,Postgres进程的入口函数是PostgresMain。
PostgresMain首先进行一些初始化工作,然后使用语句for (;
进入一个无限循环状态,等待客户端发来命令请求,接受客户端命令,执行客户端命令,将执行结果返回给客户端。
for (;
无限循环体首先调用ReadCommand从客户端读取一条命令,然后根据命令类型,调用相应的处理函数。例如,对于可以直接执行的sql语句(simple query),命令类型的代码是“Q”,处理代码如下:
case 'Q': /* simple query */
{
const char *query_string;
/* Set statement_timestamp() */
SetCurrentStatementStartTimestamp();
query_string = pq_getmsgstring(&input_message);
pq_getmsgend(&input_message);
exec_simple_query(query_string);
send_ready_for_query = true;
语句exec_simple_query(query_string)负责解析sql语句,生成查询计划,执行查询计划,将查询结果返回给客户端。
2.3 Vacuum进程
Postgresql使用的是多版本的并发控制机制,历史数据与当前数据都存放在同一个数据库中,当某些历史数据不再有用时,应当把这些历史数据从数据库 中删除掉(这个过程叫vacuum),让这些数据所占的存储空间能够被重新利用,否则数据库所占的存储空间会越来越大,最后会消耗掉所有的物理存储空间, 导致数据库无法继续正常运行。Vacuum进程负责清除无用的历史数据
Vacuum进程分两种。第一种叫Vacuum控制进程,第二种Vacuum工作进程。自动垃圾Vacuum控制进程在数据库中只有一个,数据库启动以后 这个进程就存在,它睡眠一段时间后,就创建一个或多个Vacuum工作进程来进行垃圾收集(Vacuum控制进程不直接创建Vacuum工作进程,而是通 过postmaster进程创建Vacuum工作进程),然后再进入睡眠状态,不断地重复这个循环,每次睡眠的时间由参数 autovacuum_naptime指定。Vacuum工作进程负责真正的垃圾收集工作,参数autovacuum_max_workers控制能够被 同时创建的Vacuum工作进程的个数的最大值。
与Vacuum控制进程相关的大部分代码都在backend/postmaster/autovacuum.c文件中,Vacuum控制进程的入口函数是 AutoVacLauncherMain。AutoVacLauncherMain首先进行一些初始化工作,然后使用语句for (;
进入一个无限循环状态,睡眠一端时间,检查是否需要创建新的Vacuum工作进程,如果需要,通知Postmaser创建新的Vacuum工作进程,然后继续睡眠,不断地重复这个过程。