客户端可以通过两种 "子协议" 来发送请求,分别是 simple query
和 extened query
。使用 simple query
时,客户端发送字符串文本请求,后端收到后立即处理并返回结果(gsql);使用 extened query
时,发送请求的过程被分为若干步骤,通常包括 Parse,Bind , Execute和Sync。本文主要关注 extened query
。
客户端和服务端所有通信都通过消息流进行。消息的第一个字节标识消息类型,随后四个字节标识消息内容的长度(该长度包括这四个字节本身),具体的消息内容由消息类型决定。
openGauss 目前支持如下客户端消息类型:
c++switch (qtype) {
case 'a': /* Not reach : Trigger shipped to DN */
case 'A': /* AC WLM */
case 'b': /* Barrier */
case 'B': /* bind */
case 'c': /* copy done */
case 'C': /* close */
case 'D': /* describe */
case 'd': /* copy data */
case 'e': /* Thread ID */
case 'E': /* execute */
case 'f': /* copy fail */
case 'F': /* fastpath function call */
case 'g': /* GXID */
case 'G': /* PGXCBucketMap and PGXCNodeId */
case 'h': /* hybrid message query */
case 'H': /* flush */
case 'i': /* Instrumentation */
case 'I': /* Push, Pop schema name */
case 'j': /* Check gtm mode */
case 'J': /* Trace ID */
case 'k': /* Global session ID */
case 'K': /* client conn driver net_time */
case 'l': /* get and handle csn for csnminsync */
case 'L': /* Link gc_fdw */
case 'M': /* Command ID */
case 'n': /* Committing */
case 'N': /* Commit csn */
case 'o': /* role name */
case 'O': /* to reset openGauss thread in pooler stateless reuse mode */
case 'p': /* Process Pid */
case 'P': /* parse */
case 'q': /* Query ID */
case 'Q': /* simple query */
case 'r': /* Plan ID with sync */
case 'R': /* Reply collect info */
case 's': /* Snapshot */
case 'S': /* sync */
case 't': /* Timestamp */
case 'T': /* consistency point */
case 'u': /* AUTONOMOUS_TRANSACTION simple query */
case 'U': /* batch bind-execute */
case 'V': /* client conn driver support trace info*/
case 'W': /* WLM Control Group */
case 'w': /* dynamic WLM */
case 'x': /* imcs populate query */
case 'X': /* terminate */
case 'y': /* sequence from cn 2 dn */
case 'Y': /* plan with params */
case 'z': /* PBE for DDL */
case 'Z': /* simple plan */
default:
break;
}
服务端收到如上消息的处理流程可以参考 PostgresMain。服务端发送给客户端的消息有如下类型(不完全):
javaswitch (c) {
case 'K': //receive dbTime
case 'A': // Asynchronous Notify
case '1': // Parse Complete (response to Parse)
case 't': // ParameterDescription
case '2': // Bind Complete (response to Bind)
case '3': // Close Complete (response to Close)
case 'n': // No Data (response to Describe)
case 's': // Portal Suspended (end of Execute)
case 'C': // Command Status (end of Execute)
case 'D': // Data Transfer (ongoing Execute response)
case 'E': // Error Response (response to pretty much everything; backend then skips until Sync)
case 'I': // Empty Query (end of Execute)
case 'N': // Notice Response
case 'S': // Parameter Status
case 'Z': // Ready For Query (eventual response to Sync)
case 'G': // CopyInResponse
case 'H': // CopyOutResponse
case 'c': // CopyDone
case 'd': // CopyData
default:
throw new IOException("Unexpected packet type: " + c);
}
客户端处理如上服务端消息的流程可以参考 PostgreSQL libqp 的实现 pqParseInput3。
openGauss 通信协议包括两个阶段: startup
阶段和常规 normal
阶段。 startup
阶段,客户端尝试创建连接并发送授权信息,如果一切正常,服务端会反馈状态信息,连接成功创建,随后进入 normal
阶段。 normal
阶段,客户端发送请求至服务端,服务端执行命令并将结果返回给客户端。客户端请求结束后,可以主动发送消息断开连接。
normal
阶段,客户端可以通过两种 "子协议" 来发送请求,分别是 simple query
和 extened query
。使用 simple query
时,客户端发送字符串文本请求,后端收到后立即处理并返回结果(gsql);使用 extened query
时,发送请求的过程被分为若干步骤,通常包括 Parse,Bind , Execute和Sync。
在 openGauss 中,Extended Query(扩展查询,JDBC默认协议) 是一种通过多个步骤拆分 SQL 执行过程的协议,主要用于参数化查询,能有效避免 SQL 注入、提升重复执行效率。Extended Query 不允许在一个请求中包含多条 SQL 命令,否则会报语法错误 ( Extended Query模式下JDBC会自动拆分一条语句的多个SQL) 。Extended Query 协议通常包括 5 个步骤,分别是 Parse,Bind,Describe(可选),Execute 和 Sync。以下分别介绍各个阶段的处理流程。
SELECT * FROM users WHERE id = $1
),服务端收到该消息后,调用 exec_parse_message
函数进行处理,进行语法分析、语义分析和重写,同时会创建一个 Plan Cache 的结构,用于缓存后续的执行计划。Portal
或Statement
ID)。Parse("SELECT * FROM users WHERE id = $1")
,服务器返回解析成功的确认。$1 = 123
),服务器将参数与解析树绑定,生成执行计划(Execution Plan)。openGauss 收到该消息后,调用 exec_bind_message
函数进行处理。为之前保存的 Prepared Statement 创建执行计划并将其保存在 Plan Cache 中,创建一个 Portal
用于后续执行。在 openGauss 内核中,Portal 是对查询执行状态的一种抽象,该结构贯穿执行器运行的始终。
id
是整数,$1
不能是字符串)。Bind Complete
消息。Bind(参数: [123])
,服务器将123
与$1
绑定,生成针对id=123
的执行计划。Execute
消息中可以指定返回的行数,若行数为 0,表示返回所有行。Fetch
控制返回行数)。INSERT
),执行事务日志记录和数据页更新。Execute(Portal ID)
,服务端收到消息后,执行 Bind
阶段创建的 Portal,执行结果通过 DataRow
消息返回给客户端,执行完成后发送 CommandComplete
消息。Sync
消息,服务器清理本次查询的资源,结束当前会话的查询流程。ReadyForQuery
消息,表明当前会话处于 “就绪状态”,可接收新的查询。Sync
,服务器清理资源,等待下一次查询请求。对于一次执行的多条SQL语句,JDBC会自动将语句拆分成多个单独的SQL语句,每条SQL语句都独立执行Parse/Bind/Describe/Execute流程
javaString sql = "INSERT INTO users (name, age) VALUES ('Alice', 30);select * from users where age=?;select * from users;select * from users;select * from users;select * from users;";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, 30);
pstmt.execute();
本文作者:司小远
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!