日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

TiDB 源码阅读系列文章(十六)INSERT 语句详解

發(fā)布時間:2024/4/17 编程问答 46 豆豆
生活随笔 收集整理的這篇文章主要介紹了 TiDB 源码阅读系列文章(十六)INSERT 语句详解 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

在之前的一篇文章 《TiDB 源碼閱讀系列文章(四)INSERT 語句概覽》 中,我們已經(jīng)介紹了 INSERT 語句的大體流程。為什么需要為 INSERT 單獨再寫一篇?因為在 TiDB 中,單純插入一條數(shù)據(jù)是最簡單的情況,也是最常用的情況;更為復雜的是在 INSERT 語句中設定各種行為,比如,對于 Unique Key 沖突的情況應如何處理:是報錯?是忽略當前插入的數(shù)據(jù)?還是覆蓋已有數(shù)據(jù)?所以,這篇會為大家繼續(xù)深入介紹 INSERT 語句。

本文將首先介紹在 TiDB 中的 INSERT 語句的分類,以及各語句的語法和語義,然后分別介紹五種 INSERT 語句的源碼實現(xiàn)。

INSERT 語句的種類

從廣義上講,TiDB 有以下六種 INSERT 語句:

  • Basic INSERT
  • INSERT IGNORE
  • INSERT ON DUPLICATE KEY UPDATE
  • INSERT IGNORE ON DUPLICATE KEY UPDATE
  • REPLACE
  • LOAD DATA

這六種語句理論上都屬于 INSERT 語句。

第一種,Basic INSERT,即是最普通的 INSERT 語句,語法 INSERT INTO VALUES (),語義為插入一條語句,若發(fā)生唯一約束沖突(主鍵沖突、唯一索引沖突),則返回執(zhí)行失敗。

第二種,語法 INSERT IGNORE INTO VALUES (),是當 INSERT 的時候遇到唯一約束沖突后,忽略當前 INSERT 的行,并記一個 warning。當語句執(zhí)行結(jié)束后,可以通過 SHOW WARNINGS 看到哪些行沒有被插入。

第三種,語法 INSERT INTO VALUES () ON DUPLICATE KEY UPDATE,是當沖突后,更新沖突行后插入數(shù)據(jù)。如果更新后的行跟表中另一行沖突,則返回錯誤。

第四種,是在上一種情況,更新后的行又跟另一行沖突后,不插入該行并顯示為一個 warning。

第五種,語法 REPLACE INTO VALUES (),是當沖突后,刪除表上的沖突行,并繼續(xù)嘗試插入數(shù)據(jù),如再次沖突,則繼續(xù)刪除標上沖突數(shù)據(jù),直到表上沒有與改行沖突的數(shù)據(jù)后,插入數(shù)據(jù)。

最后一種,語法 LOAD DATA INFILE INTO 的語義與 INSERT IGNORE 相同,都是沖突即忽略,不同的是 LOAD DATA 的作用是將數(shù)據(jù)文件導入到表中,也就是其數(shù)據(jù)來源于 csv 數(shù)據(jù)文件。

由于 INSERT IGNORE ON DUPLICATE KEY UPDATE 是在 INSERT ON DUPLICATE KEY UPDATE 上做了些特殊處理,將不再單獨詳細介紹,而是放在同一小節(jié)中介紹;LOAD DATA 由于其自身的特殊性,將留到其他篇章介紹。

Basic INSERT 語句

幾種 INSERT 語句的最大不同在于執(zhí)行層面,這里接著 《TiDB 源碼閱讀系列文章(四)INSERT 語句概覽》 來講語句執(zhí)行過程。不記得前面內(nèi)容的同學可以返回去看原文章。

INSERT 的執(zhí)行邏輯在 executor/insert.go 中。其實前面講的前四種 INSERT 的執(zhí)行邏輯都在這個文件里。這里先講最普通的 Basic INSERT。

InsertExec 是 INSERT 的執(zhí)行器實現(xiàn),其實現(xiàn)了 Executor 接口。最重要的是下面三個接口:

  • Open:進行一些初始化
  • Next:執(zhí)行寫入操作
  • Close:做一些清理工作

其中最重要也是最復雜的是 Next 方法,根據(jù)是否通過一個 SELECT 語句來獲取數(shù)據(jù)(INSERT SELECT FROM),將 Next 流程分為,insertRows 和 insertRowsFromSelect 兩個流程。兩個流程最終都會進入 exec 函數(shù),執(zhí)行 INSERT。

exec 函數(shù)里處理了前四種 INSERT 語句,其中本節(jié)要講的普通 INSERT 直接進入了 insertOneRow。

在講 insertOneRow 之前,我們先看一段 SQL。

CREATE TABLE t (i INT UNIQUE); INSERT INTO t VALUES (1); BEGIN; INSERT INTO t VALUES (1); COMMIT;

