天天看點

TiDB 最佳實踐

本文檔總結使用 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 的基礎上做了一些優化。具體的實作可以參考《Percolator 和 TiDB 事務算法》這篇文章。本文檔隻讨論以下幾點:

  • 樂觀鎖

    TiDB 的樂觀事務模型,隻有在真正送出的時候,才會做沖突檢測。如果有沖突,則需要重試。這種模型在沖突嚴重的場景下,會比較低效,因為重試之前的操作都是無效的,需要重複做。舉一個比較極端的例子,就是把資料庫當做計數器用,如果通路的并發度比較高,那麼一定會有嚴重的沖突,導緻大量的重試甚至是逾時。但是如果通路沖突并不十分嚴重,那麼樂觀鎖模型具備較高的效率。在沖突嚴重的場景下,推薦使用悲觀鎖,或在系統架構層面解決問題,比如将計數器放在 Redis 中。

  • 悲觀鎖

    TiDB 的悲觀事務模式,悲觀事務的行為和 MySQL 基本一緻,在執行階段就會上鎖,先到先得,避免沖突情況下的重試,可以保證有較多沖突的事務的成功率。悲觀鎖同時解決了希望通過 

    select for update

     對資料提前鎖定的場景。但如果業務場景本身沖突較少,樂觀鎖的性能會更有優勢。
  • 事務大小限制

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

    • 單個事務包含的 SQL 語句不超過 5000 條(預設)

      - 單條 KV entry 不超過 6MB(預設) - KV entry 的總大小不超過 10G

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

資料分片

TiKV 自動将底層資料按照 Key 的 Range 進行分片。每個 Region 是一個 Key 的範圍,從 

StartKey

 到 

EndKey

 的左閉右開區間。Region 中的 Key-Value 總量超過一定值,就會自動分裂。這部分使用者不需要擔心。

負載均衡

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

SQL on KV

TiDB 自動将 SQL 結構映射為 KV 結構。具體的可以參考《三篇文章了解 TiDB 技術内幕 - 說計算》這篇文檔。簡單來說,TiDB 執行了以下操作:

  • 一行資料映射為一個 KV,Key 以 

    TableID

     構造字首,以行 ID 為字尾
  • 一條索引映射為一個 KV,Key 以 

    TableID+IndexID

     構造字首,以索引值構造字尾

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

二級索引

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 慢一些,通常情況下慢 20%,在資料頻繁修改造成版本較多的情況下,會慢的更多。如果可能,建議避免對索引的逆序 Scan。

場景與實踐

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

部署

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

推薦通過 TiUP 部署 TiDB 叢集,這個工具可以部署、停止、銷毀、更新整個叢集,非常友善易用。非常不推薦手動部署,後期的維護和更新會很麻煩。

導入資料

為了提高導入資料期間的寫入性能,可以對 TiKV 的參數進行調優,具體的文檔檢視 TiKV 性能參數調優。

寫入

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

注意

對事務的大小限制,要考慮 TiDB 做編碼以及事務額外 Key 的開銷,在使用的時候,建議每個事務的行數不超過 200 行,且單行資料小于 100k,否則可能性能不佳。

建議無論是 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 語句。

查詢

看業務的查詢需求以及具體的語句,可以參考 TiDB 專用系統變量和文法這篇文檔。可以通過 SET 語句控制 SQL 執行的并發度,另外通過 Hint 控制 Join 實體算子選擇。

另外 MySQL 标準的索引選擇 Hint 文法,也可以用,通過 

Use Index/Ignore Index hint

 控制優化器選擇索引。

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

但徹底的隔離 OLTP 和 OLAP,推薦将 OLAP 的業務跑在 TiFlash 上。TiFlash 是列存引擎,在 OLAP 的分析查詢場景上,性能極具亮點,TiFlash 可以在存儲層上做到實體隔離,并可做到一緻性讀取。

監控和日志

Metrics 系統是了解系統狀态的最佳方法,建議所有的使用者都部署監控系統。

TiDB 使用 Grafana + Prometheus 監控系統狀态。如果使用 TiUP 部署叢集,那麼會自動部署和配置監控系統。

監控系統中的監控項很多,大部分是給 TiDB 開發者檢視的内容,如果沒有對源代碼比較深入的了解,并沒有必要了解這些監控項。我們會精簡出一些和業務相關或者是系統關鍵元件狀态相關的監控項,放在一個獨立的 

overview

 面闆中,供使用者使用。

除了監控之外,檢視日志也是了解系統狀态的常用方法。TiDB 的三個元件 tidb-server/tikv-server/pd-server 都有一個 

--log-file

 的參數。如果啟動的時候設定了這個參數,那麼日志會儲存着參數所設定的檔案的位置,另外會自動的按天對 Log 檔案做歸檔。如果沒有設定 

--log-file

 參數,日志會輸出在 

stderr

 中。

從 4.0 版本開始,從解決易用性的角度出發,提供了 TiDB Dashboard UI 系統,通過浏覽器通路 

http://PD_IP:PD_PORT/dashboard

 即可打開 TiDB Dashboard。TiDB Dashboard 可以提供叢集狀态、性能分析、流量可視化、SQL 診斷、日志搜尋等功能。

文檔

了解一個系統或者解決使用中的問題最好的方法是閱讀文檔,明白實作原理。TiDB 有大量的官方文檔,希望大家在遇到問題的時候能先嘗試通過文檔或者搜尋 Issue list 尋找解決方案。官方文檔檢視 docs-cn。如果希望閱讀英文文檔,可以檢視 docs。

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

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

  • 公衆号:微信搜尋 PingCAP
  • 知乎專欄:TiDB 的後花園
  • 官方部落格

TiDB 的最佳适用場景

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

  • 資料量大,單機儲存不下
  • 不希望做 Sharding 或者懶得做 Sharding
  • 通路模式上沒有明顯的熱點
  • 需要事務、需要強一緻、需要災備
  • 希望 Real-Time HTAP,減少存儲鍊路