TiDB Best Practice

NO IMAGE
1 Star2 Stars3 Stars4 Stars5 Stars 給文章打分!
Loading...

本文件用於總結在使用 TiDB 時候的一些最佳實踐,主要涉及 SQL 使用、OLAP/OLTP 優化技巧,特別是一些 TiDB 專有的優化開關。
建議先閱讀講解 TiDB 原理的三篇文章(講儲存說計算談排程),再來看這篇文章。

前言

資料庫是一個通用的基礎元件,在開發過程中會考慮到多種目標場景,在具體的業務場景中,需要根據業務的實際情況對資料的引數或者使用方式進行調整。

TiDB 是一個相容 MySQL 協議和語法的分散式資料庫,但是由於其內部實現,特別是支援分散式儲存以及分散式事務,使得一些使用方法和 MySQL 有所區別。

基本概念

TiDB 的最佳實踐與其實現原理密切相關,建議讀者先了解一些基本的實現機制,包括 Raft、分散式事務、資料分片、負載均衡、SQL 到 KV 的對映方案、二級索引的實現方法、分散式執行引擎。下面會做一點簡單的介紹,更詳細的資訊可以參考 PingCAP 公眾號以及知乎專欄的一些文章。

Raft

Raft 是一種一致性協議,能提供強一致的資料複製保證,TiDB 最底層用 Raft 來同步資料。每次寫入都要寫入多數副本,才能對外返回成功,這樣即使丟掉少數副本,也能保證系統中還有最新的資料。比如最大 3 副本的話,每次寫入 2 副本才算成功,任何時候,只丟失一個副本的情況下,存活的兩個副本中至少有一個具有最新的資料。

相比 Master-Slave 方式的同步,同樣是儲存三副本,Raft 的方式更為高效,寫入的延遲取決於最快的兩個副本,而不是最慢的那個副本。所以使用 Raft 同步的情況下,異地多活成為可能。在典型的兩地三中心場景下,每次寫入只需要本資料中心以及離得近的一個資料中心寫入成功就能保證資料的一致性,而並不需要三個資料中心都寫成功。但是這並不意味著在任何場景都能構建跨機房部署的業務,當寫入量比較大時候,機房之間的頻寬和延遲成為關鍵因素,如果寫入速度超過機房之間的頻寬,或者是機房之間延遲過大,整個 Raft 同步機制依然無法很好的運轉。

分散式事務

TiDB 提供完整的分散式事務,事務模型是在 Google Percolator 的基礎上做了一些優化。具體的實現大家可以參考這篇文章。這裡只說兩點:

  • 樂觀鎖

    TiDB 的事務模型採用樂觀鎖,只有在真正提交的時候,才會做衝突檢測,如果有衝突,則需要重試。這種模型在衝突嚴重的場景下,會比較低效,因為重試之前的操作都是無效的,需要重複做。舉一個比較極端的例子,就是把資料庫當做計數器用,如果訪問的併發度比較高,那麼一定會有嚴重的衝突,導致大量的重試甚至是超時。但是如果訪問衝突並不十分嚴重,那麼樂觀鎖模型具備較高的效率。所以在衝突嚴重的場景下,推薦在系統架構層面解決問題,比如將計數器放在 Redis 中。

  • 事務大小限制

    由於分散式事務要做兩階段提交,並且底層還需要做 Raft 複製,如果一個事務非常大,會使得提交過程非常慢,並且會卡住下面的 Raft 複製流程。為了避免系統出現被卡住的情況,我們對事務的大小做了限制:

    • 單條 KV entry 不超過 6MB

    • KV entry 的總條數不超過 30w

    • KV entry 的總大小不超過 100MB

    在 Google 的 Cloud Spanner 上面,也有類似的限制

資料分片

TiKV 自動將底層資料按照 Key 的 Range 進行分片。每個 Region 是一個 Key 的範圍,從 StartKey 到 EndKey 的左閉右開區間。Region 中的 Key-Value 總量超過一定值,就會自動分裂。這部分使用者不需要擔心。

