標籤:

TiDB 源碼閱讀系列文章(四)Insert 語句概覽

作者: @申礫

本文為 TiDB 源碼閱讀系列文章的第四篇。上一篇文章簡單介紹了整體流程,無論什麼語句,大體上是在這個框架下運行,DDL 語句也不例外。

本篇文章會以 Insert 語句為例進行講解,幫助讀者理解前一篇文章,下一篇文章會介紹 Select 語句的執行流程。這兩條是最常用的讀、寫語句,其他的語句相信讀者能觸類旁通,可以自行研究或者是等待後續的文章。對於這兩類語句,目前也只會針對核心流程進行說明,更複雜的 Join、Insert-Into-OnDuplicate-Update 等會等到後面的文章進行講解。另外本文會重點介紹每個語句在執行框架下面的具體執行邏輯,請讀者閱讀前先了解 Insert 語句的行為。

表結構

這裡先給一個表結構,下面介紹的 SQL 語句都是在這個表上的操作。

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

Insert 語句

INSERT INTO t VALUES ("pingcap001", "pingcap", 3); 以這條語句為例,解釋 Insert 是如何運行的。

語句處理流程

首先大家回憶一下上一篇文章介紹的框架,一條 SQL 語句經過協議層、Parser、Plan、Executor 這樣幾個模塊處理後,變成可執行的結構,再通過 Next() 來驅動語句的真正執行。對於框架,每類語句都差不多;對於每個核心步驟,每個語句會有自己的處理邏輯。

語法解析

先看 Parser,對於 Insert 語句的解析邏輯在這裡,可以看到這條語句會被解析成下面這個結構:

// InsertStmt is a statement to insert new rows into an existing table.// See https://dev.mysql.com/doc/refman/5.7/en/insert.htmltype InsertStmt struct { dmlNode IsReplace bool IgnoreErr bool Table *TableRefsClause Columns [](#)*ColumnName Lists [](#)[](#)ExprNode Setlist [](#)*Assignment Priority mysql.PriorityEnum OnDuplicate [](#)*Assignment Select ResultSetNode}

這裡提到的語句比較簡單,只會涉及 Table 以及 Lists 這兩個欄位,也就是向哪個表插入哪些數據。其中 Lists 是一個二維數組,數組中的每一行對應於一行數據,這個語句只包含一行數據。有了 AST 之後,需要對其進行一系列處理,預處理、合法性驗證、許可權檢查這些暫時跳過(每個語句的處理邏輯都差不多),我們看一下針對 Insert 語句的處理邏輯。

查詢計劃

接下來是將 AST 轉成 Plan 結構,這個操作是在 planBuilder.buildInsert() 中完成。對於這個簡單的語句,主要涉及兩個部分:

  • 補全 Schema 信息

包括 Database/Table/Column 信息,這個語句沒有指定向那些列插入數據,所以會使用所有的列。

  • 處理 Lists 中的數據

這裡會處理一遍所有的 Value,將 ast.ExprNode 轉換成 expression.Expression,也就是納入了我們的表達式框架,後面會在這個框架下求值。大多數情況下,這裡的 Value 都是常量,也就是 expression.Constant。

如果 Insert 語句比較複雜,比如要插入的數據來自於一個 Select,或者是 OnDuplicateUpdate 這種情況,還會做更多的處理,這裡暫時不再深入描述,讀者可以執行看 buildInsert() 中其他的代碼。

現在 ast.InsertStmt 已經被轉換成為 plan.Insert 結構,對於 Insert 語句並沒有什麼可以優化的地方,plan.Insert 這個結構只實現了 Plan 這個介面,所以在下面這個判斷中,不會走進 Optimize 流程:

if logic, ok := p.(LogicalPlan); ok { return doOptimize(builder.optFlag, logic) }

其他比較簡單的語句也不會進入 doOptimize,比如 Show 這種語句,下一篇文章會講解 Select 語句,會涉及到 doOptimize 函數。

執行

