pgsql中的hook

   为了支持用户对数据库的扩展,目前的PGSQL提供了很多hook来满足用户的需求。
   一般说来,数据库提供了多种扩展机制,例如大家最常见的PLSQL,用户使用SQL来开发存储过程,为了更高的效率和更大的灵活性,也可以使用C语言来开发。为了支持用户更深入的扩展数据库,开源的数据库提供了更加灵活的方式。
   说到对数据库的扩展,就不可避免要谈到数据库内部的运行机制,因为数据库的扩展是和内部运行细节密切相关的。目前的关系数据库,简单来说,处理一个SQL基本上就是一个三段论:词法语法分析,查询优化,和执行查询。
   对于词法语法分析来说,目前的开源数据库普遍借助了开源的词法语法分析工具yacc或是bision,例如PG中语法脚本是sr/backend/parser/gram.y,这个脚本用左递归文法描述了所支持的SQL,在编译的时候,这个脚本会被yacc作为输入来生成词法分析的程序。这个程序主要的作用是讲输入的一个查询处理之后输出一个词法树,具体的词法树取决于输入的SQL,例如如果输入是一个查询,那么输出就是一个SelectStmt,这个数据结构的具体定义见源码。在生成SelectStmt之后,会被转化成一个Query的数据结构,这个是查询优化器的输入。在转化成Query之后,并没有结束词法语法分析,还需要一个查询改写的过程。PG中的查询改写,其实分的不是特别清晰,PG中的查询改写,一部分在优化器之前,一部分在优化器内。在获得了Query但是优化器之前, PG会对基于view的查询进行改写,简单来说,假设一个之前建立了view:create view v1 as select * from t1 where C1 = a,当前的查询是 select * from v1 where c2 = b, 那么词汇此刻,这个查询在语义上会被改写成 select * from (select * from t1 where c1 = a ) where c2 =b 注意,这里说的是语义上,实际的改写细节比较简单,就是讲Query中rangetable使用一个子查询来代替,这个子查询其实是一个Query数据结构,代表了当前的view v1.
    在查询优化阶段,planner会为优化做一些改写,例如最明显的就是将subquery和sublink提上来,直接用join来替代,另外还有就是将一些谓词进行正规化,消除重复代码和获得常量等。在完成了这些查询改写之后,planner会先首先为join操作搜索最佳的join顺序,主要是依据代价模型来获取开销最小的顺序,一般会采用动态规划算法,当然可以通过配置来使用遗传算法。在获得了最佳的join顺序之后,PG会为这个顺序生成最下层也是最基础的plan节点,这个基础的plan会告诉executor如何去扫描表。接着,以此为基础,planner生成agg节点,agg节点主要告诉executor如何做聚集操作,注意,agg节点也会告诉executor如何做过滤操作,这些过滤操作不是简单的对列做过滤,而是对涉及到聚集结果的操作,例如having语句。在agg节点基础上,planner一次生成distinct、sort、limit节点。最终得到一棵左深数状的plan。
    在执行查询阶段,也是分三步走的:第一步,准备工作,将查询计划中的每个node生成一个对应的PlanState,为每个操作准备好所需要的资源,最终会以递归的方式生成一个左深树状的PlanState;第二步,执行PlanState,执行的方式就是经典教科书中所描述的迭代器模型;第三步,后续清理工作,释放资源。
    在粗略描述了PG内部的运行过程之后,我们回到hook这个话题上面来。PG hook目前的使用还不是特别活跃,虽然最近几年的PGCon有相应的ppt。个人觉得主要原因是:一来没有相应的文档介绍,在PG的手册中没有提到,只能从内部的代码中看到;二来使用的门槛比较高,很少有项目需要用到,除非有深度定制的需求,例如定制查询优化器和executor。
    简单来说,这些hook可以PG预先定义的一些接口,用户实现了这些接口之后,将程序封装到动态库中,用户将hook的动态库部署到PG可以访问的目录中。PG加载hook有两种方式:一种是在PG启动的时候直接加载,这就需要在配置文件中制定;一种就是手动加载,每次连接PG之后,使用load命令来加载。一旦PG加载了hook之后,每次运行到相应的代码段,发现某个hook存在,那么PG就会直接去调用这个hook。
    前面提到PG的运行阶段可以分为词法语法分析、查询优化、查询执行这三个阶段。对于这三个阶段,PG中都预先定义了hook来满足用户的深度定制需求。
    在词法语法分析阶段,PG申明了一个hook (src/backend/parser/analyze.c):
     post_parse_analyze_hook_type post_parse_analyze_hook = NULL;
     post_parse_analyze_hook_type申明是:
     typedef void (*post_parse_analyze_hook_type) (ParseState *pstate,Query *query);
     这个hook是词法语法分析完成、得到Query之后被调用的,用户可以通过这个hook来改写Query。
    在查询优化阶段,PG申明的hook比较多。首当其冲的就是planner_hook,申明在src/backend/optimizer/plan/planner.c中,类型定义是:
   typedef PlannedStmt *(*planner_hook_type) (Query *parse,int cursorOptions,  ParamListInfo boundParams);
   如果你对PG代码很熟悉的话,会发现这就是查询优化器函数的原型。对的,hacker可以通过这个hook来生成查询计划。这个是个非常重量级的hook,如果不是特别有需要,还是别动它的主意,后果自负,O(∩_∩)O~。
   前面提到PG一般默认使用动态规划来找最佳的join顺序,这个是PG内置的算法。当然如果为了发paper,出于实验的目的,为了实验下新的算法来找最佳join顺序,你可以考虑实现这个hook——join_search_hook,它的类型定义是:
   typedef RelOptInfo *(*join_search_hook_type) (PlannerInfo *root, int levels_needed,  List *initial_rels);
   这个hook被调用的堆栈 比较深,不是很好找,位于 src/backend/optimizer/path/allpath.c 的make_rel_from_joinlist方法中。
   当然,在查询优化阶段,还有其他的hook,这里就不说了,大家可以翻代码。
   在查询执行阶段,PG中提供了一些重量级的hook来帮助大家定制executor的行为:
   ExecutorStart_hook_type ExecutorStart_hook = NULL;
   ExecutorRun_hook_type ExecutorRun_hook = NULL;
   ExecutorFinish_hook_type ExecutorFinish_hook = NULL;
   ExecutorEnd_hook_type ExecutorEnd_hook = NULL;
   这些hook为用户控制executor的初始化、执行、完成、结束提供了接口。
   当然,除了上面提到的这些处理查询的hook之外,还有一些比较重要的hook,例如:check_password_hook和ClientAuthentication_hook,具体的申明定义可以看代码。
  事实上,PG从若干年之前就开始提供hook了,最早可以追溯到2008年的8.3版本了,这些hook使得用户可以专注于数据库的后端的查询优化和执行,为用户深度定制PG提供了基础。
   
    
   
   

猜你喜欢

转载自scarbrofair.iteye.com/blog/2120185