天天看點

mysql 模型映射_TiDB如何将關系模型如何映射到KV中?

TiDB 的整體架構如下圖所示↓

mysql 模型映射_TiDB如何将關系模型如何映射到KV中?

底層使用的是KV存儲,但卻支援MySQL的協定,這是如何做到的呢?

先得了解TiDB架構中幾個核心子產品↓

TiDB Server 負責接收 SQL 請求,處理 SQL 相關的邏輯,并通過 PD 找到存儲計算所需資料的 TiKV 位址,與 TiKV 互動擷取資料,最終傳回結果。TiDB Server 是無狀态的,其本身并不存儲資料,隻負責計算,可以無限水準擴充,可以通過負載均衡元件(如LVS、HAProxy 或 F5)對外提供統一的接入位址。

Placement Driver (簡稱 PD) 是整個叢集的管理子產品,其主要工作有三個:一是存儲叢集的元資訊(某個 Key 存儲在哪個 TiKV 節點);二是對 TiKV 叢集進行排程和負載均衡(如資料的遷移、Raft group leader 的遷移等);三是配置設定全局唯一且遞增的事務 ID。

PD 通過 Raft 協定保證資料的安全性。Raft 的 leader server 負責處理所有操作,其餘的 PD server 僅用于保證高可用。建議部署奇數個 PD 節點。

TiKV Server 負責存儲資料,從外部看 TiKV 是一個分布式的提供事務的 Key-Value 存儲引擎。存儲資料的基本機關是 Region,每個 Region 負責存儲一個 Key Range(從 StartKey 到 EndKey 的左閉右開區間)的資料,每個 TiKV 節點會負責多個 Region。TiKV 使用 Raft 協定做複制,保持資料的一緻性和容災。副本以 Region 為機關進行管理,不同節點上的多個 Region 構成一個 Raft Group,互為副本。資料在多個 TiKV 之間的負載均衡由 PD 排程,這裡也是以 Region 為機關進行排程。

=========樸素分割線========

了解完架構特點,再回歸正題。

SQL 和 KV 結構之間存在巨大的差別,那麼如何能夠友善高效地進行映射,就成為一個很重要的問題。一個好的映射方案必須有利于對資料操作的需求。那麼我們先看一下對資料的操作有哪些需求,分别有哪些特點。

對于 Row,可以選擇行存或者列存,這兩種各有優缺點。TiDB 面向的首要目标是 OLTP 業務,這類業務需要支援快速地讀取、儲存、修改、删除一行資料,是以采用行存是比較合适的。

對于 Index,TiDB 不止需要支援 Primary Index,還需要支援 Secondary Index。Index 的作用的輔助查詢,提升查詢性能。查詢的時候有兩種模式,一種是點查,比如通過 Primary Key 或者 Unique Key 的等值條件進行查詢,如 select name from user where id=1; ,這種需要通過索引快速定位到某一行資料;另一種是 Range 查詢,如 select name from user where age > 30 and age < 35;,這個時候需要通過idxAge索引查詢 age 在 30 和 35 之間的那些資料。Index 還分為 Unique Index 和 非 Unique Index,這兩種都需要支援。

分析完需要存儲的資料的特點,我們再看看對這些資料的操作需求,主要考慮 Insert/Update/Delete/Select 這四種語句。

對于 Insert 語句,需要将 Row 寫入 KV,并且建立好索引資料。

對于 Update 語句,需要将 Row 更新的同時,更新索引資料(如果有必要)。

對于 Delete 語句,需要在删除 Row 的同時,将索引也删除。

上面三個語句處理起來都很簡單。對于 Select 語句,情況會複雜一些。首先我們需要能夠簡單快速地讀取一行資料,是以每個 Row 需要有一個 ID (顯示或隐式的 ID)。其次可能會讀取連續多行資料,比如 Select * from user;。最後還有通過索引讀取資料的需求,對索引的使用可能是點查或者是範圍查詢。

在上一篇文章三篇文章了解 TiDB 技術内幕——說存儲中,我們知道TiKV是一個全局有序的分布式 Key-Value 引擎,全局有序這一點重要,可以幫助我們解決不少問題。比如對于快速擷取一行資料,假設我們能夠構造出某一個或者某幾個 Key,定位到這一行,我們就能利用 TiKV 提供的 Seek 方法快速定位到這一行資料所在位置。再比如對于掃描全表的需求,如果能夠映射為一個 Key 的 Range,從 StartKey 掃描到 EndKey,那麼就可以簡單的通過這種方式獲得全表資料。操作 Index 資料也是類似的思路。接下來讓我們看看 TiDB 是如何做的。

TiDB 對每個表配置設定一個 TableID,每一個索引都會配置設定一個 IndexID,每一行配置設定一個 RowID(如果表有整數型的 Primary Key,那麼會用 Primary Key 的值當做 RowID),其中 TableID 在整個叢集内唯一,IndexID/RowID 在表内唯一,這些 ID 都是 int64 類型。

每行資料按照如下規則進行編碼成 Key-Value pair:

Key: tablePrefix{tableID}_recordPrefixSep{rowID}

Value: [col1, col2, col3, col4]

其中 Key 的 tablePrefix/recordPrefixSep 都是特定的字元串常量,用于在 KV 空間内區分其他資料。

對于 Index 資料,會按照如下規則編碼成 Key-Value pair:

Key: tablePrefix{tableID}_indexPrefixSep{indexID}_indexedColumnsValue

Value: rowID

Index 資料還需要考慮 Unique Index 和非 Unique Index 兩種情況,對于 Unique Index,可以按照上述編碼規則。但是對于非 Unique Index,通過這種編碼并不能構造出唯一的 Key,因為同一個 Index 的 tablePrefix{tableID}_indexPrefixSep{indexID} 都一樣,可能有多行資料的 ColumnsValue 是一樣的,是以對于非 Unique Index 的編碼做了一點調整:

Key: tablePrefix{tableID}_indexPrefixSep{indexID}_indexedColumnsValue_rowID

Value: null

這裡再舉個簡單的例子,便于大家了解,還是以上面的表結構為例。假設表中有 3 行資料:

1, "TiDB", "SQL Layer", 10

2, "TiKV", "KV Engine", 20

3, "PD", "Manager", 30

那麼首先每行資料都會映射為一個 Key-Value pair,注意這個表有一個 Int 類型的 Primary Key,是以 RowID 的值即為這個 Primary Key 的值。假設這個表的 Table ID 為 10,其 Row 的資料為:

t10_r1 --> ["TiDB", "SQL Layer", 10]

t10_r2 --> ["TiKV", "KV Engine", 20]

t10_r3 --> ["PD", "Manager", 30]

除了 Primary Key 之外,這個表還有一個 Index,假設這個 Index 的 ID 為 1,則其資料為:

t10_i1_10_1 --> null

t10_i1_20_2 --> null

t10_i1_30_3 --> null

看到這裡,大家已經是習得SQL與KV映射之道的高手了~