【笔记】《TiDB 源码阅读系列》4 INSERT 语句概览

前言

原文链接:TiDB 源码阅读系列文章(四)Insert 语句概览

前几篇我做的笔记:【笔记】《TiDB 源码阅读系列》1-3 SQL 框架

原文里有些地方和现在的 TiDB 略有差异,我会在笔记中指出。

(四) Insert 语句概览

本文重点介绍语句在执行框架下的具体执行逻辑。

语句

连接 TiDB

mysql -h 127.0.0.1 -P 4000 -u root

建立表

CREATE TABLE t (
	id VARCHAR(31),
	name VARCHAR(50),
	age int,
	key id_idx (id)
);

插入语句

INSERT INTO t VALUES ("pingcap001", "pingcap", 3);

处理流程简述:

  1. 通过协议层、Parser、Plan、Executor 后变成可执行的结构。
  2. 通过 Next() 来驱动语句真正执行。

语法解析

Insert 语句会被解析成一个 InsertStmt 结构体,它定义在:

parser/ast/dml.go

1345: // InsertStmt 是一个向已存在的表中插入新行的语句
type InsertStmt struct {
	dmlNode

	IsReplace   bool
	IgnoreErr   bool
	Table       *TableRefsClause
	Columns     []*ColumnName
	Lists       [][]ExprNode
	Setlist     []*Assignment
	Priority    mysql.PriorityEnum
	OnDuplicate []*Assignment
	Select      ResultSetNode
}

这里只用到了 Table 和 Lists 字段,分别表示向哪个表插入以及插入哪些数据。

生成 AST 之后,需要对其进行一系列处理,我们先跳过一些公共的处理逻辑,如预处理、合法性验证、权限检查。

查询计划

接下来是将 AST 转成 Plan 结构,进入流程是:

executor/compile.go

func (c *Compiler) Compile(ctx context.Context, stmtNode ast.StmtNode) (*ExecStmt, error) {
61: finalPlan, names, err := planner.Optimize(ctx, c.Ctx, stmtNode, infoSchema)

planner/optimize.go

func Optimize(ctx context.Context, sctx sessionctx.Context, node ast.Node, is infoschema.InfoSchema) (plannercore.Plan, types.NameSlice, error) {
60:	bestPlan, names, _, err := optimize(ctx, sctx, node, is)

func optimize(ctx context.Context, sctx sessionctx.Context, node ast.Node, is infoschema.InfoSchema) (plannercore.Plan, types.NameSlice, float64, error) {
133: p, err := builder.Build(ctx, node)

planner/core/planbuilder.go

/ Build 将抽象语法树构建为 Plan
func (b *PlanBuilder) Build(ctx context.Context, node ast.Node) (Plan, error) {
377: switch x := node.(type) {
393: return b.buildInsert(ctx, x)

主要涉及两个部分:

  1. 补全 Schema 信息,包括数据库/表/列信息。(?)
  2. 根据 Insert 的类型(values, set, select)分发到不同函数中。对于 insert values,会处理 Lists 中所有的Value,将 ast.ExprNode 转换为 expression.Expression,也就是纳入表达式框架,后面会在这个框架下求值。

planner/core/planbuilder.go

func (b *PlanBuilder) buildInsert(ctx context.Context, insert *ast.InsertStmt) (Plan, error) {
1878: err := b.buildSetValuesOfInsert(ctx, insert, insertPlan, mockTablePlan, checkRefColumn)
1884: err := b.buildValuesListOfInsert(ctx, insert, insertPlan, mockTablePlan, checkRefColumn)
1890: err := b.buildSelectPlanOfInsert(ctx, insert, insertPlan)

func (b *PlanBuilder) buildValuesListOfInsert(..., insertPlan *Insert, ...) error {
2070: for i, valuesItem := range insert.Lists {
2079: 	exprList := make([]expression.Expression, 0, len(valuesItem))
2080:	for j, valueItem := range valuesItem {
2081:		var expr expression.Expression
2123:		exprList = append(exprList, expr)
2125:	insertPlan.Lists = append(insertPlan.Lists, exprList)

已经生成的 insertPlan 是 plannercore.Insert 类型,并且实现了 Plan 接口。

注意,Insert 并不被认为是逻辑计划(没有需要优化的地方),所以会在164行直接返回,不进行后面的操作。

而对于 Select 这种语句(下几篇中提到),会经过 DoOptimize 函数将逻辑计划优化并转换为物理计划:

planner/optimize.go

func optimize(ctx context.Context, sctx sessionctx.Context, node ast.Node, is infoschema.InfoSchema) (plannercore.Plan, types.NameSlice, float64, error) {
163: if !isLogicalPlan {
164: 	return p, names, 0, nil
172: finalPlan, cost, err := plannercore.DoOptimize(ctx, builder.GetOptFlag(), logic)
173: return finalPlan, names, cost, err

planner/core/optimizer.go

118: func DoOptimize(ctx context.Context, flag uint64, logic LogicalPlan) (PhysicalPlan, float64, error) {

执行

经过一系列复杂调用, plannercore.Insert 在这里被转换成了 executor.InsertExec 结构:
【笔记】《TiDB 源码阅读系列》4 INSERT 语句概览_第1张图片
executer/builder.go

func (b *executorBuilder) buildInsert(v *plannercore.Insert) Executor {
728: insert := &InsertExec{
729:	InsertValues: ivs,
730:	OnDuplicate:  append(v.OnDuplicate, v.GenCols.OnDuplicates...),
731: }
732: return insert

后续的执行都由这个结构进行,主要流程在 insert.go 中
【笔记】《TiDB 源码阅读系列》4 INSERT 语句概览_第2张图片

executer/insert.go, insert_common.go

func (e *InsertExec) Next(ctx context.Context, req *chunk.Chunk) error {
256: return insertRows(ctx, e)

// insert_common.go 对插入数据的每行进行表达式求值。
func insertRows(ctx context.Context, base insertCommon) (err error) {
236: if err = base.exec(ctx, rows); err != nil {
	
func (e *InsertExec) exec(ctx context.Context, rows [][]types.Datum) error {
84: for _, row := range rows {
85:		if _, err := e.addRecord(ctx, row); err != nil {

// insert_common.go 将一行数据写入存储引擎中
func (e *InsertValues) addRecord(ctx context.Context, row []types.Datum) (int64, error) {
974: h, err := e.Table.AddRecord(e.ctx, row, table.WithCtx(ctx))

table/tables/tables.go

454: func (t *TableCommon) AddRecord(ctx sessionctx.Context, r []types.Datum, opts ...table.AddRecordOption) (recordID int64, err error) {

最后在这个函数中构造索引、行,写入事务缓存,就不单独列出了。

小结

Insert 语句的执行流程如下:

  1. server/conn.go 监听端口,得到语句后进行处理,传给 session 包。
  2. session/session.go 的 execute 函数包含了语句解析执行的三个核心流程。
  3. 调用 parse 包中的内容,将语句解析成抽象语法树,即 ast.InsertStmt 类型。
  4. 调用 execute/complie.go,转发给 planner 包处理,传回一个 Plan 接口类型,实际是 plannercore.Insert 类型。
  5. 调用 executor 包,在 executor/builder.go 中 Plan 被转换为了 executor.InsertExec 类型,即执行器。
  6. 使用 Next 函数来执行语句,INSERT 语句在返回前就会被执行,通过 table 包执行结果并写入到事务缓存中。
  7. 将 resultSet 返回给 server/conn.go(SELECT语句会在此时调用 Next 执行)。

你可能感兴趣的:(TiDB,学习笔记,Go)