TiDB Best Practice
本文檔用於總結在使用 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 來調整查詢並發度。
ref="https://github.com/pingcap/docs-cn/blob/master/sql/tidb-specific.md#tidb_distsql_scan_concurrency">tidbdistsqlscan_concurrency在進行掃描數據的時候的並發度,這裡包括掃描 Table 以及索引數據。
ref="https://github.com/pingcap/docs-cn/blob/master/sql/tidb-specific.md#tidb_index_lookup_size">tidbindexlookup_size如果是需要訪問索引獲取行 ID 之後再訪問 Table 數據,那麼每次會把一批行 ID 作為一次請求去訪問 Table 數據,這個參數可以設置 Batch 的大小,較大的 Batch 會使得延遲增加,較小的 Batch 可能會造成更多的查詢次數。這個參數的合適大小與查詢涉及的數據量有關。一般不需要調整。
ref="https://github.com/pingcap/docs-cn/blob/master/sql/tidb-specific.md#tidb_index_lookup_concurrency">tidbindexlookup_concurrency
如果是需要訪問索引獲取行 ID 之後再訪問 Table 數據,每次通過行 ID 獲取數據時候的並發度通過這個參數調節。
- 通過索引保證結果順序
索引除了可以用來過濾數據之外,還能用來對數據排序,首先按照索引的順序獲取行 ID,然後再按照行 ID 的返回順序返回行的內容,這樣可以保證返回結果按照索引列有序。前面提到了掃索引和獲取 Row 之間是並行 + Pipeline 模式,如果要求按照索引的順序返回 Row,那麼這兩次查詢之間的並發度設置的太高並不會降低延遲,所以默認的並發度比較保守。可以通過 ref="https://gihttp://thub.com/pingcap/docs-cn/blob/master/sql/tidb-specific.md#tidb_index_serial_scan_concurrency">tidbindexserialscanconcurrency 變數進行並發度調整。
- 逆序索引
目前 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 的各項技術細節內幕,大家可以關注下面這些文章發布渠道:
- 公眾號:微信搜索 PingCAP
- 知乎專欄:TiDB 的後花園
官方博客
TiDB 的最佳適用場景
簡單來說,TiDB 適合具備下面這些特點的場景:
- 數據量大,單機保存不下
- 不希望做 Sharding 或者懶得做 Sharding
- 訪問模式上沒有明顯的熱點
- 需要事務、需要強一致、需要災備
推薦閱讀:
※爬取知乎60萬用戶信息之後的簡單分析
※MySQL小巧而強大的管理工具
※圖解 SQL 里的各種 JOIN
※有哪些值得推薦的 Kettle 的學習資料?
※現在資料庫的發展方向如何,在商業智能領域中(主要處理數據業務)作為一名 ETL 調度人員所需要的專業知識有哪些?
TAG:数据库 |