PostgreSQL数据库网络层——libpq 查询协议PGQueryClass

PGQueryClass tracks which query protocol we are now executing

typedef enum
{
	PGQUERY_SIMPLE,				/* simple Query protocol (PQexec) */
	PGQUERY_EXTENDED,			/* full Extended protocol (PQexecParams) */
	PGQUERY_PREPARE,			/* Parse only (PQprepare) */
	PGQUERY_DESCRIBE,			/* Describe Statement or Portal */
	PGQUERY_SYNC				/* Sync (at end of a pipeline) */
} PGQueryClass;

PGQUERY_SIMPLE

PGQUERY_SIMPLE简单查询协议就是客户端通过 Query 消息发送一个文本命令给服务端,服务端处理请求,回复查询结果。查询结果通常包括两部分内容:结构和数据。结构通过 RowDescription 消息传递,包括列名、类型 OID 和长度等;数据通过 DataRow 消息传递,每个 DataRow 消息中包含一行数据。
请添加图片描述
简单查询协议的发送代码在PQsendQueryInternal函数中,还是挺简单的,就和上述图片中的请求数据结构一致。最后需要设置entry->queryclass为PGQUERY_SIMPLE,代表是简单查询协议。PQsendQuery Submit a query, but don’t wait for it to finish.

static int PQsendQueryInternal(PGconn *conn, const char *query, bool newQuery) {
	PGcmdQueueEntry *entry = NULL;
	if (!PQsendQueryStart(conn, newQuery)) return 0;
	if (!query) { /* check the argument */
		appendPQExpBufferStr(&conn->errorMessage, libpq_gettext("command string is a null pointer\n"));
		return 0;
	}
	entry = pqAllocCmdQueueEntry(conn);
	if (entry == NULL) return 0;				/* error msg already set */
	
	if (conn->pipelineStatus == PQ_PIPELINE_OFF) { /* Send the query message(s) */
		/* construct the outgoing Query message */
		if (pqPutMsgStart('Q', conn) < 0 || pqPuts(query, conn) < 0 || pqPutMsgEnd(conn) < 0) {			
			pqRecycleCmdQueueEntry(conn, entry); /* error message should be set up already */
			return 0;
		}		
		entry->queryclass = PGQUERY_SIMPLE; /* remember we are using simple query protocol */		
		entry->query = strdup(query); /* and remember the query text too, if possible */
		

PGQUERY_EXTENDED

PGQUERY_EXTENDED Extended Query 协议将以上 Simple Query 的处理流程分为若干步骤,每一步都由单独的服务端消息进行确认。该协议可以使用服务端的 perpared-statement 功能,即先发送一条参数化 SQL,服务端收到 SQL(Statement)之后对其进行解析、重写并保存,这里保存的 Statement 也就是所谓 Prepared-statement,可以被复用;执行 SQL 时,直接获取事先保存的 Prepared-statement 生成计划并执行,避免对同类型 SQL 重复解析和重写;随后,服务端会在适当的条件下缓存计划,以备后续复用。
PostgreSQL的扩展查询协议将一个SQL的执行过程拆分成三个层次,相邻的两个层次间抽象出statement和portal对象,每个层次允许单独重复调用,并且在当前连接的生命周期内,也允许再次调用,使整个SQL的执行过程具有了可重复利用性,对中间结果的保存使得重复调用减少了一些执行开销,提供了性能,对于同一模板的SQL,也提高了执行速度。
在这里插入图片描述
Parse类型的消息,是扩展查询的第一个阶段,与简单查询PGQUERY_SIMPLE类似,其内容中含有SQL的字符串,但是允许SQL字符串中含有参数占位符,同时允许为这个SQL定义一个statement名字,如果没有定义一个statement,那么会生成一个未命名的statement,后端会保留当前连接中所有命名的statement和最后一个未命名的statement,其他未命名的statement都会呗丢弃。其内容中还含有参数占位符数据类型的相关信息。后端接收Parse消息,与Query不同的是,Parse消息并不会使SQL真正执行,因为可能还缺少参数信息,但后端会开始对Parse消息中的SQL进行词法和语法解析,生成对应的结构,等待参数完整后生成对应的计划。
在这里插入图片描述
Bind类型的消息,是扩展查询的第二个阶段,将Parse消息中SQL的参数占位符进行填充,实质成为一个完整的SQL语句,所以需要接收Parse消息中statement的名字,Bind消息将会产生一个portal入口,可以认为这个portal是这个SQL计划的名字,但bind消息并不会使这个计划真正执行,同样一个portal可以是未命名的,Bind消息中其他内容主要是参数占位符相关的信息,补全参数的信息。
在这里插入图片描述
Execute类型的消息,是扩展查询的第三个阶段,将一个SQL语句经过Parse与bind之后就可以真正的执行,Execute消息就是通知后端开始执行,所以Execute需要接收Bind消息产生的Portal名字,其内容中还包含SQL请求的要返回的最大行数,默认为0,表示不限制。
在这里插入图片描述
Close类型的消息,是删除关闭一个之前定义的statement或portal,所以其内容为一个statement名字或portal名字,并且需要指定名字所代表的含义,statement为"S",portal为"P"。未命名的statement或portal无法关闭,他们将持续到连接关闭后自动删除,Close消息提供了对statement或portal重命名的可能性,并且可以让后端删除对应的结构用以释放内存。Close消息在应用时可能并不多见,因为所有基于连接定义的statement和portal都会在连接关闭时自动关闭,往往不需要手动关闭。
在这里插入图片描述
Flush消息会刷新后端数据缓冲区中的数据,使后端不再聚集数据贰拾立刻返回给前端数据缓冲区中的数据,Flush消息必须在任何其他扩展查询消息之后,Sync消息之前发出。
在这里插入图片描述

Extended Query 协议的发送代码在PQsendQueryInternal函数中,PG14在传统批处理模式新增了流水线模式,流水线模式只能在Extended Query 协议下使用,且使用未命名的portal。

static int PQsendQueryInternal(PGconn *conn, const char *query, bool newQuery) {
    ...
	else {
		/* In pipeline mode we cannot use the simple protocol, so we send Parse, Bind, Describe Portal, Execute, Close Portal (with the unnamed portal). */
		if (pqPutMsgStart('P', conn) < 0 || pqPuts("", conn) < 0 || pqPuts(query, conn) < 0 || pqPutInt(0, 2, conn) < 0 || pqPutMsgEnd(conn) < 0)
			goto sendFailed;
		if (pqPutMsgStart('B', conn) < 0 || pqPuts("", conn) < 0 || pqPuts("", conn) < 0 || pqPutInt(0, 2, conn) < 0 || pqPutInt(0, 2, conn) < 0 || pqPutInt(0, 2, conn) < 0 || pqPutMsgEnd(conn) < 0)
			goto sendFailed;
		if (pqPutMsgStart('D', conn) < 0 || pqPutc('P', conn) < 0 || pqPuts("", conn) < 0 || pqPutMsgEnd(conn) < 0)
			goto sendFailed;
		if (pqPutMsgStart('E', conn) < 0 || pqPuts("", conn) < 0 || pqPutInt(0, 4, conn) < 0 || pqPutMsgEnd(conn) < 0)
			goto sendFailed;
		if (pqPutMsgStart('C', conn) < 0 || pqPutc('P', conn) < 0 || pqPuts("", conn) < 0 || pqPutMsgEnd(conn) < 0)
			goto sendFailed;

		entry->queryclass = PGQUERY_EXTENDED;
		entry->query = strdup(query);
	}
	/* Give the data a push.  In nonblock mode, don't complain if we're unable to send it all; PQgetResult() will do any additional flushing needed. */
	if (pqPipelineFlush(conn) < 0)
		goto sendFailed;	

PQsendQueryGuts函数是传统批处理模式下使用extended query协议发送查询的接口,在调用该函数之前PQsendQueryStart必须已经调用了。

static int PQsendQueryGuts(PGconn *conn,const char *command,const char *stmtName,int nParams,const Oid *paramTypes,const char *const *paramValues,const int *paramLengths,const int *paramFormats,int resultFormat) {
	int			i;
	PGcmdQueueEntry *entry;
	entry = pqAllocCmdQueueEntry(conn);
	if (entry == NULL)return 0;				/* error msg already set */
	/* We will send Parse (if needed), Bind, Describe Portal, Execute, Sync (if not in pipeline mode), using specified statement name and the unnamed portal. */
	if (command){
		/* construct the Parse message */
		if (pqPutMsgStart('P', conn) < 0 ||pqPuts(stmtName, conn) < 0 ||pqPuts(command, conn) < 0)
			goto sendFailed;
		if (nParams > 0 && paramTypes){
			if (pqPutInt(nParams, 2, conn) < 0)goto sendFailed;
			for (i = 0; i < nParams; i++){
				if (pqPutInt(paramTypes[i], 4, conn) < 0) goto sendFailed;
			}
		}else{
			if (pqPutInt(0, 2, conn) < 0) goto sendFailed;
		}
		if (pqPutMsgEnd(conn) < 0) goto sendFailed;
	}
	if (pqPutMsgStart('B', conn) < 0 || pqPuts("", conn) < 0 || pqPuts(stmtName, conn) < 0) /* Construct the Bind message */
		goto sendFailed;
	
	if (nParams > 0 && paramFormats){ /* Send parameter formats */
		if (pqPutInt(nParams, 2, conn) < 0) goto sendFailed;
		for (i = 0; i < nParams; i++) {
			if (pqPutInt(paramFormats[i], 2, conn) < 0) goto sendFailed;
		}
	} else {
		if (pqPutInt(0, 2, conn) < 0) goto sendFailed;
	}
	if (pqPutInt(nParams, 2, conn) < 0) goto sendFailed;

	for (i = 0; i < nParams; i++) {
		if (paramValues && paramValues[i]){
			int			nbytes;
			if (paramFormats && paramFormats[i] != 0){				
				if (paramLengths) /* binary parameter */
					nbytes = paramLengths[i];
				else{
					appendPQExpBufferStr(&conn->errorMessage, libpq_gettext("length must be given for binary parameter\n"));
					goto sendFailed;
				}
			}else{ /* text parameter, do not use paramLengths */				
				nbytes = strlen(paramValues[i]);
			}
			if (pqPutInt(nbytes, 4, conn) < 0 || pqPutnchar(paramValues[i], nbytes, conn) < 0)
				goto sendFailed;
		}else{/* take the param as NULL */
			if (pqPutInt(-1, 4, conn) < 0)goto sendFailed;
		}
	}
	if (pqPutInt(1, 2, conn) < 0 || pqPutInt(resultFormat, 2, conn)) goto sendFailed;
	if (pqPutMsgEnd(conn) < 0) goto sendFailed;
	/* construct the Describe Portal message */
	if (pqPutMsgStart('D', conn) < 0 ||pqPutc('P', conn) < 0 ||pqPuts("", conn) < 0 ||pqPutMsgEnd(conn) < 0)
		goto sendFailed;
	/* construct the Execute message */
	if (pqPutMsgStart('E', conn) < 0 ||pqPuts("", conn) < 0 ||pqPutInt(0, 4, conn) < 0 ||pqPutMsgEnd(conn) < 0)
		goto sendFailed;
	/* construct the Sync message if not in pipeline mode */
	if (conn->pipelineStatus == PQ_PIPELINE_OFF){
		if (pqPutMsgStart('S', conn) < 0 ||pqPutMsgEnd(conn) < 0)goto sendFailed;
	}
	/* remember we are using extended query protocol */
	entry->queryclass = PGQUERY_EXTENDED;

PGQUERY_SYNC

PQpipelineSync函数作为管道的一部分发送同步消息,并刷新到服务器(Send a Sync message as part of a pipeline, and flush to server)。立即开始在管道中提交更多命令是合法的,而无需等待当前管道的结果。无需结束流水线模式并重新启动。 如果管道中的命令失败,则每个后续命令(包括 PQpipelineSync 发送的同步消息的结果)都将设置为 PGRES_PIPELINE_ABORTED 状态。 如果整个管道被正确处理,则生成带有 PGRES_PIPELINE_SYNC 的 PGresult。It’s legal to start submitting more commands in the pipeline immediately, without waiting for the results of the current pipeline. There’s no need to end pipeline mode and start it again. If a command in a pipeline fails, every subsequent command up to and including the result to the Sync message sent by PQpipelineSync gets set to PGRES_PIPELINE_ABORTED state. If the whole pipeline is processed without error, a PGresult with PGRES_PIPELINE_SYNC is produced.

在调用 PQpipelineSync 之前可能已经发送了查询,但是在检索命令结果之前需要调用 PQpipelineSync。Queries can already have been sent before PQpipelineSync is called, but PQpipelineSync need to be called before retrieving command results.

连接将保持在流水线模式,并且对于新的同步命令执行功能不可用,直到来自流水线的所有结果都被客户端处理。The connection will remain in pipeline mode and unavailable for new synchronous command execution functions until all results from the pipeline are processed by the client.

Sync消息表示一个扩展查询的结束,由于扩展查询不想简单查询一样由单协议表示,所有对于扩展查询的消息序列,需要有一个明显的结束标志。后端会等到接收到Sync消息后,才会对这次请求中的扩展查询消息进行处理。
在这里插入图片描述

PGQUERY_PREPARE

PQsendPrepare函数提交 Parse 消息,但不要等待它完成,并设置queryclass为PGQUERY_PREPARE(PQsendPrepare Submit a Parse message, but don’t wait for it to finish)。

PGQUERY_DESCRIBE

PQsendDescribe函数是发送描述命令的常用代码(PQsendDescribe Common code to send a Describe command)。
desc_type 的可用选项是Available options for desc_type are

  • ‘S’ 描述一个准备好的语句 ‘S’ to describe a prepared statement
  • ‘P’ 描述门户 ‘P’ to describe a portal.

成功返回 1,失败返回 0。 Returns 1 on success and 0 on failure.

Describe类型的消息,是获取一个statement或portal的描述。当内容为S时是获取statement的描述,为了获取这个statement参数占位符相关的信息,以便得知该填充什么格式的参数。当内容为P时是获取portal的描述,为了获取这个portal返回行的信息,以便得知该以什么样的格式接收应答数据。

在这里插入图片描述
参考
https://stackoverflow.com/questions/65469533/where-is-query-pipelining-in-libpq
https://www.postgresql.org/message-id/flat/CAMsr+YFUjJytRyV4J-16bEoiZyH=4nj+sQ7JP9ajwz=B4dMMZw@mail.gmail.com
原文
https://www.percona.com/blog/how-postgresql-pipeline-mode-works/

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值