負載均衡

PD 會根據整個 TiKV 叢集的狀態,對叢集的負載進行排程。排程是以 Region 為單位,以 PD 配置的策略為排程邏輯,自動完成。

SQL on KV

TiDB 自動將 SQL 結構對映為 KV 結構。具體的可以參考這篇文件。簡單來說,TiDB 做了兩件事:

  • 一行資料對映為一個 KV,Key 以 TableID 構造字首,以行 ID 為字尾

  • 一條索引對映為一個 KV,Key 以 TableID IndexID 構造字首,以索引值構造字尾

可以看到,對於一個表中的資料或者索引,會具有相同的字首,這樣在 TiKV 的 Key 空間內,這些 Key-Value 會在相鄰的位置。那麼當寫入量很大,並且集中在一個表上面時,就會造成寫入的熱點,特別是連續寫入的資料中某些索引值也是連續的(比如 update time 這種按時間遞增的欄位),會再很少的幾個 Region 上形成寫入熱點,成為整個系統的瓶頸。同樣,如果所有的資料讀取操作也都集中在很小的一個範圍內 (比如在連續的幾萬或者十幾萬行資料上),那麼可能造成資料的訪問熱點。

Secondary Index

TiDB 支援完整的二級索引,並且是全域性索引,很多查詢可以通過索引來優化。如果利用好二級索引,對業務非常重要,很多 MySQL 上的經驗在 TiDB 這裡依然適用,不過 TiDB 還有一些自己的特點,需要注意,這一節主要討論在 TiDB 上使用二級索引的一些注意事項。

  • 二級索引是否有多越好