把這段 SQL 分別一行行地粘在 MySQL 和 TiDB 中看下結(jié)果。

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 是在語句執(zhí)行的時候做的檢測。這樣處理的原因是,TiDB 在設計上,與 TiKV 是分層的結(jié)構(gòu),為了保證高效率的執(zhí)行,在事務內(nèi)只有讀操作是必須從存儲引擎獲取數(shù)據(jù),而所有的寫操作都事先放在單 TiDB 實例內(nèi)事務自有的 memDbBuffer 中,在事務提交時才一次性將事務寫入 TiKV。在實現(xiàn)中是在 insertOneRow 中設置了 PresumeKeyNotExists 選項,所有的 INSERT 操作如果在本地檢測沒發(fā)現(xiàn)沖突,就先假設插入不會發(fā)生沖突,不需要去 TiKV 中檢查沖突數(shù)據(jù)是否存在,只將這些數(shù)據(jù)標記為待檢測狀態(tài)。最后到提交過程中,統(tǒng)一將整個事務里待檢測數(shù)據(jù)使用 BatchGet 接口做一次批量檢測。

當所有的數(shù)據(jù)都通過 insertOneRow 執(zhí)行完插入后,INSERT 語句基本結(jié)束,剩余的工作為設置一下 lastInsertID 等返回信息,并最終將其結(jié)果返回給客戶端。

INSERT IGNORE 語句

