上篇博客简要的介绍了下psql命令行客户端的前台代码。这一次,咱们来看看后台的代码吧。node
十分很差意思的是,上篇博客咱们只说明了前台登录的代码,没有介绍前台登录过程当中,后台是如何工做的。即:后台接到前台的链接请求后发生了什么?调用了哪些函数?启动了哪些进程?linux
那么,咱们就先讲讲后台的工做流程吧。sql
这里首先咱们要知道postgresql是典型的“Server/Client”的模式。即服务器后台有一个主进程(postmaster),该进程根据客户端的链接请求,fork一个服务端进程(postgres)为之服务。数据库
具体来讲,postmaster监听着一个特定的 TCP/IP 端口等待进来的链接。每当检测到一个链接请求时,postmaster进程派生出一个新的叫postgres的服务器进程。服务器任务(postgres进程)相互之间使用信号量和共享内存进行通信, 以确保在并行的数据访问过程当中的数据完整性。数组
前台程序发出一个启动命令后到Postmaster后,Postmaster根据其提供的信息创建一个子进程,也就是后台进程,专门为前台服务。Postmaster负责维护后台进程的生命周期,但与后台进程相独立。这样在后台进程崩溃后能够重启动后台进程而不会和这些后台进程一块儿崩溃。服务器
落实到代码里呢?数据结构
咱们首先看看\src\backend\main下的main.c文件。咱们说过每一个程序都有个“main”函数,以前也说明了psql里的main函数。后台的main函数就定义在main.c文件里。app
在这个main函数里主要作了什么?我写在下面:ide
line99: 函数MemoryContextInit()启动必须的子系统error和memory管理系统; line110:函数set_pglocale_pgservice()获取并设置环境变量; line146~148: 函数init_locale初始化环境变量; line219~228:根据输入参数肯定程序走向,这里进入了PostmasterMain(){跳转至postmaster.c文件中}
这里咱们能够看到,main函数只是作了一些初始化的工做,它随后就把传进来的参数原封不动的传给了PostmasterMain(argc, argv)函数。那么继续进入PostmasterMain函数,他在src/backend/postmaster/postmaster.c中。函数
在函数postmaster中又作了哪些事?
根据命令行参数设定相应的环境值,初始化监听端口,检查其维护的数据库文件是否存在,设置signal handlers从操做系统上监听其感兴趣的消息; 调用StartupDataBase()启动后台子进程; 调用ServerLoop()监听新的创建链接消息。
ServerLoop()是一个死循环,当有一个新的创建链接消息到来的时候,查找自身维护的端口列表,看是否有空闭的端口,若是有调用static int BackendStartup(Port *port)来fork一个后台进程。而后,ServerLoop()判断下列几个后台支持进程的状态(linux上你能够用ps命令查看,Windows的话就职务管理器咯):
system logger process autovacuum process background writer process the archiver process the stats collector process
当发现一个或多个进程发现崩溃后,从新启动它们,确保数据库总体的正常运行。Postmaster在循环中就这样一直不停的监听联系请求和维护后台支持进程。
以上的这些步骤都是在启动数据库服务器的时候完成的(postmaster命令、postgres命令或者pg_ctl命令),即在你运行psql以前。当服务器作好上面的准备后,才能够接受前台的链接请求。
在监听并接受了一个前台的链接请求后,postmaster调用BackendStartup(Port *port)来fork一个后台进程。在该函数里:
最后在src/backend/tcop/postgres.c的PostgresMain()函数里,设置好环境变量和内存上下文,在3933行的for循环处循环检查前台的输入并利用函数ReadCommand(StringInfo inBuf)读取前台命令。
根据读取的命令字符串的首字符的不一样,可分为如下几种命令:
至此,后台服务进程正式开始工做。
当postgresql的后台服务进程postgres收到前台发来的查询语句后,首先将其传递到查询分析模块,进行词法分析,语法分析和语义分析。如果功能性命令(例如create table,create user和backup命令等)则将其分配到功能性命令处理模块;对于查询处理命令(SELECT/INSERT/DELETE/UPDATE)则为其构建查询语法树,交给查询重写模块。
总的来讲流程以下:
SQL命令 --(词法和语法分析)--> 分析树 --(语义分析)--> 查询树
在代码里的调用路径以下(方框内为函数,数字显示了调用顺序):
所以,查询分析的处理过程以下:
postgre命令的词法分析和语法分析是由Unix工具Yacc和Lex制做的。它们依赖的文件定义在src\backend\parser下的scan.l和gram.y。其中:
在raw_parser函数(在src/backend/parser/parser.c下)中,主要经过调用Lex和Yacc配合生成的base_yyparse函数来实现词法分析和语法分析的工做。
其它重要的文件以下:
kwlookup.c:提供ScanKeywordLookup函数,该函数判断输入的字符串是不是关键字,如果则返回单词表中对应单词的指针;
scanup.c:提供几个词法分析时经常使用的函数。scanstr函数处理转义字符,downcase_truncate_identifier函数将大写英文字符转换为小写字符,truncate_identifier函数截断超过最大标识符长度的标识符,scanner_isspace函数判断输入字符是否为空白字符。
scan.l:定义词法结构,编译生成scan.c;
gram.y:定义语法结构,编译生成gram.c;
gram.h:定义关键字的数值编号。
值得一提的是,若是你想修改postgresql的语法,你要关注下两个文件“gram.y”和“kwlist.h”。简要地说就是将新添加的关键字添加到kwlist.h中,同时修改gram.y文件中的语法规则,而后从新编译便可。具体能够看下这篇博客如何修改Postgres的语法规则文件---gram.y
至于文件间的调用关系?我仍是上个图吧:
至于词法分析和语法分析实现的细节,这应该是编译原理课程上学习的东西,这里先就不提了,之后有时间好好学习下Lex和Yacc的语法好了,到时候再写点东西与你们共享。
语义分析阶段会检查命令中是否有不符合语义规则的成分。主要做用是为了检查命令是否能够正确的执行。
exec_simple_query函数在从词法和语法分析模块获取了parsetree_list以后,会对其中的每一颗子树调用pg_analyze_and_rewrite进行语义分析和查询重写。其中负责语义分析的模块是在src/backend/parser/analyze.c中的parse_analyze函数。该函数会根据获得的分析树生成一个对应的查询树。而后查询重写模块会对这颗查询树进行修正,这就是查询重写的任务了,而这并非这篇博客的重点,放在下一篇博客里再说好了。
在parse_analyze函数里,会首先生成一个ParseState类型的变量记录语义分析的状态,而后调用transformTopLevelStmt函数处理语义分析。transformTopLevelStmt是处理语义分析的主过程,它自己只执行把'SELECT ... INTO'语句转换成'CREATE TABLE AS'的任务,剩下的语义分析和生成查询树的任务交给transformStmt函数去处理。
在transformStmt函数里,会先调用nodeTag函数获取传进来的语法树(praseTree)的NodeTag。有关NodeTag的定义在src/include/nodes/nodes.h中。postgresql使用NodeTag封装了大多数的数据结构,把它们封装成节点这一统一的形式,每种节点类型做为一个枚举类型。那么只要读取节点的NodeTag就能够知道节点的类型信息。
所以,随后在transformStmt函数中的switch语句里根据NodeTag区分不一样的命令类型,从而进行不一样的处理。在这里共有8种不一样的命令类型:
SELECT INSERT DELETE UPDATE //增 删 改 查 DeclareCursor //定义游标 Explain //显示查询的执行计划 CreateTableAs //建表、视图等命令 UTILITY //其它命令
对应这8种命令的NodeTag值和语义分析函数以下:
NodeTag值 语义分析函数 T_InsertStmt transformInsertStmt T_DeleteStmt transformDeleteStmt T_UpdateStmt transformUpdateStmt T_SelectStmt ( transformValuesClause 或者 transformSelectStmt 或者 transformSetOperationStmt ) T_DeclareCursorStmt transformDeclareCursorStmt T_ExplainStmt transformExplainStmt T_CreateTableAsStmt transformCreateTableAsStmt default 做为Unility类型处理,直接在分析树上封装一个Query节点返回
程序就根据这8种不一样的命令类型,指派不一样的语义分析函数去执行语义分析,生成一个查询树。
那在这里就以SELECT语句的transformSelectStmt函数为例看看语义分析函数的流程吧:
这样之后咱们就获得了一个查询命令的查询树Query。
其实写到这里原本还想继续分析transformWithClause这些解析各类子句的函数,后来想一想这样篇幅也太多了,并且未免也太细了,也留给各位朋友们一块儿讨论吧。
最后,咱们再来看看生成的Query节点的结构(定义在src/include/nodes/parsenodes.h)吧。这里贴一下代码了,上次都由于贴的代码太多被博客园团队踢出首页了,此次求放过。
typedef struct Query { NodeTag type; CmdType commandType; /* select|insert|update|delete|utility */ QuerySource querySource; /* where did I come from? */ uint32 queryId; /* query identifier (can be set by plugins) */ bool canSetTag; /* do I set the command result tag? */ Node *utilityStmt; /* non-null if this is DECLARE CURSOR or a non-optimizable statement */ int resultRelation; /* rtable index of target relation for INSERT/UPDATE/DELETE; 0 for SELECT */ bool hasAggs; /* has aggregates in tlist or havingQual */ bool hasWindowFuncs; /* has window functions in tlist */ bool hasSubLinks; /* has subquery SubLink */ bool hasDistinctOn; /* distinctClause is from DISTINCT ON */ bool hasRecursive; /* WITH RECURSIVE was specified */ bool hasModifyingCTE; /* has INSERT/UPDATE/DELETE in WITH */ bool hasForUpdate; /* FOR [KEY] UPDATE/SHARE was specified */ bool hasRowSecurity; /* row security applied? */ List *cteList; /* WITH list (of CommonTableExpr's) */ List *rtable; /* list of range table entries */ FromExpr *jointree; /* table join tree (FROM and WHERE clauses) */ List *targetList; /* target list (of TargetEntry) */ OnConflictExpr *onConflict; /* ON CONFLICT DO [NOTHING | UPDATE] */ List *returningList; /* return-values list (of TargetEntry) */ List *groupClause; /* a list of SortGroupClause's */ List *groupingSets; /* a list of GroupingSet's if present */ Node *havingQual; /* qualifications applied to groups */ List *windowClause; /* a list of WindowClause's */ List *distinctClause; /* a list of SortGroupClause's */ List *sortClause; /* a list of SortGroupClause's */ Node *limitOffset; /* # of result tuples to skip (int8 expr) */ Node *limitCount; /* # of result tuples to return (int8 expr) */ List *rowMarks; /* a list of RowMarkClause's */ Node *setOperations; /* set-operation tree if this is top level of a UNION/INTERSECT/EXCEPT query */ List *constraintDeps; /* a list of pg_constraint OIDs that the query depends on to be semantically valid */ List *withCheckOptions; /* a list of WithCheckOption's, which are * only added during rewrite and therefore * are not written out as part of Query. */ } Query;
值得关注的是commandType,rtable,resultRelation,jointree和targetList这几个变量,看懂这几个变量也就比较好懂这个数据结构了。你们看看英文也就懂了,我也很少说了。
好吧,水了这么多,这篇算是告一段落了,本身对查询处理这一块也有个粗浅的认识了。这里要感谢《PostgreSQL数据库内核分析》这本书,虽然基于的是8.x的版本,可是对于我理解新版本也颇有帮助。感谢图书的做者的无私奉献。
下一篇准备继续未完的事业,读一读postgresql的查询重写(rewrite)模块的代码,你们下期见吧。
最后一句,但愿本身能坚持下去,加油。