二級索引能加速查詢,但是要注意新增一個索引是有副作用的,在上一節中我們介紹了索引的儲存模型,那麼每增加一個索引,在插入一條資料的時候,就要新增一個 Key-Value,所以索引越多,寫入越慢,並且空間佔用越大。另外過多的索引也會影響優化器執行時間,並且不合適的索引會誤導優化器。所以索引並不是越多越好。

  • 對哪些列建索引比較合適

    上面提到,索引很重要但不是越多越好,我們需要根據具體的業務特點建立合適的索引。原則上我們需要對查詢中需要用到的列建立索引,目的是提高效能。下面幾種情況適合建立索引:

    • 區分度比較大的列,通過索引能顯著地減少過濾後的行數

    • 有多個查詢條件時,可以選擇組合索引,注意需要把等值條件的列放在組合索引的前面

    這裡舉一個例子,假設常用的查詢是 select * from t where c1 = 10 and c2 = 100 and c3 > 10, 那麼可以考慮建立組合索引 Index cidx (c1, c2, c3),這樣可以用查詢條件構造出一個索引字首進行 Scan。

  • 通過索引查詢和直接掃描 Table 的區別

    TiDB 實現了全域性索引,所以索引和 Table 中的資料並不一定在一個資料分片上,通過索引查詢的時候,需要先掃描索引,得到對應的行 ID,然後通過行 ID 去取資料,所以可能會涉及到兩次網路請求,會有一定的效能開銷。

    如果查詢涉及到大量的行,那麼掃描索引是併發進行,只要第一批結果已經返回,就可以開始去取 Table 的資料,所以這裡是一個並行 Pipeline 的模式,雖然有兩次訪問的開銷,但是延遲並不會很大。

    有兩種情況不會涉及到兩次訪問的問題:

    • 索引中的列已經滿足了查詢需求。比如 Table t 上面的列 c 有索引,查詢是 select c from t where c > 10; 這個時候,只需要訪問索引,就可以拿到所需要的全部資料。這種情況我們稱之為覆蓋索引(Covering Index)。所以如果很關注查詢效能,可以將部分不需要過濾但是需要再查詢結果中返回的列放入索引中,構造成組合索引,比如這個例子: select c1, c2 from t where c1 > 10; 要優化這個查詢可以建立組合索引 Index c12 (c1, c2)

    • 表的 Primary Key 是整數型別。在這種情況下,TiDB 會將 Primary Key 的值當做行 ID,所以如果查詢條件是在 PK 上面,那麼可以直接構造出行 ID 的範圍,直接掃描 Table 資料,獲取結果。

  • 查詢併發度

    資料分散在很多 Region 上,所以 TiDB 在做查詢的時候會併發進行,預設的併發度比較保守,因為過高的併發度會消耗大量的系統資源,且對於 OLTP 型別的查詢,往往不會涉及到大量的資料,較低的併發度已經可以滿足需求。對於 OLAP 型別的 Query,往往需要較高的併發度。所以 TiDB 支援通過 System Variable 來調整查詢併發度。

    • tidb_distsql_scan_concurrency

      在進行掃描資料的時候的併發度,這裡包括掃描 Table 以及索引資料。
      
    • tidb_index_lookup_size

      
      如果是需要訪問索引獲取行 ID 之後再訪問 Table 資料,那麼每次會把一批行 ID 作為一次請求去訪問 Table 資料,這個引數可以設定 Batch 的大小,較大的 Batch 會使得延遲增加,較小的 Batch 可能會造成更多的查詢次數。這個引數的合適大小與查詢涉及的資料量有關。一般不需要調整。
      
    • tidb_index_lookup_concurrency

      如果是需要訪問索引獲取行 ID 之後再訪問 Table 資料,每次通過行 ID 獲取資料時候的併發度通過這個引數調節。
      
  • 通過索引保證結果順序

    索引除了可以用來過濾資料之外,還能用來對資料排序,首先按照索引的順序獲取行 ID,然後再按照行 ID 的返回順序返回行的內容,這樣可以保證返回結果按照索引列有序。前面提到了掃索引和獲取 Row 之間是並行 Pipeline 模式,如果要求按照索引的順序返回 Row,那麼這兩次查詢之間的併發度設定的太高並不會降低延遲,所以預設的併發度比較保守。可以通過 tidb_index_serial_scan_concurrency 變數進行併發度調整。

  • 逆序索引

    目前 TiDB 支援對索引進行逆序 Scan,但是速度要比順序 Scan 慢 5 倍左右,所以儘量避免對索引的逆序 Scan。

場景與實踐

上一節我們討論了一些 TiDB 基本的實現機制及其對使用帶來的影響,本節我們從具體的使用場景出發,談一些更為具體的操作實踐。我們以從部署到支撐業務這條鏈路為序,進行討論。

部署

在部署之前請務必閱讀 TiDB 部署建議以及對硬體的需求

推薦通過 TiDB-Ansible
部署 TiDB 叢集,這個工具可以部署、停止、銷燬、升級整個叢集,非常方便易用。

具體的使用文件在這裡。非常不推薦手動部署,後期的維護和升級會很麻煩。

匯入資料

如果有 Unique Key 並且業務端可以保證資料中沒有衝突,可以在 Session 內開啟這個開關: SET @@session.tidb_skip_constraint_check=1;

另外為了提高寫入效能,可以對 TiKV 的引數進行調優,具體的文件在這裡

請特別注意這個引數:

[raftstore]
# 預設為 true,表示強制將資料刷到磁碟上。如果是非金融安全級別的業務場景,建議設定成 false,
# 以便獲得更高的效能。
sync-log = true

寫入

上面提到了 TiDB 對單個事務的大小有限制,這層限制是在 KV 層面,反映在 SQL 層面的話,簡單來說一行資料會對映為一個 KV entry,每多一個索引,也會增加一個 KV entry,所以這個限制反映在 SQL 層面是:

  • 單行資料不大於 6MB

  • 總的行數*(1 索引個數) < 30w

  • 一次提交的全部資料小於 100MB