INSERT IGNORE 的語義在前面已經(jīng)介紹了。之前介紹了普通 INSERT 在提交的時候才檢查,那 INSERT IGNORE 是否可以呢?答案是不行的。因為:

  • INSERT IGNORE 如果在提交時檢測,那事務模塊就需要知道哪些行需要忽略,哪些直接報錯回滾,這無疑增加了模塊間的耦合。
  • 用戶希望立刻獲取 INSERT IGNORE 有哪些行沒有寫入進去。即,立刻通過 SHOW WARNINGS 看到哪些行實際沒有寫入。
  • 這就需要在執(zhí)行 INSERT IGNORE 的時候,及時檢查數(shù)據(jù)的沖突情況。一個顯而易見的做法是,把需要插入的數(shù)據(jù)試著讀出來,當發(fā)現(xiàn)沖突后,記一個 warning,再繼續(xù)下一行。但是對于一個語句插入多行的情況,就需要反復從 TiKV 讀取數(shù)據(jù)來進行檢測,顯然,這樣的效率并不高。于是,TiDB 實現(xiàn)了 batchChecker,代碼在 executor/batch_checker.go。

    在 batchChecker 中,首先,拿待插入的數(shù)據(jù),將其中可能沖突的唯一約束在 getKeysNeedCheck 中構(gòu)造成 Key(TiDB 是通過構(gòu)造唯一的 Key 來實現(xiàn)唯一約束的,詳見 《三篇文章了解 TiDB 技術(shù)內(nèi)幕——說計算》)。

    然后,將構(gòu)造出來的 Key 通過 BatchGetValues 一次性讀上來,得到一個 Key-Value map,能被讀到的都是沖突的數(shù)據(jù)。

    最后,拿即將插入的數(shù)據(jù)的 Key 到 BatchGetValues 的結(jié)果中進行查詢。如果查到了沖突的行,構(gòu)造好 warning 信息,然后開始下一行,如果查不到?jīng)_突的行,就可以進行安全的 INSERT 了。這部分的實現(xiàn)在 batchCheckAndInsert 中。

    同樣,在所有數(shù)據(jù)執(zhí)行完插入后,設置返回信息,并將執(zhí)行結(jié)果返回客戶端。

    INSERT ON DUPLICATE KEY UPDATE 語句

    INSERT ON DUPLICATE KEY UPDATE 是幾種 INSERT 語句中最為復雜的。其語義的本質(zhì)是包含了一個 INSERT 和 一個 UPDATE。較之與其他 INSERT 復雜的地方就在于,UPDATE 語義是可以將一行更新成任何合法的樣子。

    在上一節(jié)中,介紹了 TiDB 中對于特殊的 INSERT 語句采用了 batch 的方式來實現(xiàn)其沖突檢查。在處理 INSERT ON DUPLICATE KEY UPDATE 的時候我們采用了同樣的方式,但由于語義的復雜性,實現(xiàn)步驟也復雜了不少。

    首先,與 INSERT IGNORE 相同,首先將待插入數(shù)據(jù)構(gòu)造出來的 Key,通過 BatchGetValues 一次性地讀出來,得到一個 Key-Value map。再把所有讀出來的 Key 對應的表上的記錄也通過一次 BatchGetValues 讀出來,這部分數(shù)據(jù)是為了將來做 UPDATE 準備的,具體實現(xiàn)在 initDupOldRowValue。

    然后,在做沖突檢查的時候,如果遇到?jīng)_突,則首先進行一次 UPDATE。我們在前面 Basic INSERT 小節(jié)中已經(jīng)介紹了,TiDB 的 INSERT 是提交的時候才去 TiKV 真正執(zhí)行。同樣的,UPDATE 語句也是在事務提交的時候才真正去 TiKV 執(zhí)行的。在這次 UPDATE 中,可能還是會遇到唯一約束沖突的問題,如果遇到了,此時即報錯返回,如果該語句是 INSERT IGNORE ON DUPLICATE KEY UPDATE 則會忽略這個錯誤,繼續(xù)下一行。

    在上一步的 UPDATE 中,還需要處理以下場景,如下面這個 SQL:

    CREATE TABLE t (i INT UNIQUE); INSERT INTO t VALUES (1), (1) ON DUPLICATE KEY UPDATE i = i;

    可以看到,這個 SQL 中,表中原來并沒有數(shù)據(jù),第二句的 INSERT 也就不可能讀到可能沖突的數(shù)據(jù),但是,這句 INSERT 本身要插入的兩行數(shù)據(jù)之間沖突了。這里的正確執(zhí)行應該是,第一個 1 正常插入,第二個 1 插入的時候發(fā)現(xiàn)有沖突,更新第一個 1。此時,就需要做如下處理。將上一步被 UPDATE 的數(shù)據(jù)對應的 Key-Value 從第一步的 Key-Value map 中刪掉,將 UPDATE 出來的數(shù)據(jù)再根據(jù)其表信息構(gòu)造出唯一約束的 Key 和 Value,把這個 Key-Value 對放回第一步讀出來 Key-Value map 中,用于后續(xù)數(shù)據(jù)進行沖突檢查。這個細節(jié)的實現(xiàn)在 fillBackKeys。這種場景同樣出現(xiàn)在,其他 INSERT 語句中,如 INSERT IGNORE、REPLACE、LOAD DATA。之所以在這里介紹是因為,INSERT ON DUPLICATE KEY UPDATE 是最能完整展現(xiàn) batchChecker 的各方面的語句。

    最后,同樣在所有數(shù)據(jù)執(zhí)行完插入/更新后,設置返回信息,并將執(zhí)行結(jié)果返回客戶端。

    REPLACE 語句

    REPLACE 語句雖然它看起來像是獨立的一類 DML,實際上觀察語法的話,它與 Basic INSERT 只是把 INSERT 換成了 REPLACE。與之前介紹的所有 INSERT 語句不同的是,REPLACE 語句是一個一對多的語句。簡要說明一下就是,一般的 INSERT 語句如果需要 INSERT 某一行,那將會當遭遇了唯一約束沖突的時候,出現(xiàn)以下幾種處理方式:

    • 放棄插入,報錯返回:Basic INSERT
    • 放棄插入,不報錯:INSERT IGNORE
    • 放棄插入,改成更新沖突的行,如果更新的值再次沖突
    • 報錯:INSERT ON DUPLICATE KEY UPDATE
    • 不報錯:INSERT IGNORE ON DUPLICATE KEY UPDATE

    他們都是處理一行數(shù)據(jù)跟表中的某一行沖突時的不同處理。但是 REPLACE 語句不同,它將會刪除遇到的所有沖突行,直到?jīng)]有沖突后再插入數(shù)據(jù)。如果表中有 5 個唯一索引,那有可能有 5 條與等待插入的行沖突的行。那么 REPLACE 語句將會一次性刪除這 5 行,再將自己插入。看以下 SQL:

    CREATE TABLE t ( i int unique, j int unique, k int unique, l int unique, m int unique);INSERT INTO t VALUES (1, 1, 1, 1, 1), (2, 2, 2, 2, 2), (3, 3, 3, 3, 3), (4, 4, 4, 4, 4);REPLACE INTO t VALUES (1, 2, 3, 4, 5);SELECT * FROM t; i j k l m 1 2 3 4 5

    在執(zhí)行完之后,實際影響了 5 行數(shù)據(jù)。

    理解了 REPLACE 語句的特殊性以后,我們就可以更容易理解其具體實現(xiàn)。

    與 INSERT 語句類似,REPLACE 語句的主要執(zhí)行部分也在其 Next 方法中,與 INSERT 不同的是,其中的 insertRowsFromSelect 和 insertRows 傳遞了 ReplaceExec 自己的 exec 方法。在 exec 中調(diào)用了 replaceRow,其中同樣使用了 batchChecker 中的批量沖突檢測,與 INSERT 有所不同的是,這里會刪除一切檢測出的沖突,最后將待插入行寫入。

    寫在最后

    INSERT 語句是所有 DML 語句中最復雜,功能最強大多變的一個。其既有像 INSERT ON DUPLICATE UPDATE 這種能執(zhí)行 INSERT 也能執(zhí)行 UPDATE 的語句,也有像 REPLACE 這種一行數(shù)據(jù)能影響許多行數(shù)據(jù)的語句。INSERT 語句自身都可以連接一個 SELECT 語句作為待插入數(shù)據(jù)的輸入,因此,其又受到了來自 planner 的影響(關于 planner 的部分詳見相關的源碼閱讀文章: (七)基于規(guī)則的優(yōu)化 和 (八)基于代價的優(yōu)化)。熟悉 TiDB 的 INSERT 各個語句實現(xiàn),可以幫助各位讀者在將來使用這些語句時,更好地根據(jù)其特色使用最為合理、高效語句。另外,如果有興趣向 TiDB 貢獻代碼的讀者,也可以通過本文更快的理解這部分的實現(xiàn)。

    作者:于帥鵬

    總結(jié)

    以上是生活随笔為你收集整理的TiDB 源码阅读系列文章(十六)INSERT 语句详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。