原文链接:TiDB 源码阅读系列文章(四)Insert 语句概览
前几篇我做的笔记:【笔记】《TiDB 源码阅读系列》1-3 SQL 框架
原文里有些地方和现在的 TiDB 略有差异,我会在笔记中指出。
本文重点介绍语句在执行框架下的具体执行逻辑。
连接 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);
处理流程简述:
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)
主要涉及两个部分:
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 结构:
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 中
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 语句的执行流程如下: