作者
施博文,PostgreSQL 爱好者,目前就职于腾讯
SELECT * FROM t;
时,查询结束后会将元组一下子打印到屏幕上。那么就会有一个问题,当元组数量非常多的时候,为什么 SELECT 语句卡一会后,会一下子打印出来?
背景介绍

psql 的建立

当在 psql 中输入
SELECT * FROM t;
时,会发生以下几步:
Server 端的执行
当 Server 端接收到 SQL 语句后,会经过词法分析、语法分析、语义分析、生成执行计划,这时候执行器会执行生成的执行计划,得到最终的结果。
ExecutePlan
static void
ExecutePlan()
{
TupleTableSlot *slot;
for (;;)
{
* 调用下层函数获取一条元组,其结果就是一个 slot */
slot = ExecProcNode(planstate);
* 如果 slot 为空,说明所有元组都已经获取完了,跳出死循环 */
if (TupIsNull(slot))
break;
* 标记该元组要发出去,比如发给 psql 客户端 */
if (sendTuples)
{
* 该函数指针会调用 printtup 函数,而 printtup 会把这个 slot
通过 libpq 发送给 psql 客户端
*/
if (!dest->receiveSlot(slot, dest))
break;
}
}
}
复制
EndCommand
函数时,会调用
pq_putmessage('C',...)
标记语句运行完成。然而这时候 psql 并没有显示出结果。
call PqCommMethods->flush()
函数,将这个 message 发过去,但依然没有显示。
ReadyForQuery
时,会发送一个 ‘Z’ 标记,标识这时候 Server 已经可以接受新的语句了,当 psql 收到这个标记后,终于能显示查询结果了。

psql 端的处理
src/bin/psql
文件夹下,其 main 函数位于 startup.c 中。当 Server 端发来元组时, psql 会进行对应的解析处理,并将它们全部存到内存中(这在最后一章会提到),这里我们不做详细讲解。可以理解为,当收到结束标记时,所有的元组均被存到一个数组中。
print_aligned_text
函数就是最终的打印函数。其上层函数为其增加方格线、列名称等。

print_aligned_text
函数,其代码逻辑告诉我们是一行一行打印出来的。当已经 fpuc 出一些字符后,我们手动调用
call fflush(fout)
,强制刷出缓冲区,其显示结果如下图所示。

:
出现了,当前屏幕无法显示全部元组。因此疯狂按回车,直到显示不了为止。

题外话
SELECT * FROM t;
会发生什么现象?
SELECT pg_size_pretty(pg_relation_size('table_name')); --查看表 t 的大小
SELECT * FROM t;
复制

总结
本文主要研究了 PostgreSQL 中运行 Select 语句后元组是如何出现的,也算是解决了困惑我很久的一个问题。当 psql 终端开始显示元组时,说明所有的元组都已经被扫描完毕存在内存里。这时候所要做的只是把它们打印到屏幕上,而由于IO 缓冲区的存在,它们也是一段一段显示出来的,只不过显示的太快了肉眼分辨不出来而已。
PostgreSQL中文社区欢迎广大技术人员投稿
投稿邮箱:press@postgres.cn
最后修改时间:2021-09-15 09:11:08
文章转载自PostgreSQL中文社区,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。