m6米乐安卓版下载-米乐app官网下载
暂无图片
11

postgres 之一条sql的奇幻历程 -m6米乐安卓版下载

原创 大表哥 2023-01-16
1307

image.png

大家好, 今天给大家分享的是 postgres 之一条sql的奇幻历程。

简简单单的说就是在客户端连上了pg实例之后,从执行一条sql 按下键盘上的回车键到客户端返回给你结果,这之间到底发生了什么?
本文是从源码的角度来debug 跟踪一下sql执行的整体的流程。本人非c语言的开发者,难免有不当之处,望请各位大佬专家指点更正!

我们的实验环境是 linux pg 13.8 源代码 调试工具 clion:

为了debug 方便,我们直接将 clion 安装在了 linux 的server 上,并且这台server 源码方式安装了 postgres 13.8的版本:

在clion 中的debug 窗口中,启动 postgres 实例, 传入的参数是 数据文件的data 路径:

image.png

debug 方式启动之后, 我们可以看到在输出 log 命令台上,像是 后台主进程 postgres 启动成功:

image.png

pg 数据库是一款多进程的程序在后台运行, 所以我们在debug的时候,需要 debug pg 主程序 master 进程 fork 出来的子进程:

我们可以看到 主进程号是 104804

image.png

主进程和子进程的树形图: 这些子进程就是我们熟悉 pg启动后的后台 backgroud 进程 :
checkpointer,
background writer,
walwriter,
autovacuum launcher,
stats collector,
logical replication launcher

infra [postgres@ljzdcmongo004 ~]# pstree -p 104804 postgres(104804)─┬─postgres(104808) ├─postgres(104809) ├─postgres(104810) ├─postgres(104811) ├─postgres(104812) └─postgres(104813)

主进程 对应的源码文件函数是 postgresmain()(路径在 src/postmaster/postmaster.c)

/* * postmaster main entry point */ void postmastermain(int argc, char *argv[]) { int opt; int status; char *userdoption = null; bool listen_addr_saved = false; int i; char *output_config_variable = null; initprocessglobals(); postmasterpid = myprocpid; ispostmasterenvironment = true; ...

子进程初始化启动的代码 : postmastermain() 函数中

/* * remove old temporary files. at this point there can be no other * postgres processes running in this directory, so this should be safe. */ removepgtempfiles(); /* * initialize stats collection subsystem (this does not start the * collector process!) */ pgstat_init(); /* * initialize the autovacuum subsystem (again, no process start yet) */ autovac_init(); /* * if enabled, start up syslogger collection subprocess */ sysloggerpid = syslogger_start(); /* * we're ready to rock and roll... */ startuppid = startupdatabase(); assert(startuppid != 0); startupstatus = startup_running; pmstate = pm_startup; /* some workers may be scheduled to start now */ maybe_start_bgworkers(); status = serverloop(); /* * serverloop probably shouldn't ever return, but if it does, close down. */ exitpostmaster(status != status_ok); abort();

pg源码的注释还是很有意思: 当数据库基本上所有的进程启动成功后, 注释写到: we’re ready to rock and roll… 我们准备好摇滚了!!!

removepgtempfiles() – 删除一些临时文件
pgstat_init(); – fork 收集统计信息的子进程
autovac_init(); – fork 出 auto vacumm 的子进程
sysloggerpid = syslogger_start(); – fork 出收集日志信息的子进程
maybe_start_bgworkers(); --fork 出辅助的后台worker 进程
status = serverloop(); – 这个很重要,是postmaster 的监听程序: 1)监听了所有后台fork 出来的 background process , 如果挂了,直接拉起来 2) 监听了网络过来的连接,并负责fork 出来子进程来处理客户端请求的sql

基本上所有的background process 的源码文件也都在 路径 src/postmaster/ 文件夹下面: 有c语言的基础的同学可以研究一下。

image.png

我们尝试用psql 在客户端建立一个连接 ,代码应该是由 serverloop() 这个函数负责 fork 出来一个子进程来处理sql语句的请求:

监听的本质上就是一个 for(;; ) 的死循环,处理来自客户端的socket 请求:

/* * main idle loop of postmaster * * nb: needs to be called with signals blocked */ static int serverloop(void) { fd_set readmask; int nsockets; time_t last_lockfile_recheck_time, last_touch_time; last_lockfile_recheck_time = last_touch_time = time(null); nsockets = initmasks(&readmask); for (;;) { fd_set rmask; int selres; time_t now; /* * wait for a connection request to arrive. * * we block all signals except while sleeping. that makes it safe for * signal handlers, which again block all signals while executing, to * do nontrivial work. * * if we are in pm_wait_dead_end state, then we don't want to accept * any new connections, so we don't call select(), and just sleep. */ ... ... } }