另外注意,無論是大小限制還是行數限制,還要考慮 TiDB 做編碼以及事務額外 Key 的開銷,在使用的時候,建議每個事務的行數不要超過 1w 行,否則有可能會超過限制,或者是效能不佳。

建議無論是 Insert,Update 還是 Delete 語句,都通過分 Batch 或者是加 Limit 的方式限制。

在刪除大量資料的時候,建議使用 Delete * from t where xx limit 5000; 這樣的方案,通過迴圈來刪除,用 Affected Rows == 0 作為迴圈結束條件,這樣避免遇到事務大小的限制。

如果一次刪除的資料量非常大,這種迴圈的方式會越來越慢,因為每次刪除都是從前向後遍歷,前面的刪除之後,短時間內會殘留不少刪除標記(後續會被 gc 掉),影響後面的 Delete 語句。如果有可能,建議把 Where 條件細化。舉個例子,假設要刪除 2017-05-26 當天的所有資料,那麼可以這樣做:

for i from 0 to 23:
while affected_rows > 0:
delete * from t where insert_time >= i:00:00 and insert_time < (i 1):00:00 limit 5000;
affected_rows = select affected_rows()

上面是一段虛擬碼,意思就是要把大塊的資料拆成小塊刪除,以避免刪除過程中前面的 Delete 語句影響後面的 Delete 語句。

查詢

看業務的查詢需求以及具體的語句,可以參考這篇文件
可以通過 SET 語句控制 SQL 執行的併發度,另外通過 Hint 控制 Join 物理運算元選擇。

另外 MySQL 標準的索引選擇 Hint 語法,也可以用,通過 Use Index/Ignore Index hint 控制優化器選擇索引。

如果是個 OLTP 和 OLAP 混合型別的業務,可以把 TP 請求和 AP 請求傳送到不同的 tidb-server 上,這樣能夠減小 AP 業務對於 TP 業務的影響。 承載 AP 業務的 tidb-server 推薦使用高配的機器,比如 CPU 核數比較多,記憶體比較大。

監控 & 日誌

Metrics 系統是瞭解系統狀態的最佳方法,建議所有的使用者都部署監控系統。TiDB 使用 Grafana Prometheus 監控系統狀態,如果使用 TiDB-Ansible 部署叢集,那麼會自動部署和配置監控系統。

監控系統中的監控項很多,大部分是給 TiDB 開發者檢視的內容,如果沒有對原始碼比較深入的瞭解,並沒有必要了解這些監控項。我們會精簡出一些和業務相關或者是系統關鍵元件狀態相關的監控項,放在一個獨立的面板中,供使用者使用。

除了監控之外,檢視日誌也是瞭解系統狀態的常用方法。TiDB 的三個元件 tidb-server/tikv-server/pd-server 都有一個 --log-file 的引數,如果啟動的時候設定了這個引數,那麼日誌會儲存著引數所設定的檔案的位置,另外會自動的按天對 Log 檔案做歸檔。如果沒有設定 --log-file 引數,日誌會輸出在 stderr 中。

文件

瞭解一個系統或者解決使用中的問題最好的方法是閱讀文件,明白實現原理,TiDB 有大量的官方文件,希望大家在遇到問題的時候能先嚐試通過文件或者搜尋 Issue list 尋找解決方案。官方文件在這裡。如果希望閱讀英文文件,可以看這裡

其中的 FAQ
故障診斷章節建議大家仔細閱讀。另外 TiDB 還有一些不錯的工具,也有配套的文件,具體的見各項工具的 GitHub 頁面。

除了文件之外,還有很多不錯的文章介紹 TiDB 的各項技術細節內幕,大家可以關注下面這些文章釋出渠道:

TiDB 的最佳適用場景

簡單來說,TiDB 適合具備下面這些特點的場景:

  • 資料量大,單機儲存不下

  • 不希望做 Sharding 或者懶得做 Sharding

  • 訪問模式上沒有明顯的熱點

  • 需要事務、需要強一致、需要災備

相關文章

軟體開發工具 最新文章