拿到 plan.Insert 這個結構後,查詢計劃就算制定完成。最後我們看一下 Insert 是如何執行的。

首先 plan.Insert 在這裡被轉成 executor.InsertExec 結構,後續的執行都由這個結構進行。執行入口是 Next 方法,第一步是要對待插入數據的每行進行表達式求值,具體的可以看 getRows 這個函數,拿到數據後就進入最重要的邏輯— InsertExec.exec() 這個函數,這個函數有點長,不過只考慮我們文章中講述得這條 SQL 的話,可以把代碼簡化成下面這段邏輯:

for _, row := range rows { h, err := e.Table.AddRecord(e.ctx, row, false) }

接下來我們看一下 AddRecord 這個函數是如何將一行數據寫入存儲引擎中。要理解這段代碼,需要了解一下 TiDB 是如何將 SQL 的數據映射為 Key-Value,可以先讀一下我們之前寫的一些文章,比如這一篇。這裡假設讀者已經了解了這一點背景知識,那麼一定會知道這裡需要將 Row 和 Index 的 Key-Value 構造出來的,寫入存儲引擎。

構造 Index 數據的代碼在 addIndices() 函數中,會調用 index.Create() 這個方法:

構造 Index Key:func (c *index) GenIndexKey(sc *stmtctx.StatementContext, indexedValues [](#)types.Datum, h int64, buf [](#)byte) (key [](#)byte, distinct bool, err error) {...... key = c.getIndexKeyBuf(buf, len(c.prefix)+len(indexedValues)*9+9) key = append(key, [](#)byte(c.prefix)...) key, err = codec.EncodeKey(sc, key, indexedValues...) if !distinct && err == nil { key, err = codec.EncodeKey(sc, key, types.NewDatum(h)) }

構造 Index Value:func (c *index) Create(ctx context.Context, rm kv.RetrieverMutator, indexedValues [](#)types.Datum, h int64) (int64, error) { if !distinct { // non-unique index doesnt need store value, write a 0 to reduce space err = rm.Set(key, [](#)byte0) return 0, errors.Trace(err) }...... if skipCheck { err = rm.Set(key, encodeHandle(h)) return 0, errors.Trace(err) }

構造 Row 數據的代碼比較簡單,就在 tables.AddRecord 函數中:

構造 Row Key: key := t.RecordKey(recordID)

構造 Row Value:writeBufs.RowValBuf, err = tablecodec.EncodeRow(ctx.GetSessionVars().StmtCtx, row, colIDs, writeBufs.RowValBuf, writeBufs.AddRowValues)

構造完成後,調用類似下面這端代碼即可將 Key-Value 寫到當前事務的緩存中:

if err = txn.Set(key, value); err != nil { return 0, errors.Trace(err) }

在事務的提交過程中,即可將這些 Key-Value 提交到存儲引擎中。

小結

Insert 語句在諸多 DML 語句中算是最簡單的語句,本文也沒有涉及 Insert 語句中更複雜的情況,所以相對比較好理解。上面講了這麼多代碼,讓我們用一幅圖來再回顧一下整個流程。

最後給大家留一個思考題,本文描述了如何寫入數據,那麼 TiDB 是如何刪除數據的呢?也就是 Delete 語句的執行流程是什麼樣子的,請大家追蹤源碼,調研一下這個流程,有興趣的讀者可以仿照本文寫一篇源碼解析文檔,投稿給我們。

下一篇文章會介紹一下 Select 語句的執行流程,不但會涉及到 SQL 層,也會介紹 Coprocessor 模塊是如何工作的,敬請期待。


推薦閱讀:

TiDB 2016 回顧與 2017 的一些想法
oceanbase、TiDB這類NewSQL最近勢頭好強勁,它們的定位究竟是什麼?
TiDB 1.1 Alpha Release
TiDB 在 Ping++ 金融聚合支付業務中的實踐
TiDB 在零氪科技(LinkDoc)大數據醫療系統的實踐

TAG:TiDB | 源碼閱讀 |