5.深入TiDB:Insert 语句 (3)

最后所有的 Key Value 构造完毕之后会将值写入到当前事务缓存中,等待提交。

func (t *TableCommon) AddRecord(sctx sessionctx.Context, r []types.Datum, opts ...table.AddRecordOption) (recordID kv.Handle, err error) { ... var setPresume bool skipCheck := sctx.GetSessionVars().StmtCtx.BatchCheck if (t.meta.IsCommonHandle || t.meta.PKIsHandle) && !skipCheck && !opt.SkipHandleCheck { // 如果是 LazyCheck ,那么只读取本地缓存判断是否存在 if sctx.GetSessionVars().LazyCheckKeyNotExists() { var v []byte //只读取本地缓存判断是否存在 v, err = txn.GetMemBuffer().Get(ctx, key) if err != nil { setPresume = true } if err == nil && len(v) == 0 { err = kv.ErrNotExist } } else { //否则会通过rpc请求tikv从集群中校验数据是否存在 _, err = txn.Get(ctx, key) } if err == nil { handleStr := getDuplicateErrorHandleString(t, recordID, r) return recordID, kv.ErrKeyExists.FastGenByArgs(handleStr, "PRIMARY") } else if !kv.ErrNotExist.Equal(err) { return recordID, err } } //将 Key-Value 写到当前事务的缓存中 if setPresume { // 表示假定数据不存在 err = memBuffer.SetWithFlags(key, value, kv.SetPresumeKeyNotExists) } else { err = memBuffer.Set(key, value) } if err != nil { return nil, err } ... }

由于在设计上,TiDB 与 TiKV 是分层的结构,为了保证高效率的执行,在 LazyCheck 模式下,在事务内只有读操作是必须从存储引擎获取数据,而所有的写操作都事先放在单 TiDB 实例内事务自有的 memDbBuffer 中,在事务提交时才一次性将事务写入 TiKV。

如上面代码所示,在调用 AddRecord 时,会根据 Key 从 MemBuffer 中判断是否存在,不存在那么在操作 memBuffer 的时候会打上标记 SetPresumeKeyNotExists 表示假设插入不会发生冲突,不需要去 TiKV 中检查冲突数据是否存在,只将这些数据标记为待检测状态。最后到提交过程中,统一将整个事务里待检测数据做一次批量检测。

下面通过一个官方的例子来说明一下 LazyCheck 模式下 MySQL 和 TiDB 的区别:

MySQL:

mysql> CREATE TABLE t (i INT UNIQUE); Query OK, 0 rows affected (0.15 sec) mysql> INSERT INTO t VALUES (1); Query OK, 1 row affected (0.01 sec) mysql> BEGIN; Query OK, 0 rows affected (0.00 sec) mysql> INSERT INTO t VALUES (1); ERROR 1062 (23000): Duplicate entry '1' for key 'i' mysql> COMMIT; Query OK, 0 rows affected (0.11 sec)

TiDB:

mysql> CREATE TABLE t (i INT UNIQUE); Query OK, 0 rows affected (1.04 sec) mysql> INSERT INTO t VALUES (1); Query OK, 1 row affected (0.12 sec) mysql> BEGIN; Query OK, 0 rows affected (0.01 sec) mysql> INSERT INTO t VALUES (1); Query OK, 1 row affected (0.00 sec) mysql> COMMIT; ERROR 1062 (23000): Duplicate entry '1' for key 'i'

可以看出来,对于 INSERT 语句 TiDB 是在事务提交的时候才做冲突检测而 MySQL 是在语句执行的时候做的检测。

最后让我们用一幅图来再回顾一下整个流程:

tidb5

INSERT IGNORE

INSERT IGNORE和普通 Insert 不同的是当 INSERT 的时候遇到唯一约束冲突后,忽略当前 INSERT 的行,并记一个 warning。当语句执行结束后,可以通过 SHOW WARNINGS看到哪些行没有被插入。

为了实现这个目的又不影响性能,TiDB 通过 batchCheckAndInsert 批量检测来校验数据是否冲突:

func (e *InsertExec) exec(ctx context.Context, rows [][]types.Datum) error { ... sessVars := e.ctx.GetSessionVars() defer sessVars.CleanBuffers() ignoreErr := sessVars.StmtCtx.DupKeyAsWarning // 判断是否有 OnDuplicate 语句 if len(e.OnDuplicate) > 0 { ... // 判断是否包含 IGNORE 语句 } else if ignoreErr { // 判断是否重复,不重复则插入 err := e.batchCheckAndInsert(ctx, rows, e.addRecord) if err != nil { return err } // 普通 Insert } else { ... } return nil }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zzsjfg.html