天天看點

淺談 TiDB 初始化系統庫過程

作者:alexshen​

1. init()函數

在golang中,有兩個保留函數,一個是main函數,另一個就是init函數。我們都知道,golang裡的main函數是程式的入口函數,main函數執行完成之後,整個程式也就執行完成了。但是,golang在執行main之前,會幹三件事,第一件事,也是最先做的,就是導入程式中用到的各種依賴包,第二件事,就是做完第一件事情之後,對程式中的變量和常量進行初始化,那麼,第三件事,就是在做完前面兩件事之後,才會開始的,執行所有包中的init函數。在所有init函數執行完畢之後,才會去執行main函數。

init主要用途就是 初始化,不能使用初始化表達式初始化的,變量。在TiDB源碼中,初始化系統庫information_schema時,就利用到了init函數。

2. TiDB中表的分類

在TiDB中,将表分為三大類,分别是normal table、virtual table、cluster table。

淺談 TiDB 初始化系統庫過程

normal table,也就是實體表,它是持久化存儲到TiKV當中的表,主要用于存儲使用者資料。全資料庫共享唯一一份資料,不同的TiDB 讀到的資料完全一緻。

virtual table,即虛拟表,它隻有表結構資訊,而不存儲資料,當到使用的時候,比如查詢這個表的資料的時候,它會從記憶體中提取相應資料傳回,多見于performance_schema,現在僅僅上隻有相容性上的意義。

cluster table,也可以稱之為記憶體表,它是TiDB啟動之後,根據表的實際定義,生成的僅存在于TiDB記憶體中的表,具體可以參考information_schema中的表,像SLOW_QUERY、PROCESSLIST表等。

3. 本地啟動調試TiDB

TiDB啟動時會預設建立五個SCHEMA,分别是INFORMATION_SCHEMA、METRICS_SCHEMA、PERFORMANCE_SCHEMA、mysql以及test。

其中,TiDB源碼每次啟動時,都會建立INFORMATION_SCHEMA、METRICS_SCHEMA、PERFORMANCE_SCHEMA,但是卻不會每次都去建立mysql以及test,這就與TiDB表的分類有關了。mysql以及test中的表都屬于NormalTable,是以這兩個schema中的table資料最終是會存儲落實在TiKV或者是MockTiKV中,而在本地,第一次編譯TiDB源碼啟動時,TiDB會在本地建立一個MockTiKV存儲的檔案,一般是存在 ‘///tmp/tikv ’中,那麼這些NormalTable就最終就會存儲在這個MockTiKV中,那麼第二次編譯啟動TiKV時,則不會再去建立一個新的mysql以及test的SCHEMA,而是直接使用緩存中的SCHEMA。那麼剩下的三個SCHEMA,裡面的大部分表都屬于VirtualTable,隻有在INFORMATION_SCHEMA中,以*CLUSER_*開頭的表是屬于ClusterTable,其餘的則都是VirtualTable。這些就與TiDB系統資料庫初始化過程息息相關了。

接下來,我們就來看一下,整個TiDB項目源碼啟動時,都做了什麼事情,這裡我們要跳出tidb-server/main.go這個檔案,去見識更為廣闊的天地。這裡我們就僅僅拿建立系統SCHEMA的過程來講一下,至于其餘的部分,我們先不做處理。

step1 init過程,init過程中會去建立INFORMATION_SCHEMA,以及METRICS_SCHEMA的系統庫。

淺談 TiDB 初始化系統庫過程
淺談 TiDB 初始化系統庫過程

step2 執行main函數,初始化tidb-sever。main函數中,createStoreAndDomain()會通過session.BootstrapSession()去初始化一個session,而在這個過程中,有一個函數,getStoreBootStrapVersion(),這個函數是用來擷取本地MockTiKV的版本資訊,預設是0。在第一次在本地初始化啟動TiDB的時候,會在工程目錄的根目錄下建立一個tmp檔案夾,用來存儲MockTiKV的資料。當這個檔案存在時,那麼TiDB啟動就會擷取到這個檔案中TiKV的版本資訊,目前手上的源碼,TiKV版本号是62,而最新的TiDB源碼倉庫,這個版本号是等于65;如果擷取到的版本資訊小于62,則會認為,本地TiKV版本老舊,就會去執行upgrade函數,去更新TiKV;但是如果本地沒有這樣的一個TiKV,那麼這個版本就是0,就會去執行bootstrap()。

淺談 TiDB 初始化系統庫過程

step3 在我們來先預設本地沒有TiKV的緩存副本,就當作第一次啟動TiDB。那麼,在初始化session的過程中,TiDB就會在createSession()中初始化PERFORMANCE_SCHEMA系統庫,然後通過bootstrap()執行DDL以及DML語句,将mysql、test這兩個剩餘的資料庫建立,并且存儲進本地的MockTiKV中。如果存在TiKV緩存的話,TiDB就隻會去在初始化session的過程中初始化PERFORMANCE_SCHEMA資料庫,而不會去執行建立mysql以及test資料庫的DDL以及DML語句。

淺談 TiDB 初始化系統庫過程

\

淺談 TiDB 初始化系統庫過程

至于TiDB為什麼要在這一步進行PERFORMANCE_SCHEMA的初始化,而不是選擇直接在init階段就将初始化的事情做了。猜測:根據源代碼的命名方式上來看,一開始是打算這麼做的,但是,問題就出在TiDB建立PERFORMANCE_SCHEMA的代碼上。TiDB中去建立這個系統資料庫裡面的表是通過執行SQL語句的方式去做的,那麼就會帶來一個問題,由于init函數的特性,其執行順序的不确定,解析SQL語句的子產品就不一定就會先運作起來,就不一定能夠去正确解析SQL語句。這個時候,就為了保證程式能夠順利啟動,就必須就得先初始化plan/core的init函數,也就是必須得保證SQL解析子產品是正确運作起來的,這個時候,再通過go中的synv.Once方法,将PERFORMANCE_SCHEMA通過SQL語句的方式建立起來,并且寫入到記憶體中儲存,記住,這裡是記憶體,不是負責存儲的TiKV,也就是說,PERFORMANCE_SCHEMA中的表同樣的全部都屬于虛拟表,VirtualTable,不會存儲在TiKV中。其實我覺着這裡很怪異,在每次啟動代碼的時候,就都要先去解析一堆SQL語句,解析出來就會得到資料庫中的表結構,有了表結構,後續的建立過程就與建立INFORMATION_SCHEMA的方式無異,将表的資訊封裝進PERFORMANCE_SCHEMA的資料庫對象中,再通過registerVirtualTable的方式去建立PERFORMANCE_SCHEMA的表,然後存進記憶體,而不是通過執行SQL語句的方式,将表存到TiKV中。TiDB執行SQL語句的流程,最終是将語句下發到底層TiKV上去執行,但是對于PERFORMANCE_SCHEMA而言,那一堆建表的SQL語句的作用,就是給解析子產品兒來進行解析,得到表結構,然後拿到表結構去将這些表注冊成虛拟表,存進記憶體。這一點,我無法了解為什麼要這麼幹,看源碼關于這一塊的時候,我看到一堆 ‘CREATE TABLE if not exists performance_schema.table_name’ 靜态常量語句的時候,才明白過來。

淺談 TiDB 初始化系統庫過程

\