返回
Prepare/Execute 请求处理:深入解析 TiDB 的源码奥秘
见解分享
2023-10-11 17:20:58
准备就绪,在 TiDB 源码的汪洋大海中扬帆起航!在上篇文章中,我们深入剖析了 TiDB 对 COM_QUERY 命令的处理流程,而本次航行,我们聚焦于另一个备受欢迎的命令:Prepare/Execute。让我们一同揭开其神秘面纱,探寻其背后的技术奥妙。
一探究竟:Prepare/Execute 的前世今生
在数据库系统中,Prepare/Execute 是一种广泛采用的技术,它可以极大地提升重复查询的执行效率。其背后的原理很简单:它将查询语句划分为两个阶段:
- Prepare 阶段: 编译并解析查询语句,生成查询计划,但并不执行查询。
- Execute 阶段: 使用 Prepare 阶段生成的查询计划,执行查询并返回结果。
这样一来,当需要重复执行同一查询时,就不再需要重新编译和解析语句,直接使用 Prepare 阶段生成的查询计划即可,从而大幅缩短执行时间。
揭秘 TiDB 中的 Prepare/Execute 实现
在 TiDB 中,Prepare/Execute 的处理流程主要分为三个步骤:
-
Prepare 阶段:
- 解析查询语句,生成查询计划。
- 将查询计划存储在内存中,并返回一个 Prepare ID。
-
Execute 阶段:
- 使用 Prepare ID 检索 Prepare 阶段生成的查询计划。
- 执行查询并返回结果。
-
Deallocate 阶段:
- 释放 Prepare 阶段中分配的资源(例如查询计划)。
技术剖析:从源码到概念
现在,让我们从源码的角度一探究竟,了解 TiDB 中 Prepare/Execute 的实现细节:
Prepare 阶段:
func (c *conn) handlePrepare(p *mysql.PreparePacket) (*mysql.OKPacket, error) {
// 解析查询语句,生成查询计划
stmt, err := c.parsePrepare(p)
if err != nil {
return nil, err
}
// 将查询计划存储在内存中
prepareID := atomic.AddUint32(&c.lastPrepareID, 1)
c.preparedStmts[prepareID] = stmt
// 返回 Prepare ID
return &mysql.OKPacket{
AffectedRows: uint64(prepareID),
}, nil
}
Execute 阶段:
func (c *conn) handleExecute(e *mysql.ExecutePacket) (*mysql.ResultSet, error) {
// 使用 Prepare ID 检索 Prepare 阶段生成的查询计划
stmt, ok := c.preparedStmts[e.PrepareID]
if !ok {
return nil, errors.New("invalid prepare ID")
}
// 执行查询并返回结果
return stmt.Execute(c)
}
Deallocate 阶段:
func (c *conn) handleDeallocate(d *mysql.DeallocatePacket) (*mysql.OKPacket, error) {
// 释放 Prepare 阶段中分配的资源
delete(c.preparedStmts, d.PrepareID)
// 返回 OK 响应
return &mysql.OKPacket{}, nil
}