我们现在用psql 在 linux server 上建议一个连接, 并在函数中serverloop()中的 backendstartup(port) 打上断点:

backendstartup(port) 这个函数负责 fork 出子进程处理 pid = fork_process();

image.png

客户端尝试登陆:

/u01/postgres/pg13/bin/psql -h /tmp -p 5432

我们可以看到 fork 出来的 子进程 是 pid : 122846

image.png

我们去pg 的实例上验证一下: 查询 pg_backend_pid()

postgres=# select pg_backend_pid(); pg_backend_pid ---------------- 122846 (1 row)

我们观察linux 操作系统进程树上 多个子进程 122846

image.png

接下来,我们需要 attach 这个子进程122846 进行 debug, 看看我们执行一条sql后台是如何处理的?

我们的开发工具colin 对于 attached fork的进程操作很简单: run-> attach to process…

image.png

接下来我们在处理sql的入口打上断点:/src/tcop/postgres.c 中的函数 postgresmain

官方的函数注释很规范,果然是学院派的代表典范!!!

/* ---------------------------------------------------------------- * postgresmain * postgres main loop -- all backends, interactive or otherwise start here * * argc/argv are the command line arguments to be used. (when being forked * by the postmaster, these are not the original argv array of the process.) * dbname is the name of the database to connect to, or null if the database * name should be extracted from the command line arguments or defaulted. * username is the postgresql user name to be used for the session. * ---------------------------------------------------------------- */ void postgresmain(int argc, char *argv[], const char *dbname, const char *username)

具体的断点的打到 firstchar = readcommand(&input_message); 这行上, 我们看一下接收到的sql 语句:

我们执行sql:

image.png

dehug 到源代码中:

image.png

接下来,我们可以一步一步的debug : 进入到重要的函数入口 exec_simple_query(query_string);

image.png

进入函数 exec_simple_query(query_string) 后, 我们来到了关键的第一个步骤, ast 语法解析树:

image.png

接着一路debug 下一步到另外2个关键的函数:

pg_analyze_and_rewrite 函数尝试重写 sql
pg_plan_queries 函数得到sql的执行计划

image.png

在sql语句经过了 pg_analyze_and_rewrite 和 pg_plan_queries 2个阶段后, 进入实际执行的阶段
入口的函数是 /src/tcop/postgres.c 中的函数 portalrun

image.png

接下来会调用执行器 executorrun --> 根据执行计划运行正确的执行器 execseqscan (本sql为全表扫描的执行器):

具体调用的图示:

image.png

返回的结果集 tuple -》 slot = execprocnode(planstate);

image.png

最后把返回的结果集slot,发送到我们的psql执行的客户端:

image.png

客户端返回结果集:

image.png

最后我们来总结一下:

sql执行和源码中函数的简单对照关系的流程:

顺序步骤描述 调用函数以及源码文件
建立客户端连接,fork出处理sql请求的子进程 serverloop()–> backendstartup()
源码文件: src/backend/postmaster/postmaster.c
进入处理客户端发送sql语句的总入口 postgresmain()–>readcommand()–>exec_simple_query()
源码文件: src/backend/tpoc/postgres.c
ast 语法树解析成 node postgresmain()–>exec_simple_query()
raw_parsetree_list = raw_parser(query_string)
parsetree_list =pg_parse_query(query_string);
源码文件: src/backend/tpoc/postgres.c
sql 进行重写优化 postgresmain()–>exec_simple_query()
querytree_list = pg_analyze_and_rewrite(parsetree, query_string,null, 0, null);
源码文件: src/backend/tpoc/postgres.c
sql 生成执行计划树 postgresmain()–>exec_simple_query()
plantree_list = pg_plan_queries(querytree_list, query_string,cursor_opt_parallel_ok, null);
源码文件: src/backend/tpoc/postgres.c
启动portal ,执行sql语句 postgresmain()–>exec_simple_query()–>
(void) portalrun()–> portalrunselect() --> executorrun()–>standard_executorrun()–>executeplan() -->execprocnode()–>execseqscan()
源码文件:
src/backend/tpoc/postgres.c
src/backend/tpoc/pgquery.c
src/backend/executor/execmain.c
src/backend/executor/nodeseqscan.c
查询sql的结果返回给客户端 postgresmain()–>exec_simple_query()–>
dest->receiveslot(slot, dest)
源码文件:
src/backend/tpoc/postgres.c
src/backend/executor/execmain.c

have a fun 😃 !

最后修改时间:2023-01-16 18:51:04
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【米乐app官网下载的版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论

网站地图